applicationId "com.stoutner.privacybrowser.free"
}
}
-
- // `return void` removes the lint error: `Not all execution paths return a value`.
- return void
}
dependencies {
compile fileTree(include: ['*.jar'], dir: 'libs')
- compile 'com.android.support:design:25.1.1'
+ compile 'com.android.support:design:25.2.0'
// Only compile `com.google.firebase:firebase-ads:9.8.0` for the free flavor.
freeCompile 'com.google.firebase:firebase-ads:9.8.0'
android:fullBackupContent="false"
android:supportsRtl="true" >
- <!-- If `android:name="android.webkit.WebView.MetricsOptOut"` is not `true` then `WebViews` will upload metrics to Google.
- https://developer.android.com/reference/android/webkit/WebView.html -->
+ <!-- If `android:name="android.webkit.WebView.MetricsOptOut"` is not `true` then `WebViews` will upload metrics to Google. <https://developer.android.com/reference/android/webkit/WebView.html> -->
<meta-data
android:name="android.webkit.WebView.MetricsOptOut"
android:value="true" />
android:persistableMode="persistNever"
tools:ignore="UnusedAttribute" />
- <!-- `android:configChanges="orientation|screenSize"` makes the activity not reload when the orientation changes.
- `android:persistableMode="persistNever"` removes Privacy Browser from the recents screen on a device reboot.
+ <!-- `android:persistableMode="persistNever"` removes Privacy Browser from the recents screen on a device reboot.
`tools:ignore="unusedAttribute"` removes the lint warning that `persistableMode` does not apply to API < 21. -->
<activity
android:name=".activities.DomainsList"
android:label="@string/domains"
android:theme="@style/PrivacyBrowser.SecondaryActivity"
android:parentActivityName=".activities.MainWebView"
- android:configChanges="orientation|screenSize"
android:screenOrientation="fullUser"
android:persistableMode="persistNever"
tools:ignore="UnusedAttribute" />
-/**
- * Copyright 2016 Soren Stoutner <soren@stoutner.com>.
+/*
+ * Copyright 2016-2017 Soren Stoutner <soren@stoutner.com>.
*
* This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
*
import java.io.ByteArrayOutputStream;
-public class Bookmarks extends AppCompatActivity implements CreateBookmark.CreateBookmarkListener,
- CreateBookmarkFolder.CreateBookmarkFolderListener, EditBookmark.EditBookmarkListener,
- EditBookmarkFolder.EditBookmarkFolderListener, MoveToFolder.MoveToFolderListener {
+public class Bookmarks extends AppCompatActivity implements CreateBookmark.CreateBookmarkListener, CreateBookmarkFolder.CreateBookmarkFolderListener, EditBookmark.EditBookmarkListener, EditBookmarkFolder.EditBookmarkFolderListener,
+ MoveToFolder.MoveToFolderListener {
// `bookmarksDatabaseHelper` is public static so it can be accessed from `EditBookmark` and `MoveToFolder`. It is also used in `onCreate()`,
// `onCreateBookmarkCreate()`, `updateBookmarksListView()`, and `updateBookmarksListViewExcept()`.
appBar.setDisplayHomeAsUpEnabled(true);
- // Initialize the database handler and the ListView. `this` specifies the context. The two `null`s do not specify the database name or a `CursorFactory`.
- // The `0` is to specify a database version, but that is set instead using a constant in `BookmarksDatabaseHelper`.
+ // Initialize the database handler and the `ListView`. `this` specifies the context. The two `nulls` do not specify the database name or a `CursorFactory`.
+ // The `0` specifies a database version, but that is ignored and set instead using a constant in `BookmarksDatabaseHelper`.
bookmarksDatabaseHelper = new BookmarksDatabaseHelper(this, null, null, 0);
bookmarksListView = (ListView) findViewById(R.id.bookmarks_listview);
- // Set currentFolder to the home folder, which is null in the database.
+ // Set currentFolder to the home folder, which is `""` in the database.
currentFolder = "";
// Display the bookmarks in the ListView.
// Convert the id from long to int to match the format of the bookmarks database.
int databaseID = (int) id;
- // Get the bookmark `Cursor` and move it to the first row.
+ // Get the bookmark `Cursor` for this ID and move it to the first row.
Cursor bookmarkCursor = bookmarksDatabaseHelper.getBookmarkCursor(databaseID);
bookmarkCursor.moveToFirst();
@Override
public void onCreateBookmark(AppCompatDialogFragment dialogFragment) {
- // Get the `EditText`s from the `createBookmarkDialogFragment` and extract the strings.
+ // Get the `EditTexts` from the `dialogFragment`.
EditText createBookmarkNameEditText = (EditText) dialogFragment.getDialog().findViewById(R.id.create_bookmark_name_edittext);
- String bookmarkNameString = createBookmarkNameEditText.getText().toString();
EditText createBookmarkUrlEditText = (EditText) dialogFragment.getDialog().findViewById(R.id.create_bookmark_url_edittext);
+
+ // Extract the strings from the `EditTexts`.
+ String bookmarkNameString = createBookmarkNameEditText.getText().toString();
String bookmarkUrlString = createBookmarkUrlEditText.getText().toString();
// Convert the favoriteIcon Bitmap to a byte array.
// Create the bookmark.
bookmarksDatabaseHelper.createBookmark(bookmarkNameString, bookmarkUrlString, newBookmarkDisplayOrder, currentFolder, favoriteIconByteArray);
- // Refresh the ListView. `setSelection` scrolls to the bottom of the list.
+ // Refresh the `ListView`. `setSelection` scrolls to the bottom of the list.
updateBookmarksListView(currentFolder);
bookmarksListView.setSelection(newBookmarkDisplayOrder);
}
// Get a `Cursor` with the current contents of the bookmarks database.
bookmarksCursor = bookmarksDatabaseHelper.getAllBookmarksCursorByDisplayOrder(folderName);
- // Setup `bookmarksCursorAdapter` with `this` context. `false` disables autoRequery.
+ // Setup `bookmarksCursorAdapter` with `this` context. `false` disables `autoRequery`.
CursorAdapter bookmarksCursorAdapter = new CursorAdapter(this, bookmarksCursor, false) {
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
}
};
- // Update the ListView.
+ // Update the `ListView`.
bookmarksListView.setAdapter(bookmarksCursorAdapter);
}
-/**
- * Copyright 2016 Soren Stoutner <soren@stoutner.com>.
+/*
+ * Copyright 2016-2017 Soren Stoutner <soren@stoutner.com>.
*
* This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
*
-/**
+/*
* Copyright 2017 Soren Stoutner <soren@stoutner.com>.
*
* This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
package com.stoutner.privacybrowser.activities;
+import android.content.Context;
+import android.database.Cursor;
import android.os.Bundle;
+import android.support.design.widget.FloatingActionButton;
+import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
+import android.support.v7.app.AppCompatDialogFragment;
+import android.support.v7.widget.Toolbar;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.CursorAdapter;
+import android.widget.EditText;
+import android.widget.ListView;
+import android.widget.TextView;
import com.stoutner.privacybrowser.R;
+import com.stoutner.privacybrowser.dialogs.AddDomain;
+import com.stoutner.privacybrowser.helpers.DomainsDatabaseHelper;
+
+public class DomainsList extends AppCompatActivity implements AddDomain.AddDomainListener {
+ // `domainsDatabaseHelper` is used in `onCreate()`, `onAddDomain()`, and `updateDomainsRecyclerView()`.
+ private static DomainsDatabaseHelper domainsDatabaseHelper;
+
+ // `domainsRecyclerView` is used in `onCreate()` and `updateDomainsListView()`.
+ private ListView domainsListView;
-public class DomainsList extends AppCompatActivity {
private boolean twoPaneMode;
@Override
super.onCreate(savedInstanceState);
setContentView(R.layout.domains_list_coordinatorlayout);
+ // We need to use the `SupportActionBar` from `android.support.v7.app.ActionBar` until the minimum API is >= 21.
+ final Toolbar bookmarksAppBar = (Toolbar) findViewById(R.id.domains_toolbar);
+ setSupportActionBar(bookmarksAppBar);
+
+ // Display the home arrow on `SupportActionBar`.
+ ActionBar appBar = getSupportActionBar();
+ assert appBar != null;// This assert removes the incorrect warning in Android Studio on the following line that `appBar` might be null.
+ appBar.setDisplayHomeAsUpEnabled(true);
+
+ // Initialize the database handler. `this` specifies the context. The two `nulls` do not specify the database name or a `CursorFactory`.
+ // The `0` specifies the database version, but that is ignored and set instead using a constant in `DomainsDatabaseHelper`.
+ domainsDatabaseHelper = new DomainsDatabaseHelper(this, null, null, 0);
+
+ // Determine if we are in two pane mode. `domains_list_framelayout` is only populated if two panes are present.
+ twoPaneMode = ((findViewById(R.id.domains_list_framelayout)) != null);
+
+ // Initialize `domainsListView`.
+ domainsListView = (ListView) findViewById(R.id.domains_listview);
+
+ domainsListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
+ @Override
+ public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+ // Convert the id from long to int to match the format of the domains database.
+ int databaseId = (int) id;
+
+ // Get the database `Cursor` for this ID and move it to the first row.
+ Cursor domainCursor = domainsDatabaseHelper.getCursorForId(databaseId);
+ domainCursor.moveToFirst();
+
+ // If the
+ }
+ });
+
+ FloatingActionButton addDomainFAB = (FloatingActionButton) findViewById(R.id.add_domain_fab);
+ addDomainFAB.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ // Show the `AddDomain` `AlertDialog` and name the instance `@string/add_domain`.
+ AppCompatDialogFragment addDomainDialog = new AddDomain();
+ addDomainDialog.show(getSupportFragmentManager(), getResources().getString(R.string.add_domain));
+ }
+ });
+
+ // Load the `ListView`.
+ updateDomainsListView();
+ }
+
+ @Override
+ public void onAddDomain(AppCompatDialogFragment dialogFragment) {
+ // Get the `domainNameEditText` from `dialogFragment` and extract the string.
+ EditText domainNameEditText = (EditText) dialogFragment.getDialog().findViewById(R.id.domain_name_edittext);
+ String domainNameString = domainNameEditText.getText().toString();
+
+ // Create the domain.
+ domainsDatabaseHelper.addDomain(domainNameString);
+
+ // Refresh the `ListView`.
+ updateDomainsListView();
+ }
+
+ private void updateDomainsListView() {
+ // Get a `Cursor` with the current contents of the domains database.
+ Cursor domainsCursor = domainsDatabaseHelper.getCursorOrderedByDomain();
+
+ // Setup `domainsCursorAdapter` with `this` context. `false` disables `autoRequery`.
+ CursorAdapter domainsCursorAdapter = new CursorAdapter(this, domainsCursor, false) {
+ @Override
+ public View newView(Context context, Cursor cursor, ViewGroup parent) {
+ // Inflate the individual item layout. `false` does not attach it to the root.
+ return getLayoutInflater().inflate(R.layout.domain_name_linearlayout, parent, false);
+ }
+
+ @Override
+ public void bindView(View view, Context context, Cursor cursor) {
+ // Set the domain name.
+ String domainNameString = cursor.getString(cursor.getColumnIndex(DomainsDatabaseHelper.DOMAIN));
+ TextView domainNameTextView = (TextView) view.findViewById(R.id.domain_name_textview);
+ domainNameTextView.setText(domainNameString);
+ }
+ };
+
+ // Update the `RecyclerView`.
+ domainsListView.setAdapter(domainsCursorAdapter);
}
}
\ No newline at end of file
-/**
+/*
* Copyright 2015-2017 Soren Stoutner <soren@stoutner.com>.
*
* Download cookie code contributed 2017 Hendrik Knackstedt. Copyright assigned to Soren Stoutner <soren@stoutner.com>.
--- /dev/null
+/*
+ * Copyright 2017 Soren Stoutner <soren@stoutner.com>.
+ *
+ * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
+ *
+ * Privacy Browser is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Privacy Browser is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Privacy Browser. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package com.stoutner.privacybrowser.dialogs;
+
+import android.annotation.SuppressLint;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.os.Bundle;
+// We have to use `AppCompatDialogFragment` instead of `DialogFragment` or an error is produced on API <= 22.
+import android.support.annotation.NonNull;
+import android.support.v7.app.AppCompatDialogFragment;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.EditText;
+
+import com.stoutner.privacybrowser.R;
+
+public class AddDomain extends AppCompatDialogFragment {
+ // The public interface is used to send information back to the parent activity.
+ public interface AddDomainListener {
+ void onAddDomain(AppCompatDialogFragment dialogFragment);
+ }
+
+ // `addDomainListener` is used in `onAttach()` and `onCreateDialog()`.
+ private AddDomainListener addDomainListener;
+
+
+ public void onAttach(Context context) {
+ super.onAttach(context);
+
+ // Get a handle for `AddDomainListener` from `context`.
+ try {
+ addDomainListener = (AddDomainListener) context;
+ } catch(ClassCastException exception) {
+ throw new ClassCastException(context.toString() + " must implement `AddDomainListener`.");
+ }
+ }
+
+ // `@SuppressLing("InflateParams")` removes the warning about using `null` as the parent view group when inflating the `AlertDialog`.
+ @SuppressLint("InflateParams")
+ @Override
+ @NonNull
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ // Use `AlertDialog.Builder` to create the `AlertDialog`. The style formats the color of the button text.
+ AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(getActivity(), R.style.LightAlertDialog);
+ dialogBuilder.setTitle(R.string.add_domain);
+ // The parent view is `null` because it will be assigned by the `AlertDialog`.
+ dialogBuilder.setView(getActivity().getLayoutInflater().inflate(R.layout.add_domain_dialog, null));
+
+ // Set an `onClick()` listener for the negative button.
+ dialogBuilder.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ // Do nothing. The `AlertDialog` will close automatically.
+ }
+ });
+
+ // Set an `onClick()` listener for the positive button.
+ dialogBuilder.setPositiveButton(R.string.add, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ // Return the `DialogFragment` to the parent activity on add.
+ addDomainListener.onAddDomain(AddDomain.this);
+ }
+ });
+
+ // Create an `AlertDialog` from the `AlertDialog.Builder`.
+ final AlertDialog alertDialog = dialogBuilder.create();
+
+ // Remove the warning below the `setSoftInputMode` might produce `java.lang.NullPointerException`.
+ assert alertDialog.getWindow() != null;
+
+ // Show the keyboard when the `AlertDialog` is displayed on the screen.
+ alertDialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
+
+ // We need to show the `AlertDialog` before w3e can call `setOnKeyListener()` below.
+ alertDialog.show();
+
+ // Allow the `enter` key on the keyboard to create the domain from `add_domain_edittext`.
+ EditText addDomainEditText = (EditText) alertDialog.findViewById(R.id.domain_name_edittext);
+ addDomainEditText.setOnKeyListener(new View.OnKeyListener() {
+ public boolean onKey(View view, int keyCode, KeyEvent event) {
+ // If the event is a key-down on the `enter` key, select the `PositiveButton` `Add`.
+ if ((keyCode == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN)) {
+ // Trigger `addDomainListener` and return the `DialogFragment` to the parent activity.
+ addDomainListener.onAddDomain(AddDomain.this);
+ // Manually dismiss the `AlertDialog`.
+ alertDialog.dismiss();
+ // Consume the event.
+ return true;
+ } else { // If any other key was pressed, do not consume the event.
+ return false;
+ }
+ }
+ });
+
+ // `onCreateDialog()` requires the return of an `AlertDialog`.
+ return alertDialog;
+ }
+}
-/**
- * Copyright 2016 Soren Stoutner <soren@stoutner.com>.
+/*
+ * Copyright 2016-2017 Soren Stoutner <soren@stoutner.com>.
*
* This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
*
package com.stoutner.privacybrowser.dialogs;
import android.annotation.SuppressLint;
+import android.app.AlertDialog;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.support.annotation.NonNull;
-// If we don't use `android.support.v7.app.AlertDialog` instead of `android.app.AlertDialog` then the dialog will be covered by the keyboard.
-import android.support.v7.app.AlertDialog;
-// We have to use `AppCompatDialogFragment` instead of `DialogFragment` or an error is produced on API <=22.
+// We have to use `AppCompatDialogFragment` instead of `DialogFragment` or an error is produced on API <= 22.
import android.support.v7.app.AppCompatDialogFragment;
import android.view.KeyEvent;
import android.view.View;
try {
createBookmarkListener = (CreateBookmarkListener) context;
} catch(ClassCastException exception) {
- throw new ClassCastException(context.toString() + " must implement CreateBookmarkListener.");
+ throw new ClassCastException(context.toString() + " must implement `CreateBookmarkListener`.");
}
}
// Remove the warning below that `setSoftInputMode` might produce `java.lang.NullPointerException`.
assert alertDialog.getWindow() != null;
- // Show the keyboard when the `Dialog` is displayed on the screen.
+ // Show the keyboard when the `AlertDialog` is displayed on the screen.
alertDialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
// We need to show the `AlertDialog` before we can call `setOnKeyListener()` below.
EditText createBookmarkNameEditText = (EditText) alertDialog.findViewById(R.id.create_bookmark_name_edittext);
assert createBookmarkNameEditText != null; // Remove the warning below that `createBookmarkNameEditText` might be `null`.
createBookmarkNameEditText.setOnKeyListener(new View.OnKeyListener() {
- public boolean onKey(View v, int keyCode, KeyEvent event) {
- // If the event is a key-down on the `enter` button, select the `PositiveButton` `Create`.
- if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) {
+ public boolean onKey(View view, int keyCode, KeyEvent event) {
+ // If the event is a key-down on the `enter` key, select the `PositiveButton` `Create`.
+ if ((keyCode == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN)) {
// Trigger `createBookmarkListener` and return the `DialogFragment` to the parent activity.
createBookmarkListener.onCreateBookmark(CreateBookmark.this);
// Manually dismiss the `AlertDialog`.
// Allow the `enter` key on the keyboard to create the bookmark from `create_bookmark_url_edittext`.
createBookmarkUrlEditText.setOnKeyListener(new View.OnKeyListener() {
public boolean onKey(View v, int keyCode, KeyEvent event) {
- // If the event is a key-down on the "enter" button, select the PositiveButton "Create".
- if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) {
+ // If the event is a key-down on the "enter" key, select the PositiveButton "Create".
+ if ((keyCode == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN)) {
// Trigger `createBookmarkListener` and return the DialogFragment to the parent activity.
createBookmarkListener.onCreateBookmark(CreateBookmark.this);
// Manually dismiss the `AlertDialog`.
-/**
- * Copyright 2016 Soren Stoutner <soren@stoutner.com>.
+/*
+ * Copyright 2016-2017 Soren Stoutner <soren@stoutner.com>.
*
* This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
*
package com.stoutner.privacybrowser.dialogs;
import android.annotation.SuppressLint;
+import android.app.AlertDialog;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.os.Bundle;
import android.support.annotation.NonNull;
-// If we don't use `android.support.v7.app.AlertDialog` instead of `android.app.AlertDialog` then the dialog will be covered by the keyboard.
-import android.support.v7.app.AlertDialog;
// We have to use `AppCompatDialogFragment` instead of `DialogFragment` or an error is produced on API <=22.
import android.support.v7.app.AppCompatDialogFragment;
import android.view.KeyEvent;
-/**
- * Copyright 2015-2016 Soren Stoutner <soren@stoutner.com>.
+/*
+ * Copyright 2015-2017 Soren Stoutner <soren@stoutner.com>.
*
* This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
*
package com.stoutner.privacybrowser.dialogs;
import android.annotation.SuppressLint;
+import android.app.AlertDialog;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.support.annotation.NonNull;
-// If we don't use android.support.v7.app.AlertDialog instead of android.app.AlertDialog then the dialog will be covered by the keyboard.
-import android.support.v7.app.AlertDialog;
// We have to use `AppCompatDialogFragment` instead of `DialogFragment` or an error is produced on API <=22.
import android.support.v7.app.AppCompatDialogFragment;
import android.view.KeyEvent;
-/**
- * Copyright 2016 Soren Stoutner <soren@stoutner.com>.
+/*
+ * Copyright 2016-2017 Soren Stoutner <soren@stoutner.com>.
*
* This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
*
package com.stoutner.privacybrowser.dialogs;
import android.annotation.SuppressLint;
+import android.app.AlertDialog;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.net.Uri;
import android.os.Bundle;
import android.support.annotation.NonNull;
-// `android.support.v7.app.AlertDialog` uses more of the horizontal screen real estate versus `android.app.AlertDialog's` smaller width.
-import android.support.v7.app.AlertDialog;
// We have to use `AppCompatDialogFragment` instead of `DialogFragment` or an error is produced on API <=22.
import android.support.v7.app.AppCompatDialogFragment;
import android.view.KeyEvent;
-/**
- * Copyright 2016 Soren Stoutner <soren@stoutner.com>.
+/*
+ * Copyright 2016-2017 Soren Stoutner <soren@stoutner.com>.
*
* This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
*
package com.stoutner.privacybrowser.dialogs;
import android.annotation.SuppressLint;
+import android.app.AlertDialog;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.net.Uri;
import android.os.Bundle;
import android.support.annotation.NonNull;
-import android.support.v7.app.AlertDialog;
+// We have to use `AppCompatDialogFragment` instead of `DialogFragment` or an error is produced on API <= 22.
import android.support.v7.app.AppCompatDialogFragment;
import android.view.KeyEvent;
import android.view.LayoutInflater;
-/**
- * Copyright 2016 Soren Stoutner <soren@stoutner.com>.
+/*
+ * Copyright 2016-2017 Soren Stoutner <soren@stoutner.com>.
*
* This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
*
package com.stoutner.privacybrowser.dialogs;
import android.annotation.SuppressLint;
+import android.app.AlertDialog;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
-// If we don't use `android.support.v7.app.AlertDialog` instead of `android.app.AlertDialog` then the dialog will be covered by the keyboard.
import android.support.annotation.NonNull;
-import android.support.v7.app.AlertDialog;
// We have to use `AppCompatDialogFragment` instead of `DialogFragment` or an error is produced on API <=22.
import android.support.v7.app.AppCompatDialogFragment;
import android.view.KeyEvent;
-/**
+/*
* Copyright 2016 Soren Stoutner <soren@stoutner.com>.
*
* This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
package com.stoutner.privacybrowser.dialogs;
import android.annotation.SuppressLint;
+import android.app.AlertDialog;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.support.annotation.NonNull;
-// If we don't use `android.support.v7.app.AlertDialog` instead of `android.app.AlertDialog` then the dialog will be covered by the keyboard.
-import android.support.v7.app.AlertDialog;
// We have to use `AppCompatDialogFragment` instead of `DialogFragment` or an error is produced on API <=22.
import android.support.v7.app.AppCompatDialogFragment;
import android.view.KeyEvent;
-/**
- * Copyright 2016 Soren Stoutner <soren@stoutner.com>.
+/*
+ * Copyright 2016-2017 Soren Stoutner <soren@stoutner.com>.
*
* This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
*
package com.stoutner.privacybrowser.dialogs;
import android.annotation.SuppressLint;
+import android.app.AlertDialog;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.support.annotation.NonNull;
-// If we don't use `android.support.v7.app.AlertDialog` instead of `android.app.AlertDialog` then the dialog will be covered by the keyboard.
import android.support.v4.content.ContextCompat;
// We have to use `AppCompatDialogFragment` instead of `DialogFragment` or an error is produced on API <=22.
-import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatDialogFragment;
import android.view.View;
import android.view.ViewGroup;
-/**
+/*
* Copyright 2016-2017 Soren Stoutner <soren@stoutner.com>.
*
* This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
import android.net.http.SslError;
import android.os.Bundle;
import android.support.annotation.NonNull;
+// We have to use `AppCompatDialogFragment` instead of `DialogFragment` or an error is produced on API <= 22.
import android.support.v7.app.AppCompatDialogFragment;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
-/**
- * Copyright 2016 Soren Stoutner <soren@stoutner.com>.
+/*
+ * Copyright 2016-2017 Soren Stoutner <soren@stoutner.com>.
*
* This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
*
package com.stoutner.privacybrowser.dialogs;
import android.annotation.SuppressLint;
+import android.app.AlertDialog;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
-// `android.support.v7.app.AlertDialog` uses more of the horizontal screen real estate versus `android.app.AlertDialog's` smaller width.
import android.support.annotation.NonNull;
import android.support.v4.content.ContextCompat;
-import android.support.v7.app.AlertDialog;
+// We have to use `AppCompatDialogFragment` instead of `DialogFragment` or an error is produced on API <= 22. `android.support.v7.app.AlertDialog` also uses more of the horizontal screen real estate versus `android.app.AlertDialog's` smaller width.
import android.support.v7.app.AppCompatDialogFragment;
import android.util.Base64;
import android.view.LayoutInflater;
-/**
+/*
* Copyright 2016-2017 Soren Stoutner <soren@stoutner.com>.
*
* This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
-/**
- * Copyright 2016 Soren Stoutner <soren@stoutner.com>.
+/*
+ * Copyright 2016-2017 Soren Stoutner <soren@stoutner.com>.
*
* This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
*
public static final String FAVORITE_ICON = "favoriteicon";
// Initialize the database. The lint warnings for the unused parameters are suppressed.
- public BookmarksDatabaseHelper(Context context, @SuppressWarnings("UnusedParameters") String name, SQLiteDatabase.CursorFactory factory, @SuppressWarnings("UnusedParameters") int version) {
- super(context, BOOKMARKS_DATABASE, factory, SCHEMA_VERSION);
+ public BookmarksDatabaseHelper(Context context, @SuppressWarnings("UnusedParameters") String name, SQLiteDatabase.CursorFactory cursorFactory, @SuppressWarnings("UnusedParameters") int version) {
+ super(context, BOOKMARKS_DATABASE, cursorFactory, SCHEMA_VERSION);
}
@Override
public void onCreate(SQLiteDatabase bookmarksDatabase) {
- // Create the database if it doesn't exist.
- String CREATE_BOOKMARKS_TABLE = "CREATE TABLE " + BOOKMARKS_TABLE + " (" +
+ // Setup the SQL string to create the `bookmarks` table.
+ final String CREATE_BOOKMARKS_TABLE = "CREATE TABLE " + BOOKMARKS_TABLE + " (" +
_ID + " integer primary key, " +
DISPLAY_ORDER + " integer, " +
BOOKMARK_NAME + " text, " +
IS_FOLDER + " boolean, " +
FAVORITE_ICON + " blob);";
+ // Create the `bookmarks` table if it doesn't exist.
bookmarksDatabase.execSQL(CREATE_BOOKMARKS_TABLE);
}
}
public void createBookmark(String bookmarkName, String bookmarkURL, int displayOrder, String parentFolder, byte[] favoriteIcon) {
+ // We need to store the bookmark data in a `ContentValues`.
ContentValues bookmarkContentValues = new ContentValues();
// ID is created automatically.
// Get a writable database handle.
SQLiteDatabase bookmarksDatabase = this.getWritableDatabase();
- // The second argument is `null`, which makes it so that completely null rows cannot be created. Not a problem in our case.
+ // Insert a new row. The second argument is `null`, which makes it so that a completely null row cannot be created.
bookmarksDatabase.insert(BOOKMARKS_TABLE, null, bookmarkContentValues);
// Close the database handle.
final String GET_ONE_BOOKMARK = "Select * FROM " + BOOKMARKS_TABLE +
" WHERE " + _ID + " = " + databaseId;
- // Return the results as a `Cursor`. The second argument is `null` because there are no `selectionArgs`.
- // We can't close the `Cursor` because we need to use it in the parent activity.
+ // Return the results as a `Cursor`. The second argument is `null` because there are no `selectionArgs`. We can't close the `Cursor` because we need to use it in the parent activity.
return bookmarksDatabase.rawQuery(GET_ONE_BOOKMARK, null);
}
// Get a readable database handle.
SQLiteDatabase bookmarksDatabase = this.getReadableDatabase();
- // Get everything in the BOOKMARKS_TABLE.
+ // Get everything in `BOOKMARKS_TABLE`.
final String GET_ALL_BOOKMARKS = "Select * FROM " + BOOKMARKS_TABLE;
// Return the results as a Cursor. The second argument is `null` because there are no selectionArgs.
// SQL escape `folderName`.
folderName = DatabaseUtils.sqlEscapeString(folderName);
- // Get everything in the BOOKMARKS_TABLE.
+ // Get everything in the `BOOKMARKS_TABLE` with `folderName` as the `PARENT_FOLDER`.
final String GET_ALL_BOOKMARKS = "Select * FROM " + BOOKMARKS_TABLE +
" WHERE " + PARENT_FOLDER + " = " + folderName +
" ORDER BY " + DISPLAY_ORDER + " ASC";
- // Return the results as a Cursor. The second argument is `null` because there are no selectionArgs.
- // We can't close the Cursor because we need to use it in the parent activity.
+ // Return the results as a `Cursor`. The second argument is `null` because there are no `selectionArgs`. We can't close the `Cursor` because we need to use it in the parent activity.
return bookmarksDatabase.rawQuery(GET_ALL_BOOKMARKS, null);
}
--- /dev/null
+/*
+ * Copyright 2017 Soren Stoutner <soren@stoutner.com>.
+ *
+ * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
+ *
+ * Privacy Browser is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Privacy Browser is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Privacy Browser. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package com.stoutner.privacybrowser.helpers;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+
+public class DomainsDatabaseHelper extends SQLiteOpenHelper {
+ private static final int SCHEMA_VERSION = 1;
+ private static final String DOMAINS_DATABASE = "domains.db";
+ private static final String DOMAINS_TABLE = "domains";
+ private static final String _ID = "_id";
+
+ public static final String DOMAIN = "domain";
+
+
+ // Initialize the database. The lint warnings for the unused parameters are suppressed.
+ public DomainsDatabaseHelper(Context context, @SuppressWarnings("UnusedParameters") String name, SQLiteDatabase.CursorFactory cursorFactory, @SuppressWarnings("UnusedParameters") int version) {
+ super(context, DOMAINS_DATABASE, cursorFactory, SCHEMA_VERSION);
+ }
+
+ @Override
+ public void onCreate(SQLiteDatabase domainsDatabase) {
+ // Setup the SQL string to create the `domains` table.
+ final String CREATE_DOMAINS_TABLE = "CREATE TABLE " + DOMAINS_TABLE + " (" +
+ _ID + " integer primary key, " +
+ DOMAIN + " text);";
+
+ // Create the `domains` table if it doesn't exist.
+ domainsDatabase.execSQL(CREATE_DOMAINS_TABLE);
+ }
+
+ @Override
+ public void onUpgrade(SQLiteDatabase domainsDatabase, int oldVersion, int newVersion) {
+ // Code for upgrading the database will be added here when the schema version > 1.
+ }
+
+ public Cursor getCursorOrderedByDomain() {
+ // Get a readable database handle.
+ SQLiteDatabase domainsDatabase = this.getReadableDatabase();
+
+ // Get everything in `DOMAINS_TABLE` ordered by `DOMAIN`.
+ final String GET_CURSOR_SORTED_BY_DOMAIN = "Select * FROM " + DOMAINS_TABLE +
+ " ORDER BY " + DOMAIN + " ASC";
+
+ // Return the results as a `Cursor`. The second argument is `null` because there are no `selectionArgs`. We can't close the `Cursor` because we need to use it in the parent activity.
+ return domainsDatabase.rawQuery(GET_CURSOR_SORTED_BY_DOMAIN, null);
+ }
+
+ public Cursor getCursorForId(int databaseId) {
+ // Get a readable database handle.
+ SQLiteDatabase domainsDatabase = this.getReadableDatabase();
+
+ // Prepare the SQL statement to ge the `Cursor` for `databaseId`.
+ final String GET_CURSOR_FOR_ID = "Select * FROM " + DOMAINS_TABLE +
+ " WHERE " + _ID + " = " + databaseId;
+
+ // Return the results as a `Cursor`. The second argument is `null` because there are no `selectionArgs`. We can't close the `Cursor` because we need to use it in the parent activity.
+ return domainsDatabase.rawQuery(GET_CURSOR_FOR_ID, null);
+ }
+
+ public void addDomain(String domainName) {
+ // We need to store the domain data in a `ContentValues`.
+ ContentValues domainContentValues = new ContentValues();
+
+ // ID is created automatically.
+ domainContentValues.put(DOMAIN, domainName);
+
+ // Get a writable database handle.
+ SQLiteDatabase domainsDatabase = this.getWritableDatabase();
+
+ // Insert a new row. The second argument is `null`, which makes it so that a completely null row cannot be created.
+ domainsDatabase.insert(DOMAINS_TABLE, null, domainContentValues);
+
+ // Close the database handle.
+ domainsDatabase.close();
+ }
+}
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ Copyright 2017 Soren Stoutner <soren@stoutner.com>.
+
+ This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
+
+ Privacy Browser is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Privacy Browser is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Privacy Browser. If not, see <http://www.gnu.org/licenses/>. -->
+
+<!-- This selector changes the background of activated items in the domains `RecyclerView`. -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item
+ android:state_activated="true"
+ android:drawable="@color/blue_100" />
+</selector>
\ No newline at end of file
<!-- `android:baselineAligned="False"` reduces unneeded computational overhead with `RecyclerViews`. -->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
+ tools:context=".activities.DomainsList"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
android:baselineAligned="false"
android:divider="?android:attr/dividerHorizontal"
- android:showDividers="middle"
- tools:context=".activities.DomainsList">
+ android:showDividers="middle" >
- <android.support.v7.widget.RecyclerView
+ <!-- `android:dividerHeight` must be specified with `android:divider` or the height will be `0dp`. In our case we want the height to be `0dp`. -->
+ <ListView
+ android:id="@+id/domains_listview"
android:layout_height="match_parent"
- android:layout_width="200dp"
- app:layoutManager="LinearLayoutManager" />
+ android:layout_width="0dp"
+ android:layout_weight="1"
+ android:divider="@color/white"
+ android:dividerHeight="0dp" />
+
+ <FrameLayout
+ android:id="@+id/domains_list_framelayout"
+ android:layout_height="match_parent"
+ android:layout_width="0dp"
+ android:layout_weight="3" />
</LinearLayout>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ Copyright 2017 Soren Stoutner <soren@stoutner.com>.
+
+ This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
+
+ Privacy Browser is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Privacy Browser is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Privacy Browser. If not, see <http://www.gnu.org/licenses/>. -->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_height="wrap_content"
+ android:layout_width="match_parent"
+ android:orientation="vertical" >
+
+ <!-- `android.support.design.widget.TextInputLayout` makes the `android:hint` float above the `EditText`. -->
+ <android.support.design.widget.TextInputLayout
+ android:layout_height="wrap_content"
+ android:layout_width="match_parent"
+ android:layout_marginTop="12dp"
+ android:layout_marginBottom="12dp"
+ android:layout_marginStart="4dp"
+ android:layout_marginEnd="4dp" >
+
+ <!-- `android:imeOptions="actionGo" sets the keyboard to have a `go` key instead of a `new line` key. `android:inputType="textUri"` disables spell check in the `EditText`. -->
+ <android.support.design.widget.TextInputEditText
+ android:id="@+id/domain_name_edittext"
+ android:layout_height="wrap_content"
+ android:layout_width="match_parent"
+ android:hint="@string/domain_name"
+ android:imeOptions="actionGo"
+ android:inputType="textUri" />
+ </android.support.design.widget.TextInputLayout>
+</LinearLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<!--
- Copyright 2016 Soren Stoutner <soren@stoutner.com>.
+ Copyright 2016-2017 Soren Stoutner <soren@stoutner.com>.
This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp" >
- <!-- `android:imeOptions="actionGo"` sets the keyboard to have a "go" key instead of a "new line" key.
- `android:inputType="textUri"` disables spell check in the EditText. -->
+ <!-- `android:imeOptions="actionGo"` sets the keyboard to have a `go` key instead of a `new line` key. `android:inputType="textUri"` disables spell check in the `EditText`. -->
<android.support.design.widget.TextInputEditText
android:id="@+id/create_bookmark_name_edittext"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp" >
- <!-- `android:imeOptions="actionGo"` sets the keyboard to have a "go" key instead of a "new line" key.
- `android:inputType="textUri"` disables spell check in the EditText.-->
+ <!-- `android:imeOptions="actionGo"` sets the keyboard to have a `go` key instead of a `new line` key. `android:inputType="textUri"` disables spell check in the `EditText`. -->
<EditText
android:id="@+id/create_bookmark_url_edittext"
android:layout_height="wrap_content"
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ Copyright 2017 Soren Stoutner <soren@stoutner.com>.
+
+ This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
+
+ Privacy Browser is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Privacy Browser is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Privacy Browser. If not, see <http://www.gnu.org/licenses/>. -->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_height="match_parent"
+ android:layout_width="match_parent"
+ android:orientation="horizontal" >
+
+ <!-- Labels `LinearLayout`. -->
+ <LinearLayout
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:orientation="vertical" >
+
+ <TextView
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:text="@string/domain_name"
+ android:labelFor="@id/domain_name_details_edittext"/>
+ </LinearLayout>
+
+ <!-- Data `LinearLayout`. -->
+ <LinearLayout
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:orientation="vertical" >
+
+ <!-- `android:inputType="textUri"` disables spell check in the `EditText`. -->
+ <EditText
+ android:id="@+id/domain_name_details_edittext"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:inputType="textUri" />
+ </LinearLayout>
+
+</LinearLayout>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ Copyright 2017 Soren Stoutner <soren@stoutner.com>.
+
+ This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
+
+ Privacy Browser is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Privacy Browser is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Privacy Browser. If not, see <http://www.gnu.org/licenses/>. -->
+
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_height="wrap_content"
+ android:layout_width="match_parent"
+ android:orientation="horizontal"
+ android:background="@drawable/domains_list_selector" >
+
+ <TextView
+ android:id="@+id/domain_name_textview"
+ android:layout_height="wrap_content"
+ android:layout_width="match_parent"
+ android:textColor="@color/black"
+ android:textSize="22sp"
+ android:layout_margin="10dp"
+ android:maxLines="1"
+ android:ellipsize="end" />
+</LinearLayout>
\ No newline at end of file
You should have received a copy of the GNU General Public License
along with Privacy Browser. If not, see <http://www.gnu.org/licenses/>. -->
-<android.support.v7.widget.RecyclerView
+<!-- `android:dividerHeight` must be specified with `android:divider` or the height will be `0dp`. In our case we want the height to be `0dp`. -->
+<ListView
+ android:id="@+id/domains_listview"
xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ tools:context=".activities.DomainsList"
android:layout_height="match_parent"
- android:layout_width="match_parent"/>
\ No newline at end of file
+ android:layout_width="match_parent"
+ android:divider="@color/white"
+ android:dividerHeight="0dp" />
\ No newline at end of file
<!-- android:theme="@style/PrivacyBrowser.DarkAppBar" makes the text and icons in the AppBar white. -->
<android.support.v7.widget.Toolbar
+ android:id="@+id/domains_toolbar"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:background="@color/blue_700"
android:theme="@style/DarkAppBar"
app:popupTheme="@style/LightPopupOverlay" />
</android.support.design.widget.AppBarLayout>
- </LinearLayout>
- <include layout="@layout/domains_list" />
+ <include layout="@layout/domains_list" />
+ </LinearLayout>
<android.support.design.widget.FloatingActionButton
+ android:id="@+id/add_domain_fab"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:layout_gravity="bottom|end"
<!-- Domains. -->
<string name="domains">Domains</string>
+ <string name="add_domain">Add Domain</string>
+ <string name="add">Add</string>
+ <string name="domain_name">Domain Name</string>
<!-- Guide. -->
<string name="privacy_browser_guide">Privacy Browser Guide</string>
jcenter()
}
dependencies {
- classpath 'com.android.tools.build:gradle:2.2.3'
+ classpath 'com.android.tools.build:gradle:2.3.0'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
-#Tue Aug 16 16:10:23 MST 2016
+#Thu Mar 02 15:10:19 MST 2017
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip