2 * Copyright © 2015-2019 Soren Stoutner <soren@stoutner.com>.
4 * Download cookie code contributed 2017 Hendrik Knackstedt. Copyright assigned to Soren Stoutner <soren@stoutner.com>.
6 * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
8 * Privacy Browser is free software: you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation, either version 3 of the License, or
11 * (at your option) any later version.
13 * Privacy Browser is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with Privacy Browser. If not, see <http://www.gnu.org/licenses/>.
22 package com.stoutner.privacybrowser.activities;
24 import android.Manifest;
25 import android.annotation.SuppressLint;
26 import android.app.Activity;
27 import android.app.DialogFragment;
28 import android.app.DownloadManager;
29 import android.app.SearchManager;
30 import android.content.ActivityNotFoundException;
31 import android.content.BroadcastReceiver;
32 import android.content.ClipData;
33 import android.content.ClipboardManager;
34 import android.content.Context;
35 import android.content.Intent;
36 import android.content.IntentFilter;
37 import android.content.SharedPreferences;
38 import android.content.pm.PackageManager;
39 import android.content.res.Configuration;
40 import android.content.res.Resources;
41 import android.database.Cursor;
42 import android.graphics.Bitmap;
43 import android.graphics.BitmapFactory;
44 import android.graphics.Typeface;
45 import android.graphics.drawable.BitmapDrawable;
46 import android.graphics.drawable.Drawable;
47 import android.net.Uri;
48 import android.net.http.SslCertificate;
49 import android.net.http.SslError;
50 import android.os.Build;
51 import android.os.Bundle;
52 import android.os.Environment;
53 import android.os.Handler;
54 import android.preference.PreferenceManager;
55 import android.print.PrintDocumentAdapter;
56 import android.print.PrintManager;
57 import android.support.annotation.NonNull;
58 import android.support.design.widget.CoordinatorLayout;
59 import android.support.design.widget.FloatingActionButton;
60 import android.support.design.widget.NavigationView;
61 import android.support.design.widget.Snackbar;
62 import android.support.v4.app.ActivityCompat;
63 import android.support.v4.content.ContextCompat;
64 // `ShortcutInfoCompat`, `ShortcutManagerCompat`, and `IconCompat` can be switched to the non-compat version once API >= 26.
65 import android.support.v4.content.pm.ShortcutInfoCompat;
66 import android.support.v4.content.pm.ShortcutManagerCompat;
67 import android.support.v4.graphics.drawable.IconCompat;
68 import android.support.v4.view.GravityCompat;
69 import android.support.v4.widget.DrawerLayout;
70 import android.support.v4.widget.SwipeRefreshLayout;
71 import android.support.v7.app.ActionBar;
72 import android.support.v7.app.ActionBarDrawerToggle;
73 import android.support.v7.app.AppCompatActivity;
74 import android.support.v7.app.AppCompatDialogFragment;
75 import android.support.v7.widget.Toolbar;
76 import android.text.Editable;
77 import android.text.Spanned;
78 import android.text.TextWatcher;
79 import android.text.style.ForegroundColorSpan;
80 import android.util.Patterns;
81 import android.view.ContextMenu;
82 import android.view.GestureDetector;
83 import android.view.KeyEvent;
84 import android.view.Menu;
85 import android.view.MenuItem;
86 import android.view.MotionEvent;
87 import android.view.View;
88 import android.view.ViewGroup;
89 import android.view.WindowManager;
90 import android.view.inputmethod.InputMethodManager;
91 import android.webkit.CookieManager;
92 import android.webkit.HttpAuthHandler;
93 import android.webkit.SslErrorHandler;
94 import android.webkit.ValueCallback;
95 import android.webkit.WebBackForwardList;
96 import android.webkit.WebChromeClient;
97 import android.webkit.WebResourceResponse;
98 import android.webkit.WebSettings;
99 import android.webkit.WebStorage;
100 import android.webkit.WebView;
101 import android.webkit.WebViewClient;
102 import android.webkit.WebViewDatabase;
103 import android.widget.ArrayAdapter;
104 import android.widget.CursorAdapter;
105 import android.widget.EditText;
106 import android.widget.FrameLayout;
107 import android.widget.ImageView;
108 import android.widget.LinearLayout;
109 import android.widget.ListView;
110 import android.widget.ProgressBar;
111 import android.widget.RadioButton;
112 import android.widget.RelativeLayout;
113 import android.widget.TextView;
115 import com.stoutner.privacybrowser.BuildConfig;
116 import com.stoutner.privacybrowser.R;
117 import com.stoutner.privacybrowser.dialogs.AdConsentDialog;
118 import com.stoutner.privacybrowser.dialogs.CreateBookmarkDialog;
119 import com.stoutner.privacybrowser.dialogs.CreateBookmarkFolderDialog;
120 import com.stoutner.privacybrowser.dialogs.CreateHomeScreenShortcutDialog;
121 import com.stoutner.privacybrowser.dialogs.DownloadImageDialog;
122 import com.stoutner.privacybrowser.dialogs.DownloadLocationPermissionDialog;
123 import com.stoutner.privacybrowser.dialogs.EditBookmarkDialog;
124 import com.stoutner.privacybrowser.dialogs.EditBookmarkFolderDialog;
125 import com.stoutner.privacybrowser.dialogs.HttpAuthenticationDialog;
126 import com.stoutner.privacybrowser.dialogs.PinnedSslCertificateMismatchDialog;
127 import com.stoutner.privacybrowser.dialogs.UrlHistoryDialog;
128 import com.stoutner.privacybrowser.dialogs.ViewSslCertificateDialog;
129 import com.stoutner.privacybrowser.helpers.AdHelper;
130 import com.stoutner.privacybrowser.helpers.BlockListHelper;
131 import com.stoutner.privacybrowser.helpers.BookmarksDatabaseHelper;
132 import com.stoutner.privacybrowser.helpers.DomainsDatabaseHelper;
133 import com.stoutner.privacybrowser.helpers.OrbotProxyHelper;
134 import com.stoutner.privacybrowser.dialogs.DownloadFileDialog;
135 import com.stoutner.privacybrowser.dialogs.SslCertificateErrorDialog;
137 import java.io.ByteArrayInputStream;
138 import java.io.ByteArrayOutputStream;
140 import java.io.IOException;
141 import java.io.UnsupportedEncodingException;
142 import java.net.MalformedURLException;
144 import java.net.URLDecoder;
145 import java.net.URLEncoder;
146 import java.util.ArrayList;
147 import java.util.Date;
148 import java.util.HashMap;
149 import java.util.HashSet;
150 import java.util.List;
151 import java.util.Map;
152 import java.util.Set;
154 // AppCompatActivity from android.support.v7.app.AppCompatActivity must be used to have access to the SupportActionBar until the minimum API is >= 21.
155 public class MainWebViewActivity extends AppCompatActivity implements CreateBookmarkDialog.CreateBookmarkListener, CreateBookmarkFolderDialog.CreateBookmarkFolderListener,
156 CreateHomeScreenShortcutDialog.CreateHomeScreenShortcutListener, DownloadFileDialog.DownloadFileListener, DownloadImageDialog.DownloadImageListener,
157 DownloadLocationPermissionDialog.DownloadLocationPermissionDialogListener, EditBookmarkDialog.EditBookmarkListener, EditBookmarkFolderDialog.EditBookmarkFolderListener,
158 HttpAuthenticationDialog.HttpAuthenticationListener, NavigationView.OnNavigationItemSelectedListener, PinnedSslCertificateMismatchDialog.PinnedSslCertificateMismatchListener,
159 SslCertificateErrorDialog.SslCertificateErrorListener, UrlHistoryDialog.UrlHistoryListener {
161 // `darkTheme` is public static so it can be accessed from everywhere.
162 public static boolean darkTheme;
164 // `allowScreenshots` is public static so it can be accessed from everywhere. It is also used in `onCreate()`.
165 public static boolean allowScreenshots;
167 // `favoriteIconBitmap` is public static so it can be accessed from `CreateHomeScreenShortcutDialog`, `BookmarksActivity`, `BookmarksDatabaseViewActivity`, `CreateBookmarkDialog`,
168 // `CreateBookmarkFolderDialog`, `EditBookmarkDialog`, `EditBookmarkFolderDialog`, `EditBookmarkDatabaseViewDialog`, and `ViewSslCertificateDialog`. It is also used in `onCreate()`,
169 // `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onCreateHomeScreenShortcutCreate()`, `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and `applyDomainSettings()`.
170 public static Bitmap favoriteIconBitmap;
172 // `formattedUrlString` is public static so it can be accessed from `BookmarksActivity`, `CreateBookmarkDialog`, and `AddDomainDialog`.
173 // It is also used in `onCreate()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onCreateHomeScreenShortcutCreate()`, `loadUrlFromTextBox()`, and `applyProxyThroughOrbot()`.
174 public static String formattedUrlString;
176 // `sslCertificate` is public static so it can be accessed from `DomainsActivity`, `DomainsListFragment`, `DomainSettingsFragment`, `PinnedSslCertificateMismatchDialog`,
177 // and `ViewSslCertificateDialog`. It is also used in `onCreate()`.
178 public static SslCertificate sslCertificate;
180 // `orbotStatus` is public static so it can be accessed from `OrbotProxyHelper`. It is also used in `onCreate()`, `onResume()`, and `applyProxyThroughOrbot()`.
181 public static String orbotStatus;
183 // `webViewTitle` is public static so it can be accessed from `CreateBookmarkDialog` and `CreateHomeScreenShortcutDialog`. It is also used in `onCreate()`.
184 public static String webViewTitle;
186 // `appliedUserAgentString` is public static so it can be accessed from `ViewSourceActivity`. It is also used in `applyDomainSettings()`.
187 public static String appliedUserAgentString;
189 // `reloadOnRestart` is public static so it can be accessed from `SettingsFragment`. It is also used in `onRestart()`
190 public static boolean reloadOnRestart;
192 // `reloadUrlOnRestart` is public static so it can be accessed from `SettingsFragment` and `BookmarksActivity`. It is also used in `onRestart()`.
193 public static boolean loadUrlOnRestart;
195 // `restartFromBookmarksActivity` is public static so it can be accessed from `BookmarksActivity`. It is also used in `onRestart()`.
196 public static boolean restartFromBookmarksActivity;
198 // The block list versions are public static so they can be accessed from `AboutTabFragment`. They are also used in `onCreate()`.
199 public static String easyListVersion;
200 public static String easyPrivacyVersion;
201 public static String fanboysAnnoyanceVersion;
202 public static String fanboysSocialVersion;
203 public static String ultraPrivacyVersion;
205 // The request items are public static so they can be accessed by `BlockListHelper`, `RequestsArrayAdapter`, and `ViewRequestsDialog`. They are also used in `onCreate()` and `onPrepareOptionsMenu()`.
206 public static List<String[]> resourceRequests;
207 public static String[] whiteListResultStringArray;
208 private int blockedRequests;
209 private int easyListBlockedRequests;
210 private int easyPrivacyBlockedRequests;
211 private int fanboysAnnoyanceListBlockedRequests;
212 private int fanboysSocialBlockingListBlockedRequests;
213 private int ultraPrivacyBlockedRequests;
214 private int thirdPartyBlockedRequests;
216 public final static int REQUEST_DISPOSITION = 0;
217 public final static int REQUEST_URL = 1;
218 public final static int REQUEST_BLOCKLIST = 2;
219 public final static int REQUEST_SUBLIST = 3;
220 public final static int REQUEST_BLOCKLIST_ENTRIES = 4;
221 public final static int REQUEST_BLOCKLIST_ORIGINAL_ENTRY = 5;
223 public final static int REQUEST_DEFAULT = 0;
224 public final static int REQUEST_ALLOWED = 1;
225 public final static int REQUEST_THIRD_PARTY = 2;
226 public final static int REQUEST_BLOCKED = 3;
228 public final static int MAIN_WHITELIST = 1;
229 public final static int FINAL_WHITELIST = 2;
230 public final static int DOMAIN_WHITELIST = 3;
231 public final static int DOMAIN_INITIAL_WHITELIST = 4;
232 public final static int DOMAIN_FINAL_WHITELIST = 5;
233 public final static int THIRD_PARTY_WHITELIST = 6;
234 public final static int THIRD_PARTY_DOMAIN_WHITELIST = 7;
235 public final static int THIRD_PARTY_DOMAIN_INITIAL_WHITELIST = 8;
237 public final static int MAIN_BLACKLIST = 9;
238 public final static int INITIAL_BLACKLIST = 10;
239 public final static int FINAL_BLACKLIST = 11;
240 public final static int DOMAIN_BLACKLIST = 12;
241 public final static int DOMAIN_INITIAL_BLACKLIST = 13;
242 public final static int DOMAIN_FINAL_BLACKLIST = 14;
243 public final static int DOMAIN_REGULAR_EXPRESSION_BLACKLIST = 15;
244 public final static int THIRD_PARTY_BLACKLIST = 16;
245 public final static int THIRD_PARTY_INITIAL_BLACKLIST = 17;
246 public final static int THIRD_PARTY_DOMAIN_BLACKLIST = 18;
247 public final static int THIRD_PARTY_DOMAIN_INITIAL_BLACKLIST = 19;
248 public final static int THIRD_PARTY_REGULAR_EXPRESSION_BLACKLIST = 20;
249 public final static int THIRD_PARTY_DOMAIN_REGULAR_EXPRESSION_BLACKLIST = 21;
250 public final static int REGULAR_EXPRESSION_BLACKLIST = 22;
252 // `blockAllThirdPartyRequests` is public static so it can be accessed from `RequestsActivity`.
253 // It is also used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, and `applyAppSettings()`
254 public static boolean blockAllThirdPartyRequests;
256 // `currentBookmarksFolder` is public static so it can be accessed from `BookmarksActivity`. It is also used in `onCreate()`, `onBackPressed()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`,
257 // `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and `loadBookmarksFolder()`.
258 public static String currentBookmarksFolder;
260 // `domainSettingsDatabaseId` is public static so it can be accessed from `PinnedSslCertificateMismatchDialog`. It is also used in `onCreate()`, `onOptionsItemSelected()`, and `applyDomainSettings()`.
261 public static int domainSettingsDatabaseId;
263 // The pinned domain SSL Certificate variables are public static so they can be accessed from `PinnedSslCertificateMismatchDialog`. They are also used in `onCreate()` and `applyDomainSettings()`.
264 public static String pinnedDomainSslIssuedToCNameString;
265 public static String pinnedDomainSslIssuedToONameString;
266 public static String pinnedDomainSslIssuedToUNameString;
267 public static String pinnedDomainSslIssuedByCNameString;
268 public static String pinnedDomainSslIssuedByONameString;
269 public static String pinnedDomainSslIssuedByUNameString;
270 public static Date pinnedDomainSslStartDate;
271 public static Date pinnedDomainSslEndDate;
273 // The user agent constants are public static so they can be accessed from `SettingsFragment`, `DomainsActivity`, and `DomainSettingsFragment`.
274 public final static int UNRECOGNIZED_USER_AGENT = -1;
275 public final static int SETTINGS_WEBVIEW_DEFAULT_USER_AGENT = 1;
276 public final static int SETTINGS_CUSTOM_USER_AGENT = 12;
277 public final static int DOMAINS_SYSTEM_DEFAULT_USER_AGENT = 0;
278 public final static int DOMAINS_WEBVIEW_DEFAULT_USER_AGENT = 2;
279 public final static int DOMAINS_CUSTOM_USER_AGENT = 13;
282 // `appBar` is used in `onCreate()`, `onOptionsItemSelected()`, `closeFindOnPage()`, `applyAppSettings()`, and `applyProxyThroughOrbot()`.
283 private ActionBar appBar;
285 // `navigatingHistory` is used in `onCreate()`, `onNavigationItemSelected()`, `onSslMismatchBack()`, and `applyDomainSettings()`.
286 private boolean navigatingHistory;
288 // `favoriteIconDefaultBitmap` is used in `onCreate()` and `applyDomainSettings`.
289 private Bitmap favoriteIconDefaultBitmap;
291 // `drawerLayout` is used in `onCreate()`, `onNewIntent()`, `onBackPressed()`, and `onRestart()`.
292 private DrawerLayout drawerLayout;
294 // `rootCoordinatorLayout` is used in `onCreate()` and `applyAppSettings()`.
295 private CoordinatorLayout rootCoordinatorLayout;
297 // `mainWebView` is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onRestart()`, `onCreateContextMenu()`, `findPreviousOnPage()`,
298 // `findNextOnPage()`, `closeFindOnPage()`, `loadUrlFromTextBox()`, `onSslMismatchBack()`, and `applyProxyThroughOrbot()`.
299 private WebView mainWebView;
301 // `fullScreenVideoFrameLayout` is used in `onCreate()` and `onConfigurationChanged()`.
302 private FrameLayout fullScreenVideoFrameLayout;
304 // `swipeRefreshLayout` is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, and `onRestart()`.
305 private SwipeRefreshLayout swipeRefreshLayout;
307 // `urlAppBarRelativeLayout` is used in `onCreate()` and `applyDomainSettings()`.
308 private RelativeLayout urlAppBarRelativeLayout;
310 // `favoriteIconImageView` is used in `onCreate()` and `applyDomainSettings()`
311 private ImageView favoriteIconImageView;
313 // `cookieManager` is used in `onCreate()`, `onOptionsItemSelected()`, and `onNavigationItemSelected()`, `loadUrlFromTextBox()`, `onDownloadImage()`, `onDownloadFile()`, and `onRestart()`.
314 private CookieManager cookieManager;
316 // `customHeader` is used in `onCreate()`, `onOptionsItemSelected()`, `onCreateContextMenu()`, and `loadUrl()`.
317 private final Map<String, String> customHeaders = new HashMap<>();
319 // `javaScriptEnabled` is also used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, `applyDomainSettings()`, and `updatePrivacyIcons()`.
320 private boolean javaScriptEnabled;
322 // `firstPartyCookiesEnabled` is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, `onDownloadImage()`, `onDownloadFile()`, and `applyDomainSettings()`.
323 private boolean firstPartyCookiesEnabled;
325 // `thirdPartyCookiesEnabled` used in `onCreate()`, `onPrepareOptionsMenu()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, and `applyDomainSettings()`.
326 private boolean thirdPartyCookiesEnabled;
328 // `domStorageEnabled` is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, and `applyDomainSettings()`.
329 private boolean domStorageEnabled;
331 // `saveFormDataEnabled` is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, and `applyDomainSettings()`. It can be removed once the minimum API >= 26.
332 private boolean saveFormDataEnabled;
334 // `nightMode` is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, and `applyDomainSettings()`.
335 private boolean nightMode;
337 // 'homepage' is used in `onCreate()`, `onNavigationItemSelected()`, and `applyProxyThroughOrbot()`.
338 private String homepage;
340 // `searchURL` is used in `loadURLFromTextBox()` and `applyProxyThroughOrbot()`.
341 private String searchURL;
343 // `mainMenu` is used in `onCreateOptionsMenu()` and `updatePrivacyIcons()`.
344 private Menu mainMenu;
346 // `refreshMenuItem` is used in `onCreate()` and `onCreateOptionsMenu()`.
347 private MenuItem refreshMenuItem;
349 // The blocklist menu items are used in `onCreate()`, `onCreateOptionsMenu()`, and `onPrepareOptionsMenu()`.
350 private MenuItem blocklistsMenuItem;
351 private MenuItem easyListMenuItem;
352 private MenuItem easyPrivacyMenuItem;
353 private MenuItem fanboysAnnoyanceListMenuItem;
354 private MenuItem fanboysSocialBlockingListMenuItem;
355 private MenuItem ultraPrivacyMenuItem;
356 private MenuItem blockAllThirdPartyRequestsMenuItem;
358 // The blocklist variables are used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, and `applyAppSettings()`.
359 private boolean easyListEnabled;
360 private boolean easyPrivacyEnabled;
361 private boolean fanboysAnnoyanceListEnabled;
362 private boolean fanboysSocialBlockingListEnabled;
363 private boolean ultraPrivacyEnabled;
365 // `webViewDefaultUserAgent` is used in `onCreate()` and `onPrepareOptionsMenu()`.
366 private String webViewDefaultUserAgent;
368 // `defaultCustomUserAgentString` is used in `onPrepareOptionsMenu()` and `applyDomainSettings()`.
369 private String defaultCustomUserAgentString;
371 // `privacyBrowserRuntime` is used in `onCreate()`, `onOptionsItemSelected()`, and `applyAppSettings()`.
372 private Runtime privacyBrowserRuntime;
374 // `proxyThroughOrbot` is used in `onRestart()`, `onOptionsItemSelected()`, `applyAppSettings()`, and `applyProxyThroughOrbot()`.
375 private boolean proxyThroughOrbot;
377 // `incognitoModeEnabled` is used in `onCreate()` and `applyAppSettings()`.
378 private boolean incognitoModeEnabled;
380 // `fullScreenBrowsingModeEnabled` is used in `onCreate()` and `applyAppSettings()`.
381 private boolean fullScreenBrowsingModeEnabled;
383 // `inFullScreenBrowsingMode` is used in `onCreate()`, `onConfigurationChanged()`, and `applyAppSettings()`.
384 private boolean inFullScreenBrowsingMode;
386 // `hideSystemBarsOnFullscreen` is used in `onCreate()` and `applyAppSettings()`.
387 private boolean hideSystemBarsOnFullscreen;
389 // `translucentNavigationBarOnFullscreen` is used in `onCreate()` and `applyAppSettings()`.
390 private boolean translucentNavigationBarOnFullscreen;
392 // `reapplyDomainSettingsOnRestart` is used in `onCreate()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onRestart()`, and `onAddDomain()`, .
393 private boolean reapplyDomainSettingsOnRestart;
395 // `reapplyAppSettingsOnRestart` is used in `onNavigationItemSelected()` and `onRestart()`.
396 private boolean reapplyAppSettingsOnRestart;
398 // `displayingFullScreenVideo` is used in `onCreate()` and `onResume()`.
399 private boolean displayingFullScreenVideo;
401 // `downloadWithExternalApp` is used in `onCreate()`, `onCreateContextMenu()`, and `applyDomainSettings()`.
402 private boolean downloadWithExternalApp;
404 // `currentDomainName` is used in `onCreate()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onAddDomain()`, and `applyDomainSettings()`.
405 private String currentDomainName;
407 // `ignorePinnedSslCertificateForDomain` is used in `onCreate()`, `onSslMismatchProceed()`, and `applyDomainSettings()`.
408 private boolean ignorePinnedSslCertificate;
410 // `orbotStatusBroadcastReceiver` is used in `onCreate()` and `onDestroy()`.
411 private BroadcastReceiver orbotStatusBroadcastReceiver;
413 // `waitingForOrbot` is used in `onCreate()`, `onResume()`, and `applyProxyThroughOrbot()`.
414 private boolean waitingForOrbot;
416 // `domainSettingsApplied` is used in `prepareOptionsMenu()` and `applyDomainSettings()`.
417 private boolean domainSettingsApplied;
419 // `domainSettingsJavaScriptEnabled` is used in `onOptionsItemSelected()` and `applyDomainSettings()`.
420 private Boolean domainSettingsJavaScriptEnabled;
422 // `waitingForOrbotHtmlString` is used in `onCreate()` and `applyProxyThroughOrbot()`.
423 private String waitingForOrbotHtmlString;
425 // `privateDataDirectoryString` is used in `onCreate()`, `onOptionsItemSelected()`, and `onNavigationItemSelected()`.
426 private String privateDataDirectoryString;
428 // `findOnPageLinearLayout` is used in `onCreate()`, `onOptionsItemSelected()`, and `closeFindOnPage()`.
429 private LinearLayout findOnPageLinearLayout;
431 // `findOnPageEditText` is used in `onCreate()`, `onOptionsItemSelected()`, and `closeFindOnPage()`.
432 private EditText findOnPageEditText;
434 // `displayAdditionalAppBarIcons` is used in `onCreate()` and `onCreateOptionsMenu()`.
435 private boolean displayAdditionalAppBarIcons;
437 // `drawerToggle` is used in `onCreate()`, `onPostCreate()`, `onConfigurationChanged()`, `onNewIntent()`, and `onNavigationItemSelected()`.
438 private ActionBarDrawerToggle drawerToggle;
440 // `supportAppBar` is used in `onCreate()`, `onOptionsItemSelected()`, and `closeFindOnPage()`.
441 private Toolbar supportAppBar;
443 // `urlTextBox` is used in `onCreate()`, `onOptionsItemSelected()`, `loadUrlFromTextBox()`, `loadUrl()`, and `highlightUrlText()`.
444 private EditText urlTextBox;
446 // The color spans are used in `onCreate()` and `highlightUrlText()`.
447 private ForegroundColorSpan redColorSpan;
448 private ForegroundColorSpan initialGrayColorSpan;
449 private ForegroundColorSpan finalGrayColorSpan;
451 // The drawer header padding variables are used in `onCreate()` and `onConfigurationChanged()`.
452 private int drawerHeaderPaddingLeftAndRight;
453 private int drawerHeaderPaddingTop;
454 private int drawerHeaderPaddingBottom;
456 // `sslErrorHandler` is used in `onCreate()`, `onSslErrorCancel()`, and `onSslErrorProceed`.
457 private SslErrorHandler sslErrorHandler;
459 // `httpAuthHandler` is used in `onCreate()`, `onHttpAuthenticationCancel()`, and `onHttpAuthenticationProceed()`.
460 private static HttpAuthHandler httpAuthHandler;
462 // `inputMethodManager` is used in `onOptionsItemSelected()`, `loadUrlFromTextBox()`, and `closeFindOnPage()`.
463 private InputMethodManager inputMethodManager;
465 // `mainWebViewRelativeLayout` is used in `onCreate()` and `onNavigationItemSelected()`.
466 private RelativeLayout mainWebViewRelativeLayout;
468 // `urlIsLoading` is used in `onCreate()`, `onCreateOptionsMenu()`, `loadUrl()`, and `applyDomainSettings()`.
469 private boolean urlIsLoading;
471 // `pinnedDomainSslCertificate` is used in `onCreate()` and `applyDomainSettings()`.
472 private boolean pinnedDomainSslCertificate;
474 // `bookmarksDatabaseHelper` is used in `onCreate()`, `onDestroy`, `onOptionsItemSelected()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`,
475 // and `loadBookmarksFolder()`.
476 private BookmarksDatabaseHelper bookmarksDatabaseHelper;
478 // `bookmarksListView` is used in `onCreate()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, and `loadBookmarksFolder()`.
479 private ListView bookmarksListView;
481 // `bookmarksTitleTextView` is used in `onCreate()` and `loadBookmarksFolder()`.
482 private TextView bookmarksTitleTextView;
484 // `bookmarksCursor` is used in `onDestroy()`, `onOptionsItemSelected()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and `loadBookmarksFolder()`.
485 private Cursor bookmarksCursor;
487 // `bookmarksCursorAdapter` is used in `onCreateBookmark()`, `onCreateBookmarkFolder()` `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and `loadBookmarksFolder()`.
488 private CursorAdapter bookmarksCursorAdapter;
490 // `oldFolderNameString` is used in `onCreate()` and `onSaveEditBookmarkFolder()`.
491 private String oldFolderNameString;
493 // `fileChooserCallback` is used in `onCreate()` and `onActivityResult()`.
494 private ValueCallback<Uri[]> fileChooserCallback;
496 // The download strings are used in `onCreate()` and `onRequestPermissionResult()`.
497 private String downloadUrl;
498 private String downloadContentDisposition;
499 private long downloadContentLength;
501 // `downloadImageUrl` is used in `onCreateContextMenu()` and `onRequestPermissionResult()`.
502 private String downloadImageUrl;
504 // The user agent variables are used in `onCreate()` and `applyDomainSettings()`.
505 private ArrayAdapter<CharSequence> userAgentNamesArray;
506 private String[] userAgentDataArray;
508 // The request codes are used in `onCreate()`, `onCreateContextMenu()`, `onCloseDownloadLocationPermissionDialog()`, and `onRequestPermissionResult()`.
509 private final int DOWNLOAD_FILE_REQUEST_CODE = 1;
510 private final int DOWNLOAD_IMAGE_REQUEST_CODE = 2;
513 // Remove Android Studio's warning about the dangers of using SetJavaScriptEnabled. The whole premise of Privacy Browser is built around an understanding of these dangers.
514 // Also, remove the warning about needing to override `performClick()` when using an `OnTouchListener` with `WebView`.
515 @SuppressLint({"SetJavaScriptEnabled", "ClickableViewAccessibility"})
516 // Remove Android Studio's warning about deprecations. The deprecated `getColor()` must be used until API >= 23.
517 @SuppressWarnings("deprecation")
518 protected void onCreate(Bundle savedInstanceState) {
519 // Get a handle for the shared preferences.
520 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
522 // Get the theme and screenshot preferences.
523 darkTheme = sharedPreferences.getBoolean("dark_theme", false);
524 allowScreenshots = sharedPreferences.getBoolean("allow_screenshots", false);
526 // Disable screenshots if not allowed.
527 if (!allowScreenshots) {
528 getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE);
531 // Set the activity theme.
533 setTheme(R.style.PrivacyBrowserDark);
535 setTheme(R.style.PrivacyBrowserLight);
538 // Run the default commands.
539 super.onCreate(savedInstanceState);
541 // Set the content view.
542 setContentView(R.layout.main_drawerlayout);
544 // Get a handle for the resources.
545 Resources resources = getResources();
547 // Get a handle for `inputMethodManager`.
548 inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
550 // `SupportActionBar` from `android.support.v7.app.ActionBar` must be used until the minimum API is >= 21.
551 supportAppBar = findViewById(R.id.app_bar);
552 setSupportActionBar(supportAppBar);
553 appBar = getSupportActionBar();
555 // This is needed to get rid of the Android Studio warning that `appBar` might be null.
556 assert appBar != null;
558 // Add the custom `url_app_bar` layout, which shows the favorite icon and the URL text bar.
559 appBar.setCustomView(R.layout.url_app_bar);
560 appBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM);
562 // Initialize the foreground color spans for highlighting the URLs. We have to use the deprecated `getColor()` until API >= 23.
563 redColorSpan = new ForegroundColorSpan(resources.getColor(R.color.red_a700));
564 initialGrayColorSpan = new ForegroundColorSpan(resources.getColor(R.color.gray_500));
565 finalGrayColorSpan = new ForegroundColorSpan(resources.getColor(R.color.gray_500));
567 // Get a handle for `urlTextBox`.
568 urlTextBox = findViewById(R.id.url_edittext);
570 // Remove the formatting from `urlTextBar` when the user is editing the text.
571 urlTextBox.setOnFocusChangeListener((View v, boolean hasFocus) -> {
572 if (hasFocus) { // The user is editing the URL text box.
573 // Remove the highlighting.
574 urlTextBox.getText().removeSpan(redColorSpan);
575 urlTextBox.getText().removeSpan(initialGrayColorSpan);
576 urlTextBox.getText().removeSpan(finalGrayColorSpan);
577 } else { // The user has stopped editing the URL text box.
578 // Move to the beginning of the string.
579 urlTextBox.setSelection(0);
581 // Reapply the highlighting.
586 // Set the go button on the keyboard to load the URL in `urlTextBox`.
587 urlTextBox.setOnKeyListener((View v, int keyCode, KeyEvent event) -> {
588 // If the event is a key-down event on the `enter` button, load the URL.
589 if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) {
590 // Load the URL into the mainWebView and consume the event.
591 loadUrlFromTextBox();
593 // If the enter key was pressed, consume the event.
596 // If any other key was pressed, do not consume the event.
601 // Set `waitingForOrbotHTMLString`.
602 waitingForOrbotHtmlString = "<html><body><br/><center><h1>" + getString(R.string.waiting_for_orbot) + "</h1></center></body></html>";
604 // Initialize `currentDomainName`, `orbotStatus`, and `waitingForOrbot`.
605 currentDomainName = "";
606 orbotStatus = "unknown";
607 waitingForOrbot = false;
609 // Create an Orbot status `BroadcastReceiver`.
610 orbotStatusBroadcastReceiver = new BroadcastReceiver() {
612 public void onReceive(Context context, Intent intent) {
613 // Store the content of the status message in `orbotStatus`.
614 orbotStatus = intent.getStringExtra("org.torproject.android.intent.extra.STATUS");
616 // If Privacy Browser is waiting on Orbot, load the website now that Orbot is connected.
617 if (orbotStatus.equals("ON") && waitingForOrbot) {
618 // Reset `waitingForOrbot`.
619 waitingForOrbot = false;
621 // Load `formattedUrlString
622 loadUrl(formattedUrlString);
627 // Register `orbotStatusBroadcastReceiver` on `this` context.
628 this.registerReceiver(orbotStatusBroadcastReceiver, new IntentFilter("org.torproject.android.intent.action.STATUS"));
630 // Get handles for views that need to be accessed.
631 drawerLayout = findViewById(R.id.drawerlayout);
632 rootCoordinatorLayout = findViewById(R.id.root_coordinatorlayout);
633 bookmarksListView = findViewById(R.id.bookmarks_drawer_listview);
634 bookmarksTitleTextView = findViewById(R.id.bookmarks_title_textview);
635 FloatingActionButton launchBookmarksActivityFab = findViewById(R.id.launch_bookmarks_activity_fab);
636 FloatingActionButton createBookmarkFolderFab = findViewById(R.id.create_bookmark_folder_fab);
637 FloatingActionButton createBookmarkFab = findViewById(R.id.create_bookmark_fab);
638 mainWebViewRelativeLayout = findViewById(R.id.main_webview_relativelayout);
639 mainWebView = findViewById(R.id.main_webview);
640 findOnPageLinearLayout = findViewById(R.id.find_on_page_linearlayout);
641 findOnPageEditText = findViewById(R.id.find_on_page_edittext);
642 fullScreenVideoFrameLayout = findViewById(R.id.full_screen_video_framelayout);
643 urlAppBarRelativeLayout = findViewById(R.id.url_app_bar_relativelayout);
644 favoriteIconImageView = findViewById(R.id.favorite_icon);
646 // Set the bookmarks drawer resources according to the theme. This can't be done in the layout due to compatibility issues with the `DrawerLayout` support widget.
648 launchBookmarksActivityFab.setImageDrawable(resources.getDrawable(R.drawable.bookmarks_dark));
649 createBookmarkFolderFab.setImageDrawable(resources.getDrawable(R.drawable.create_folder_dark));
650 createBookmarkFab.setImageDrawable(resources.getDrawable(R.drawable.create_bookmark_dark));
651 bookmarksListView.setBackgroundColor(resources.getColor(R.color.gray_850));
653 launchBookmarksActivityFab.setImageDrawable(resources.getDrawable(R.drawable.bookmarks_light));
654 createBookmarkFolderFab.setImageDrawable(resources.getDrawable(R.drawable.create_folder_light));
655 createBookmarkFab.setImageDrawable(resources.getDrawable(R.drawable.create_bookmark_light));
656 bookmarksListView.setBackgroundColor(resources.getColor(R.color.white));
659 // Set the launch bookmarks activity FAB to launch the bookmarks activity.
660 launchBookmarksActivityFab.setOnClickListener(v -> {
661 // Create an intent to launch the bookmarks activity.
662 Intent bookmarksIntent = new Intent(getApplicationContext(), BookmarksActivity.class);
664 // Include the current folder with the `Intent`.
665 bookmarksIntent.putExtra("Current Folder", currentBookmarksFolder);
668 startActivity(bookmarksIntent);
671 // Set the create new bookmark folder FAB to display an alert dialog.
672 createBookmarkFolderFab.setOnClickListener(v -> {
673 // Show the `CreateBookmarkFolderDialog` `AlertDialog` and name the instance `@string/create_folder`.
674 AppCompatDialogFragment createBookmarkFolderDialog = new CreateBookmarkFolderDialog();
675 createBookmarkFolderDialog.show(getSupportFragmentManager(), resources.getString(R.string.create_folder));
678 // Set the create new bookmark FAB to display an alert dialog.
679 createBookmarkFab.setOnClickListener(view -> {
680 // Show the `CreateBookmarkDialog` `AlertDialog` and name the instance `@string/create_bookmark`.
681 AppCompatDialogFragment createBookmarkDialog = new CreateBookmarkDialog();
682 createBookmarkDialog.show(getSupportFragmentManager(), resources.getString(R.string.create_bookmark));
685 // Create a double-tap listener to toggle full-screen mode.
686 final GestureDetector gestureDetector = new GestureDetector(this, new GestureDetector.SimpleOnGestureListener() {
687 // Override `onDoubleTap()`. All other events are handled using the default settings.
689 public boolean onDoubleTap(MotionEvent event) {
690 if (fullScreenBrowsingModeEnabled) { // Only process the double-tap if full screen browsing mode is enabled.
691 // Toggle `inFullScreenBrowsingMode`.
692 inFullScreenBrowsingMode = !inFullScreenBrowsingMode;
694 if (inFullScreenBrowsingMode) { // Switch to full screen mode.
695 // Hide the `appBar`.
698 // Hide the banner ad in the free flavor.
699 if (BuildConfig.FLAVOR.contentEquals("free")) {
700 // The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations.
701 AdHelper.hideAd(findViewById(R.id.adview));
704 // Modify the system bars.
705 if (hideSystemBarsOnFullscreen) { // Hide everything.
706 // Remove the translucent overlays.
707 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
709 // Remove the translucent status bar overlay on the `Drawer Layout`, which is special and needs its own command.
710 drawerLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
712 /* SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
713 * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
714 * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
716 rootCoordinatorLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
718 // Set `rootCoordinatorLayout` to fill the whole screen.
719 rootCoordinatorLayout.setFitsSystemWindows(false);
720 } else { // Hide everything except the status and navigation bars.
721 // Set `rootCoordinatorLayout` to fit under the status and navigation bars.
722 rootCoordinatorLayout.setFitsSystemWindows(false);
724 // There is an Android Support Library bug that causes a scrim to print on the right side of the `Drawer Layout` when the navigation bar is displayed on the right of the screen.
725 if (translucentNavigationBarOnFullscreen) {
726 // Set the navigation bar to be translucent.
727 getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
730 } else { // Switch to normal viewing mode.
731 // Show the `appBar`.
734 // Show the `BannerAd` in the free flavor.
735 if (BuildConfig.FLAVOR.contentEquals("free")) {
736 // Reload the ad. The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations.
737 AdHelper.loadAd(findViewById(R.id.adview), getApplicationContext(), getString(R.string.ad_unit_id));
740 // Remove the translucent navigation bar flag if it is set.
741 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
743 // Add the translucent status flag if it is unset. This also resets `drawerLayout's` `View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN`.
744 getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
746 // Remove any `SYSTEM_UI` flags from `rootCoordinatorLayout`.
747 rootCoordinatorLayout.setSystemUiVisibility(0);
749 // Constrain `rootCoordinatorLayout` inside the status and navigation bars.
750 rootCoordinatorLayout.setFitsSystemWindows(true);
753 // Consume the double-tap.
755 } else { // Do not consume the double-tap because full screen browsing mode is disabled.
761 // Pass all touch events on `mainWebView` through `gestureDetector` to check for double-taps.
762 mainWebView.setOnTouchListener((View v, MotionEvent event) -> {
763 // Call `performClick()` on the view, which is required for accessibility.
766 // Send the `event` to `gestureDetector`.
767 return gestureDetector.onTouchEvent(event);
770 // Update `findOnPageCountTextView`.
771 mainWebView.setFindListener(new WebView.FindListener() {
772 // Get a handle for `findOnPageCountTextView`.
773 final TextView findOnPageCountTextView = findViewById(R.id.find_on_page_count_textview);
776 public void onFindResultReceived(int activeMatchOrdinal, int numberOfMatches, boolean isDoneCounting) {
777 if ((isDoneCounting) && (numberOfMatches == 0)) { // There are no matches.
778 // Set `findOnPageCountTextView` to `0/0`.
779 findOnPageCountTextView.setText(R.string.zero_of_zero);
780 } else if (isDoneCounting) { // There are matches.
781 // `activeMatchOrdinal` is zero-based.
782 int activeMatch = activeMatchOrdinal + 1;
784 // Build the match string.
785 String matchString = activeMatch + "/" + numberOfMatches;
787 // Set `findOnPageCountTextView`.
788 findOnPageCountTextView.setText(matchString);
793 // Search for the string on the page whenever a character changes in the `findOnPageEditText`.
794 findOnPageEditText.addTextChangedListener(new TextWatcher() {
796 public void beforeTextChanged(CharSequence s, int start, int count, int after) {
801 public void onTextChanged(CharSequence s, int start, int before, int count) {
806 public void afterTextChanged(Editable s) {
807 // Search for the text in `mainWebView`.
808 mainWebView.findAllAsync(findOnPageEditText.getText().toString());
812 // Set the `check mark` button for the `findOnPageEditText` keyboard to close the soft keyboard.
813 findOnPageEditText.setOnKeyListener((v, keyCode, event) -> {
814 if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) { // The `enter` key was pressed.
815 // Hide the soft keyboard. `0` indicates no additional flags.
816 inputMethodManager.hideSoftInputFromWindow(mainWebView.getWindowToken(), 0);
818 // Consume the event.
820 } else { // A different key was pressed.
821 // Do not consume the event.
826 // Implement swipe to refresh.
827 swipeRefreshLayout = findViewById(R.id.swiperefreshlayout);
828 swipeRefreshLayout.setOnRefreshListener(() -> mainWebView.reload());
830 // Set the swipe to refresh color according to the theme.
832 swipeRefreshLayout.setColorSchemeResources(R.color.blue_600);
833 swipeRefreshLayout.setProgressBackgroundColorSchemeResource(R.color.gray_850);
835 swipeRefreshLayout.setColorSchemeResources(R.color.blue_700);
838 // `DrawerTitle` identifies the `DrawerLayouts` in accessibility mode.
839 drawerLayout.setDrawerTitle(GravityCompat.START, getString(R.string.navigation_drawer));
840 drawerLayout.setDrawerTitle(GravityCompat.END, getString(R.string.bookmarks));
842 // Listen for touches on the navigation menu.
843 final NavigationView navigationView = findViewById(R.id.navigationview);
844 navigationView.setNavigationItemSelectedListener(this);
846 // Get handles for `navigationMenu` and the back and forward menu items. The menu is zero-based, so items 1, 2, and 3 are the second, third, and fourth entries in the menu.
847 final Menu navigationMenu = navigationView.getMenu();
848 final MenuItem navigationBackMenuItem = navigationMenu.getItem(1);
849 final MenuItem navigationForwardMenuItem = navigationMenu.getItem(2);
850 final MenuItem navigationHistoryMenuItem = navigationMenu.getItem(3);
851 final MenuItem navigationRequestsMenuItem = navigationMenu.getItem(4);
853 // Initialize the bookmarks database helper. The `0` specifies a database version, but that is ignored and set instead using a constant in `BookmarksDatabaseHelper`.
854 bookmarksDatabaseHelper = new BookmarksDatabaseHelper(this, null, null, 0);
856 // Initialize `currentBookmarksFolder`. `""` is the home folder in the database.
857 currentBookmarksFolder = "";
859 // Load the home folder, which is `""` in the database.
860 loadBookmarksFolder();
862 bookmarksListView.setOnItemClickListener((parent, view, position, id) -> {
863 // Convert the id from long to int to match the format of the bookmarks database.
864 int databaseID = (int) id;
866 // Get the bookmark cursor for this ID and move it to the first row.
867 Cursor bookmarkCursor = bookmarksDatabaseHelper.getBookmark(databaseID);
868 bookmarkCursor.moveToFirst();
870 // Act upon the bookmark according to the type.
871 if (bookmarkCursor.getInt(bookmarkCursor.getColumnIndex(BookmarksDatabaseHelper.IS_FOLDER)) == 1) { // The selected bookmark is a folder.
872 // Store the new folder name in `currentBookmarksFolder`.
873 currentBookmarksFolder = bookmarkCursor.getString(bookmarkCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME));
875 // Load the new folder.
876 loadBookmarksFolder();
877 } else { // The selected bookmark is not a folder.
878 // Load the bookmark URL.
879 loadUrl(bookmarkCursor.getString(bookmarkCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_URL)));
881 // Close the bookmarks drawer.
882 drawerLayout.closeDrawer(GravityCompat.END);
885 // Close the `Cursor`.
886 bookmarkCursor.close();
889 bookmarksListView.setOnItemLongClickListener((parent, view, position, id) -> {
890 // Convert the database ID from `long` to `int`.
891 int databaseId = (int) id;
893 // Find out if the selected bookmark is a folder.
894 boolean isFolder = bookmarksDatabaseHelper.isFolder(databaseId);
897 // Save the current folder name, which is used in `onSaveEditBookmarkFolder()`.
898 oldFolderNameString = bookmarksCursor.getString(bookmarksCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME));
900 // Show the edit bookmark folder `AlertDialog` and name the instance `@string/edit_folder`.
901 AppCompatDialogFragment editFolderDialog = EditBookmarkFolderDialog.folderDatabaseId(databaseId);
902 editFolderDialog.show(getSupportFragmentManager(), resources.getString(R.string.edit_folder));
904 // Show the edit bookmark `AlertDialog` and name the instance `@string/edit_bookmark`.
905 AppCompatDialogFragment editBookmarkDialog = EditBookmarkDialog.bookmarkDatabaseId(databaseId);
906 editBookmarkDialog.show(getSupportFragmentManager(), resources.getString(R.string.edit_bookmark));
909 // Consume the event.
913 // Get the status bar pixel size.
914 int statusBarResourceId = resources.getIdentifier("status_bar_height", "dimen", "android");
915 int statusBarPixelSize = resources.getDimensionPixelSize(statusBarResourceId);
917 // Get the resource density.
918 float screenDensity = resources.getDisplayMetrics().density;
920 // Calculate the drawer header padding. This is used to move the text in the drawer headers below any cutouts.
921 drawerHeaderPaddingLeftAndRight = (int) (15 * screenDensity);
922 drawerHeaderPaddingTop = statusBarPixelSize + (int) (4 * screenDensity);
923 drawerHeaderPaddingBottom = (int) (8 * screenDensity);
925 // The drawer listener is used to update the navigation menu.`
926 drawerLayout.addDrawerListener(new DrawerLayout.DrawerListener() {
928 public void onDrawerSlide(@NonNull View drawerView, float slideOffset) {
932 public void onDrawerOpened(@NonNull View drawerView) {
936 public void onDrawerClosed(@NonNull View drawerView) {
940 public void onDrawerStateChanged(int newState) {
941 if ((newState == DrawerLayout.STATE_SETTLING) || (newState == DrawerLayout.STATE_DRAGGING)) { // A drawer is opening or closing.
942 // Get handles for the drawer headers.
943 TextView navigationHeaderTextView = findViewById(R.id.navigationText);
944 TextView bookmarksHeaderTextView = findViewById(R.id.bookmarks_title_textview);
946 // Apply the navigation header paddings if the view is not null (sometimes it is null if another activity has already started). This moves the text in the header below any cutouts.
947 if (navigationHeaderTextView != null) {
948 navigationHeaderTextView.setPadding(drawerHeaderPaddingLeftAndRight, drawerHeaderPaddingTop, drawerHeaderPaddingLeftAndRight, drawerHeaderPaddingBottom);
951 // Apply the bookmarks header paddings if the view is not null (sometimes it is null if another activity has already started). This moves the text in the header below any cutouts.
952 if (bookmarksHeaderTextView != null) {
953 bookmarksHeaderTextView.setPadding(drawerHeaderPaddingLeftAndRight, drawerHeaderPaddingTop, drawerHeaderPaddingLeftAndRight, drawerHeaderPaddingBottom);
956 // Update the back, forward, history, and requests menu items.
957 navigationBackMenuItem.setEnabled(mainWebView.canGoBack());
958 navigationForwardMenuItem.setEnabled(mainWebView.canGoForward());
959 navigationHistoryMenuItem.setEnabled((mainWebView.canGoBack() || mainWebView.canGoForward()));
960 navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
962 // Hide the keyboard (if displayed).
963 inputMethodManager.hideSoftInputFromWindow(mainWebView.getWindowToken(), 0);
965 // Clear the focus from from the URL text box and the WebView. This removes any text selection markers and context menus, which otherwise draw above the open drawers.
966 urlTextBox.clearFocus();
967 mainWebView.clearFocus();
972 // drawerToggle creates the hamburger icon at the start of the AppBar.
973 drawerToggle = new ActionBarDrawerToggle(this, drawerLayout, supportAppBar, R.string.open_navigation_drawer, R.string.close_navigation_drawer);
975 // Get a handle for the progress bar.
976 final ProgressBar progressBar = findViewById(R.id.progress_bar);
978 mainWebView.setWebChromeClient(new WebChromeClient() {
979 // Update the progress bar when a page is loading.
981 public void onProgressChanged(WebView view, int progress) {
982 // Inject the night mode CSS if night mode is enabled.
984 // `background-color: #212121` sets the background to be dark gray. `color: #BDBDBD` sets the text color to be light gray. `box-shadow: none` removes a lower underline on links
985 // used by WordPress. `text-decoration: none` removes all text underlines. `text-shadow: none` removes text shadows, which usually have a hard coded color.
986 // `border: none` removes all borders, which can also be used to underline text. `a {color: #1565C0}` sets links to be a dark blue.
987 // `::selection {background: #0D47A1}' sets the text selection highlight color to be a dark blue. `!important` takes precedent over any existing sub-settings.
988 mainWebView.evaluateJavascript("(function() {var parent = document.getElementsByTagName('head').item(0); var style = document.createElement('style'); style.type = 'text/css'; " +
989 "style.innerHTML = '* {background-color: #212121 !important; color: #BDBDBD !important; box-shadow: none !important; text-decoration: none !important;" +
990 "text-shadow: none !important; border: none !important;} a {color: #1565C0 !important;} ::selection {background: #0D47A1 !important;}'; parent.appendChild(style)})()", value -> {
991 // Initialize a handler to display `mainWebView`.
992 Handler displayWebViewHandler = new Handler();
994 // Setup a runnable to display `mainWebView` after a delay to allow the CSS to be applied.
995 Runnable displayWebViewRunnable = () -> {
996 // Only display `mainWebView` if the progress bar is gone. This prevents the display of the `WebView` while it is still loading.
997 if (progressBar.getVisibility() == View.GONE) {
998 mainWebView.setVisibility(View.VISIBLE);
1002 // Displaying of `mainWebView` after 500 milliseconds.
1003 displayWebViewHandler.postDelayed(displayWebViewRunnable, 500);
1007 // Update the progress bar.
1008 progressBar.setProgress(progress);
1010 // Set the visibility of the progress bar.
1011 if (progress < 100) {
1012 // Show the progress bar.
1013 progressBar.setVisibility(View.VISIBLE);
1015 // Hide the progress bar.
1016 progressBar.setVisibility(View.GONE);
1018 // Display `mainWebView` if night mode is disabled.
1019 // Because of a race condition between `applyDomainSettings` and `onPageStarted`, when night mode is set by domain settings the `WebView` may be hidden even if night mode is not
1020 // currently enabled.
1022 mainWebView.setVisibility(View.VISIBLE);
1025 //Stop the swipe to refresh indicator if it is running
1026 swipeRefreshLayout.setRefreshing(false);
1030 // Set the favorite icon when it changes.
1032 public void onReceivedIcon(WebView view, Bitmap icon) {
1033 // Only update the favorite icon if the website has finished loading.
1034 if (progressBar.getVisibility() == View.GONE) {
1035 // Save a copy of the favorite icon.
1036 favoriteIconBitmap = icon;
1038 // Place the favorite icon in the appBar.
1039 favoriteIconImageView.setImageBitmap(Bitmap.createScaledBitmap(icon, 64, 64, true));
1043 // Save a copy of the title when it changes.
1045 public void onReceivedTitle(WebView view, String title) {
1046 // Save a copy of the title.
1047 webViewTitle = title;
1050 // Enter full screen video.
1052 public void onShowCustomView(View view, CustomViewCallback callback) {
1053 // Set the full screen video flag.
1054 displayingFullScreenVideo = true;
1056 // Pause the ad if this is the free flavor.
1057 if (BuildConfig.FLAVOR.contentEquals("free")) {
1058 // The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations.
1059 AdHelper.pauseAd(findViewById(R.id.adview));
1062 // Remove the translucent overlays.
1063 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
1065 // Remove the translucent status bar overlay on the `Drawer Layout`, which is special and needs its own command.
1066 drawerLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
1068 /* SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
1069 * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
1070 * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
1072 rootCoordinatorLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
1074 // Set `rootCoordinatorLayout` to fill the entire screen.
1075 rootCoordinatorLayout.setFitsSystemWindows(false);
1077 // Disable the sliding drawers.
1078 drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED);
1080 // Add `view` to `fullScreenVideoFrameLayout` and display it on the screen.
1081 fullScreenVideoFrameLayout.addView(view);
1082 fullScreenVideoFrameLayout.setVisibility(View.VISIBLE);
1085 // Exit full screen video.
1087 public void onHideCustomView() {
1088 // Unset the full screen video flag.
1089 displayingFullScreenVideo = false;
1091 // Hide `fullScreenVideoFrameLayout`.
1092 fullScreenVideoFrameLayout.removeAllViews();
1093 fullScreenVideoFrameLayout.setVisibility(View.GONE);
1095 // Enable the sliding drawers.
1096 drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED);
1098 // Apply the appropriate full screen mode the `SYSTEM_UI` flags.
1099 if (fullScreenBrowsingModeEnabled && inFullScreenBrowsingMode) { // Privacy Browser is currently in full screen browsing mode.
1100 if (hideSystemBarsOnFullscreen) { // Hide everything.
1101 // Remove the translucent navigation setting if it is currently flagged.
1102 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
1104 // Remove the translucent status bar overlay.
1105 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
1107 // Remove the translucent status bar overlay on the `Drawer Layout`, which is special and needs its own command.
1108 drawerLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
1110 /* SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
1111 * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
1112 * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
1114 rootCoordinatorLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
1115 } else { // Hide everything except the status and navigation bars.
1116 // Remove any `SYSTEM_UI` flags from `rootCoordinatorLayout`.
1117 rootCoordinatorLayout.setSystemUiVisibility(0);
1119 // Add the translucent status flag if it is unset.
1120 getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
1122 if (translucentNavigationBarOnFullscreen) {
1123 // Set the navigation bar to be translucent. This also resets `drawerLayout's` `View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN`.
1124 getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
1126 // Set the navigation bar to be black.
1127 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
1130 } else { // Switch to normal viewing mode.
1131 // Show the `appBar` if `findOnPageLinearLayout` is not visible.
1132 if (findOnPageLinearLayout.getVisibility() == View.GONE) {
1136 // Show the `BannerAd` in the free flavor.
1137 if (BuildConfig.FLAVOR.contentEquals("free")) {
1138 // Initialize the ad. The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations.
1139 AdHelper.initializeAds(findViewById(R.id.adview), getApplicationContext(), getFragmentManager(), getString(R.string.google_app_id), getString(R.string.ad_unit_id));
1142 // Remove any `SYSTEM_UI` flags from `rootCoordinatorLayout`.
1143 rootCoordinatorLayout.setSystemUiVisibility(0);
1145 // Remove the translucent navigation bar flag if it is set.
1146 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
1148 // Add the translucent status flag if it is unset. This also resets `drawerLayout's` `View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN`.
1149 getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
1151 // Constrain `rootCoordinatorLayout` inside the status and navigation bars.
1152 rootCoordinatorLayout.setFitsSystemWindows(true);
1155 // Show the ad if this is the free flavor.
1156 if (BuildConfig.FLAVOR.contentEquals("free")) {
1157 // Reload the ad. The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations.
1158 AdHelper.loadAd(findViewById(R.id.adview), getApplicationContext(), getString(R.string.ad_unit_id));
1164 public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) {
1165 // Show the file chooser if the device is running API >= 21.
1166 if (Build.VERSION.SDK_INT >= 21) {
1167 // Store the file path callback.
1168 fileChooserCallback = filePathCallback;
1170 // Create an intent to open a chooser based ont the file chooser parameters.
1171 Intent fileChooserIntent = fileChooserParams.createIntent();
1173 // Open the file chooser. Currently only one `startActivityForResult` exists in this activity, so the request code, used to differentiate them, is simply `0`.
1174 startActivityForResult(fileChooserIntent, 0);
1180 // Register `mainWebView` for a context menu. This is used to see link targets and download images.
1181 registerForContextMenu(mainWebView);
1183 // Allow the downloading of files.
1184 mainWebView.setDownloadListener((String url, String userAgent, String contentDisposition, String mimetype, long contentLength) -> {
1185 // Check if the download should be processed by an external app.
1186 if (downloadWithExternalApp) { // Download with an external app.
1187 openUrlWithExternalApp(url);
1188 } else { // Download with Android's download manager.
1189 // Check to see if the WRITE_EXTERNAL_STORAGE permission has already been granted.
1190 if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) { // The storage permission has not been granted.
1191 // The WRITE_EXTERNAL_STORAGE permission needs to be requested.
1193 // Store the variables for future use by `onRequestPermissionsResult()`.
1195 downloadContentDisposition = contentDisposition;
1196 downloadContentLength = contentLength;
1198 // Show a dialog if the user has previously denied the permission.
1199 if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) { // Show a dialog explaining the request first.
1200 // Instantiate the download location permission alert dialog and set the download type to DOWNLOAD_FILE.
1201 DialogFragment downloadLocationPermissionDialogFragment = DownloadLocationPermissionDialog.downloadType(DownloadLocationPermissionDialog.DOWNLOAD_FILE);
1203 // Show the download location permission alert dialog. The permission will be requested when the the dialog is closed.
1204 downloadLocationPermissionDialogFragment.show(getFragmentManager(), getString(R.string.download_location));
1205 } else { // Show the permission request directly.
1206 // Request the permission. The download dialog will be launched by `onRequestPermissionResult()`.
1207 ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_FILE_REQUEST_CODE);
1209 } else { // The storage permission has already been granted.
1210 // Get a handle for the download file alert dialog.
1211 AppCompatDialogFragment downloadFileDialogFragment = DownloadFileDialog.fromUrl(url, contentDisposition, contentLength);
1213 // Show the download file alert dialog.
1214 downloadFileDialogFragment.show(getSupportFragmentManager(), getString(R.string.download));
1219 // Allow pinch to zoom.
1220 mainWebView.getSettings().setBuiltInZoomControls(true);
1222 // Hide zoom controls.
1223 mainWebView.getSettings().setDisplayZoomControls(false);
1225 // Don't allow mixed content (HTTP and HTTPS) on the same website.
1226 if (Build.VERSION.SDK_INT >= 21) {
1227 mainWebView.getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_NEVER_ALLOW);
1230 // Set the WebView to use a wide viewport. Otherwise, some web pages will be scrunched and some content will render outside the screen.
1231 mainWebView.getSettings().setUseWideViewPort(true);
1233 // Set the WebView to load in overview mode (zoomed out to the maximum width).
1234 mainWebView.getSettings().setLoadWithOverviewMode(true);
1236 // Explicitly disable geolocation.
1237 mainWebView.getSettings().setGeolocationEnabled(false);
1239 // Initialize cookieManager.
1240 cookieManager = CookieManager.getInstance();
1242 // Replace the header that `WebView` creates for `X-Requested-With` with a null value. The default value is the application ID (com.stoutner.privacybrowser.standard).
1243 customHeaders.put("X-Requested-With", "");
1245 // Initialize the default preference values the first time the program is run. `false` keeps this command from resetting any current preferences back to default.
1246 PreferenceManager.setDefaultValues(this, R.xml.preferences, false);
1248 // Get a handle for the `Runtime`.
1249 privacyBrowserRuntime = Runtime.getRuntime();
1251 // Store the application's private data directory.
1252 privateDataDirectoryString = getApplicationInfo().dataDir;
1253 // `dataDir` will vary, but will be something like `/data/user/0/com.stoutner.privacybrowser.standard`, which links to `/data/data/com.stoutner.privacybrowser.standard`.
1255 // Initialize `inFullScreenBrowsingMode`, which is always false at this point because Privacy Browser never starts in full screen browsing mode.
1256 inFullScreenBrowsingMode = false;
1258 // Initialize the privacy settings variables.
1259 javaScriptEnabled = false;
1260 firstPartyCookiesEnabled = false;
1261 thirdPartyCookiesEnabled = false;
1262 domStorageEnabled = false;
1263 saveFormDataEnabled = false; // Form data can be removed once the minimum API >= 26.
1266 // Store the default user agent.
1267 webViewDefaultUserAgent = mainWebView.getSettings().getUserAgentString();
1269 // Initialize the WebView title.
1270 webViewTitle = getString(R.string.no_title);
1272 // Initialize the favorite icon bitmap. `ContextCompat` must be used until API >= 21.
1273 Drawable favoriteIconDrawable = ContextCompat.getDrawable(getApplicationContext(), R.drawable.world);
1274 BitmapDrawable favoriteIconBitmapDrawable = (BitmapDrawable) favoriteIconDrawable;
1275 assert favoriteIconBitmapDrawable != null;
1276 favoriteIconDefaultBitmap = favoriteIconBitmapDrawable.getBitmap();
1278 // If the favorite icon is null, load the default.
1279 if (favoriteIconBitmap == null) {
1280 favoriteIconBitmap = favoriteIconDefaultBitmap;
1283 // Initialize the user agent array adapter and string array.
1284 userAgentNamesArray = ArrayAdapter.createFromResource(this, R.array.user_agent_names, R.layout.spinner_item);
1285 userAgentDataArray = resources.getStringArray(R.array.user_agent_data);
1287 // Apply the app settings from the shared preferences.
1290 // Instantiate the block list helper.
1291 BlockListHelper blockListHelper = new BlockListHelper();
1293 // Initialize the list of resource requests.
1294 resourceRequests = new ArrayList<>();
1296 // Parse the block lists.
1297 final ArrayList<List<String[]>> easyList = blockListHelper.parseBlockList(getAssets(), "blocklists/easylist.txt");
1298 final ArrayList<List<String[]>> easyPrivacy = blockListHelper.parseBlockList(getAssets(), "blocklists/easyprivacy.txt");
1299 final ArrayList<List<String[]>> fanboysAnnoyanceList = blockListHelper.parseBlockList(getAssets(), "blocklists/fanboy-annoyance.txt");
1300 final ArrayList<List<String[]>> fanboysSocialList = blockListHelper.parseBlockList(getAssets(), "blocklists/fanboy-social.txt");
1301 final ArrayList<List<String[]>> ultraPrivacy = blockListHelper.parseBlockList(getAssets(), "blocklists/ultraprivacy.txt");
1303 // Store the list versions.
1304 easyListVersion = easyList.get(0).get(0)[0];
1305 easyPrivacyVersion = easyPrivacy.get(0).get(0)[0];
1306 fanboysAnnoyanceVersion = fanboysAnnoyanceList.get(0).get(0)[0];
1307 fanboysSocialVersion = fanboysSocialList.get(0).get(0)[0];
1308 ultraPrivacyVersion = ultraPrivacy.get(0).get(0)[0];
1310 // Get a handle for the activity. This is used to update the requests counter while the navigation menu is open.
1311 Activity activity = this;
1313 mainWebView.setWebViewClient(new WebViewClient() {
1314 // `shouldOverrideUrlLoading` makes this `WebView` the default handler for URLs inside the app, so that links are not kicked out to other apps.
1315 // The deprecated `shouldOverrideUrlLoading` must be used until API >= 24.
1316 @SuppressWarnings("deprecation")
1318 public boolean shouldOverrideUrlLoading(WebView view, String url) {
1319 if (url.startsWith("http")) { // Load the URL in Privacy Browser.
1320 // Reset the formatted URL string so the page will load correctly if blocking of third-party requests is enabled.
1321 formattedUrlString = "";
1323 // Apply the domain settings for the new URL. `applyDomainSettings` doesn't do anything if the domain has not changed.
1324 boolean userAgentChanged = applyDomainSettings(url, true, false);
1326 // Check if the user agent has changed.
1327 if (userAgentChanged) {
1328 // Manually load the URL. The changing of the user agent will cause WebView to reload the previous URL.
1329 mainWebView.loadUrl(url, customHeaders);
1331 // Returning true indicates that Privacy Browser is manually handling the loading of the URL.
1334 // Returning false causes the current WebView to handle the URL and prevents it from adding redirects to the history list.
1337 } else if (url.startsWith("mailto:")) { // Load the email address in an external email program.
1338 // Use `ACTION_SENDTO` instead of `ACTION_SEND` so that only email programs are launched.
1339 Intent emailIntent = new Intent(Intent.ACTION_SENDTO);
1341 // Parse the url and set it as the data for the intent.
1342 emailIntent.setData(Uri.parse(url));
1344 // Open the email program in a new task instead of as part of Privacy Browser.
1345 emailIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
1348 startActivity(emailIntent);
1350 // Returning true indicates Privacy Browser is handling the URL by creating an intent.
1352 } else if (url.startsWith("tel:")) { // Load the phone number in the dialer.
1353 // Open the dialer and load the phone number, but wait for the user to place the call.
1354 Intent dialIntent = new Intent(Intent.ACTION_DIAL);
1356 // Add the phone number to the intent.
1357 dialIntent.setData(Uri.parse(url));
1359 // Open the dialer in a new task instead of as part of Privacy Browser.
1360 dialIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
1363 startActivity(dialIntent);
1365 // Returning true indicates Privacy Browser is handling the URL by creating an intent.
1367 } else { // Load a system chooser to select an app that can handle the URL.
1368 // Open an app that can handle the URL.
1369 Intent genericIntent = new Intent(Intent.ACTION_VIEW);
1371 // Add the URL to the intent.
1372 genericIntent.setData(Uri.parse(url));
1374 // List all apps that can handle the URL instead of just opening the first one.
1375 genericIntent.addCategory(Intent.CATEGORY_BROWSABLE);
1377 // Open the app in a new task instead of as part of Privacy Browser.
1378 genericIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
1380 // Start the app or display a snackbar if no app is available to handle the URL.
1382 startActivity(genericIntent);
1383 } catch (ActivityNotFoundException exception) {
1384 Snackbar.make(mainWebView, getString(R.string.unrecognized_url) + " " + url, Snackbar.LENGTH_SHORT).show();
1387 // Returning true indicates Privacy Browser is handling the URL by creating an intent.
1392 // Check requests against the block lists. The deprecated `shouldInterceptRequest()` must be used until minimum API >= 21.
1393 @SuppressWarnings("deprecation")
1395 public WebResourceResponse shouldInterceptRequest(WebView view, String url){
1396 // Create an empty web resource response to be used if the resource request is blocked.
1397 WebResourceResponse emptyWebResourceResponse = new WebResourceResponse("text/plain", "utf8", new ByteArrayInputStream("".getBytes()));
1399 // Reset the whitelist results tracker.
1400 whiteListResultStringArray = null;
1402 // Initialize the third party request tracker.
1403 boolean isThirdPartyRequest = false;
1405 // Initialize the current domain string.
1406 String currentDomain = "";
1408 // Nobody is happy when comparing null strings.
1409 if (!(formattedUrlString == null) && !(url == null)) {
1410 // Get the domain strings to URIs.
1411 Uri currentDomainUri = Uri.parse(formattedUrlString);
1412 Uri requestDomainUri = Uri.parse(url);
1414 // Get the domain host names.
1415 String currentBaseDomain = currentDomainUri.getHost();
1416 String requestBaseDomain = requestDomainUri.getHost();
1418 // Update the current domain variable.
1419 currentDomain = currentBaseDomain;
1421 // Only compare the current base domain and the request base domain if neither is null.
1422 if (!(currentBaseDomain == null) && !(requestBaseDomain == null)) {
1423 // Determine the current base domain.
1424 while (currentBaseDomain.indexOf(".", currentBaseDomain.indexOf(".") + 1) > 0) { // There is at least one subdomain.
1425 // Remove the first subdomain.
1426 currentBaseDomain = currentBaseDomain.substring(currentBaseDomain.indexOf(".") + 1);
1429 // Determine the request base domain.
1430 while (requestBaseDomain.indexOf(".", requestBaseDomain.indexOf(".") + 1) > 0) { // There is at least one subdomain.
1431 // Remove the first subdomain.
1432 requestBaseDomain = requestBaseDomain.substring(requestBaseDomain.indexOf(".") + 1);
1435 // Update the third party request tracker.
1436 isThirdPartyRequest = !currentBaseDomain.equals(requestBaseDomain);
1440 // Block third-party requests if enabled.
1441 if (isThirdPartyRequest && blockAllThirdPartyRequests) {
1442 // Increment the blocked requests counters.
1444 thirdPartyBlockedRequests++;
1446 // Update the titles of the blocklist menu items. This must be run from the UI thread.
1447 activity.runOnUiThread(() -> {
1448 navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
1449 blocklistsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
1450 blockAllThirdPartyRequestsMenuItem.setTitle(thirdPartyBlockedRequests + " - " + getString(R.string.block_all_third_party_requests));
1453 // Add the request to the log.
1454 resourceRequests.add(new String[]{String.valueOf(REQUEST_THIRD_PARTY), url});
1456 // Return an empty web resource response.
1457 return emptyWebResourceResponse;
1460 // Check UltraPrivacy if it is enabled.
1461 if (ultraPrivacyEnabled) {
1462 if (blockListHelper.isBlocked(currentDomain, url, isThirdPartyRequest, ultraPrivacy)) {
1463 // Increment the blocked requests counters.
1465 ultraPrivacyBlockedRequests++;
1467 // Update the titles of the blocklist menu items. This must be run from the UI thread.
1468 activity.runOnUiThread(() -> {
1469 navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
1470 blocklistsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
1471 ultraPrivacyMenuItem.setTitle(ultraPrivacyBlockedRequests + " - " + getString(R.string.ultraprivacy));
1474 // The resource request was blocked. Return an empty web resource response.
1475 return emptyWebResourceResponse;
1478 // If the whitelist result is not null, the request has been allowed by UltraPrivacy.
1479 if (whiteListResultStringArray != null) {
1480 // Add a whitelist entry to the resource requests array.
1481 resourceRequests.add(whiteListResultStringArray);
1483 // The resource request has been allowed by UltraPrivacy. `return null` loads the requested resource.
1488 // Check EasyList if it is enabled.
1489 if (easyListEnabled) {
1490 if (blockListHelper.isBlocked(currentDomain, url, isThirdPartyRequest, easyList)) {
1491 // Increment the blocked requests counters.
1493 easyListBlockedRequests++;
1495 // Update the titles of the blocklist menu items. This must be run from the UI thread.
1496 activity.runOnUiThread(() -> {
1497 navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
1498 blocklistsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
1499 easyListMenuItem.setTitle(easyListBlockedRequests + " - " + getString(R.string.easylist));
1502 // Reset the whitelist results tracker (because otherwise it will sometimes add results to the list due to a race condition).
1503 whiteListResultStringArray = null;
1505 // The resource request was blocked. Return an empty web resource response.
1506 return emptyWebResourceResponse;
1510 // Check EasyPrivacy if it is enabled.
1511 if (easyPrivacyEnabled) {
1512 if (blockListHelper.isBlocked(currentDomain, url, isThirdPartyRequest, easyPrivacy)) {
1513 // Increment the blocked requests counters.
1515 easyPrivacyBlockedRequests++;
1517 // Update the titles of the blocklist menu items. This must be run from the UI thread.
1518 activity.runOnUiThread(() -> {
1519 navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
1520 blocklistsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
1521 easyPrivacyMenuItem.setTitle(easyPrivacyBlockedRequests + " - " + getString(R.string.easyprivacy));
1524 // Reset the whitelist results tracker (because otherwise it will sometimes add results to the list due to a race condition).
1525 whiteListResultStringArray = null;
1527 // The resource request was blocked. Return an empty web resource response.
1528 return emptyWebResourceResponse;
1532 // Check Fanboy’s Annoyance List if it is enabled.
1533 if (fanboysAnnoyanceListEnabled) {
1534 if (blockListHelper.isBlocked(currentDomain, url, isThirdPartyRequest, fanboysAnnoyanceList)) {
1535 // Increment the blocked requests counters.
1537 fanboysAnnoyanceListBlockedRequests++;
1539 // Update the titles of the blocklist menu items. This must be run from the UI thread.
1540 activity.runOnUiThread(() -> {
1541 navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
1542 blocklistsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
1543 fanboysAnnoyanceListMenuItem.setTitle(fanboysAnnoyanceListBlockedRequests + " - " + getString(R.string.fanboys_annoyance_list));
1546 // Reset the whitelist results tracker (because otherwise it will sometimes add results to the list due to a race condition).
1547 whiteListResultStringArray = null;
1549 // The resource request was blocked. Return an empty web resource response.
1550 return emptyWebResourceResponse;
1552 } else if (fanboysSocialBlockingListEnabled){ // Only check Fanboy’s Social Blocking List if Fanboy’s Annoyance List is disabled.
1553 if (blockListHelper.isBlocked(currentDomain, url, isThirdPartyRequest, fanboysSocialList)) {
1554 // Increment the blocked requests counters.
1556 fanboysSocialBlockingListBlockedRequests++;
1558 // Update the titles of the blocklist menu items. This must be run from the UI thread.
1559 activity.runOnUiThread(() -> {
1560 navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
1561 blocklistsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
1562 fanboysSocialBlockingListMenuItem.setTitle(fanboysSocialBlockingListBlockedRequests + " - " + getString(R.string.fanboys_social_blocking_list));
1565 // Reset the whitelist results tracker (because otherwise it will sometimes add results to the list due to a race condition).
1566 whiteListResultStringArray = null;
1568 // The resource request was blocked. Return an empty web resource response.
1569 return emptyWebResourceResponse;
1573 // Add the request to the log because it hasn't been processed by any of the previous checks.
1574 if (whiteListResultStringArray != null ) { // The request was processed by a whitelist.
1575 resourceRequests.add(whiteListResultStringArray);
1576 } else { // The request didn't match any blocklist entry. Log it as a default request.
1577 resourceRequests.add(new String[]{String.valueOf(REQUEST_DEFAULT), url});
1580 // The resource request has not been blocked. `return null` loads the requested resource.
1584 // Handle HTTP authentication requests.
1586 public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, String host, String realm) {
1587 // Store `handler` so it can be accessed from `onHttpAuthenticationCancel()` and `onHttpAuthenticationProceed()`.
1588 httpAuthHandler = handler;
1590 // Display the HTTP authentication dialog.
1591 AppCompatDialogFragment httpAuthenticationDialogFragment = HttpAuthenticationDialog.displayDialog(host, realm);
1592 httpAuthenticationDialogFragment.show(getSupportFragmentManager(), getString(R.string.http_authentication));
1595 // Update the URL in urlTextBox when the page starts to load.
1597 public void onPageStarted(WebView view, String url, Bitmap favicon) {
1598 // Reset the list of resource requests.
1599 resourceRequests.clear();
1601 // Initialize the counters for requests blocked by each blocklist.
1602 blockedRequests = 0;
1603 easyListBlockedRequests = 0;
1604 easyPrivacyBlockedRequests = 0;
1605 fanboysAnnoyanceListBlockedRequests = 0;
1606 fanboysSocialBlockingListBlockedRequests = 0;
1607 ultraPrivacyBlockedRequests = 0;
1608 thirdPartyBlockedRequests = 0;
1610 // If night mode is enabled, hide `mainWebView` until after the night mode CSS is applied.
1612 mainWebView.setVisibility(View.INVISIBLE);
1615 // Hide the keyboard. `0` indicates no additional flags.
1616 inputMethodManager.hideSoftInputFromWindow(mainWebView.getWindowToken(), 0);
1618 // Check to see if Privacy Browser is waiting on Orbot.
1619 if (!waitingForOrbot) { // We are not waiting on Orbot, so we need to process the URL.
1620 // The formatted URL string must be updated at the beginning of the load, so that if the user toggles JavaScript during the load the new website is reloaded.
1621 formattedUrlString = url;
1623 // Display the formatted URL text.
1624 urlTextBox.setText(formattedUrlString);
1626 // Apply text highlighting to `urlTextBox`.
1629 // Apply any custom domain settings if the URL was loaded by navigating history.
1630 if (navigatingHistory) {
1631 // Apply the domain settings.
1632 boolean userAgentChanged = applyDomainSettings(url, true, false);
1634 // Reset `navigatingHistory`.
1635 navigatingHistory = false;
1637 // Manually load the URL if the user agent has changed, which will have caused the previous URL to be reloaded.
1638 if (userAgentChanged) {
1639 loadUrl(formattedUrlString);
1643 // Set `urlIsLoading` to `true`, so that redirects while loading do not trigger changes in the user agent, which forces another reload of the existing page.
1644 urlIsLoading = true;
1646 // Replace Refresh with Stop if the menu item has been created. (The WebView typically begins loading before the menu items are instantiated.)
1647 if (refreshMenuItem != null) {
1649 refreshMenuItem.setTitle(R.string.stop);
1651 // If the icon is displayed in the AppBar, set it according to the theme.
1652 if (displayAdditionalAppBarIcons) {
1654 refreshMenuItem.setIcon(R.drawable.close_dark);
1656 refreshMenuItem.setIcon(R.drawable.close_light);
1663 // It is necessary to update `formattedUrlString` and `urlTextBox` after the page finishes loading because the final URL can change during load.
1665 public void onPageFinished(WebView view, String url) {
1666 // Reset the wide view port if it has been turned off by the waiting for Orbot message.
1667 if (!waitingForOrbot) {
1668 // Only use a wide view port if the URL starts with `http`, not for `file://` and `content://`.
1669 mainWebView.getSettings().setUseWideViewPort(url.startsWith("http"));
1672 // Flush any cookies to persistent storage. `CookieManager` has become very lazy about flushing cookies in recent versions.
1673 if (firstPartyCookiesEnabled && Build.VERSION.SDK_INT >= 21) {
1674 cookieManager.flush();
1677 // Update the Refresh menu item if it has been created.
1678 if (refreshMenuItem != null) {
1679 // Reset the Refresh title.
1680 refreshMenuItem.setTitle(R.string.refresh);
1682 // If the icon is displayed in the AppBar, reset it according to the theme.
1683 if (displayAdditionalAppBarIcons) {
1685 refreshMenuItem.setIcon(R.drawable.refresh_enabled_dark);
1687 refreshMenuItem.setIcon(R.drawable.refresh_enabled_light);
1692 // Reset `urlIsLoading`, which is used to prevent reloads on redirect if the user agent changes.
1693 urlIsLoading = false;
1695 // Clear the cache and history if Incognito Mode is enabled.
1696 if (incognitoModeEnabled) {
1697 // Clear the cache. `true` includes disk files.
1698 mainWebView.clearCache(true);
1700 // Clear the back/forward history.
1701 mainWebView.clearHistory();
1703 // Manually delete cache folders.
1705 // Delete the main cache directory.
1706 privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/cache");
1708 // Delete the secondary `Service Worker` cache directory.
1709 // A `String[]` must be used because the directory contains a space and `Runtime.exec` will not escape the string correctly otherwise.
1710 privacyBrowserRuntime.exec(new String[] {"rm", "-rf", privateDataDirectoryString + "/app_webview/Service Worker/"});
1711 } catch (IOException e) {
1712 // Do nothing if an error is thrown.
1716 // Update `urlTextBox` and apply domain settings if not waiting on Orbot.
1717 if (!waitingForOrbot) {
1718 // Check to see if `WebView` has set `url` to be `about:blank`.
1719 if (url.equals("about:blank")) { // `WebView` is blank, so `formattedUrlString` should be `""` and `urlTextBox` should display a hint.
1720 // Set `formattedUrlString` to `""`.
1721 formattedUrlString = "";
1723 urlTextBox.setText(formattedUrlString);
1725 // Request focus for `urlTextBox`.
1726 urlTextBox.requestFocus();
1728 // Display the keyboard.
1729 inputMethodManager.showSoftInput(urlTextBox, 0);
1731 // Apply the domain settings. This clears any settings from the previous domain.
1732 applyDomainSettings(formattedUrlString, true, false);
1733 } else { // `WebView` has loaded a webpage.
1734 // Set the formatted URL string. Getting the URL from the WebView instead of using the one provided by `onPageFinished` makes websites like YouTube function correctly.
1735 formattedUrlString = mainWebView.getUrl();
1737 // Only update the URL text box if the user is not typing in it.
1738 if (!urlTextBox.hasFocus()) {
1739 // Display the formatted URL text.
1740 urlTextBox.setText(formattedUrlString);
1742 // Apply text highlighting to `urlTextBox`.
1747 // Store the SSL certificate so it can be accessed from `ViewSslCertificateDialog` and `PinnedSslCertificateMismatchDialog`.
1748 sslCertificate = mainWebView.getCertificate();
1750 // Check the current website SSL certificate against the pinned SSL certificate if there is a pinned SSL certificate the user has not chosen to ignore it for this session.
1751 // Also ignore if changes in the user agent causes an error while navigating history.
1752 if (pinnedDomainSslCertificate && !ignorePinnedSslCertificate && navigatingHistory) {
1753 // Initialize the current SSL certificate variables.
1754 String currentWebsiteIssuedToCName = "";
1755 String currentWebsiteIssuedToOName = "";
1756 String currentWebsiteIssuedToUName = "";
1757 String currentWebsiteIssuedByCName = "";
1758 String currentWebsiteIssuedByOName = "";
1759 String currentWebsiteIssuedByUName = "";
1760 Date currentWebsiteSslStartDate = null;
1761 Date currentWebsiteSslEndDate = null;
1764 // Extract the individual pieces of information from the current website SSL certificate if it is not null.
1765 if (sslCertificate != null) {
1766 currentWebsiteIssuedToCName = sslCertificate.getIssuedTo().getCName();
1767 currentWebsiteIssuedToOName = sslCertificate.getIssuedTo().getOName();
1768 currentWebsiteIssuedToUName = sslCertificate.getIssuedTo().getUName();
1769 currentWebsiteIssuedByCName = sslCertificate.getIssuedBy().getCName();
1770 currentWebsiteIssuedByOName = sslCertificate.getIssuedBy().getOName();
1771 currentWebsiteIssuedByUName = sslCertificate.getIssuedBy().getUName();
1772 currentWebsiteSslStartDate = sslCertificate.getValidNotBeforeDate();
1773 currentWebsiteSslEndDate = sslCertificate.getValidNotAfterDate();
1776 // Initialize `String` variables to store the SSL certificate dates. `Strings` are needed to compare the values below, which doesn't work with `Dates` if they are `null`.
1777 String currentWebsiteSslStartDateString = "";
1778 String currentWebsiteSslEndDateString = "";
1779 String pinnedDomainSslStartDateString = "";
1780 String pinnedDomainSslEndDateString = "";
1782 // Convert the `Dates` to `Strings` if they are not `null`.
1783 if (currentWebsiteSslStartDate != null) {
1784 currentWebsiteSslStartDateString = currentWebsiteSslStartDate.toString();
1787 if (currentWebsiteSslEndDate != null) {
1788 currentWebsiteSslEndDateString = currentWebsiteSslEndDate.toString();
1791 if (pinnedDomainSslStartDate != null) {
1792 pinnedDomainSslStartDateString = pinnedDomainSslStartDate.toString();
1795 if (pinnedDomainSslEndDate != null) {
1796 pinnedDomainSslEndDateString = pinnedDomainSslEndDate.toString();
1799 // Check to see if the pinned SSL certificate matches the current website certificate.
1800 if (!currentWebsiteIssuedToCName.equals(pinnedDomainSslIssuedToCNameString) || !currentWebsiteIssuedToOName.equals(pinnedDomainSslIssuedToONameString) ||
1801 !currentWebsiteIssuedToUName.equals(pinnedDomainSslIssuedToUNameString) || !currentWebsiteIssuedByCName.equals(pinnedDomainSslIssuedByCNameString) ||
1802 !currentWebsiteIssuedByOName.equals(pinnedDomainSslIssuedByONameString) || !currentWebsiteIssuedByUName.equals(pinnedDomainSslIssuedByUNameString) ||
1803 !currentWebsiteSslStartDateString.equals(pinnedDomainSslStartDateString) || !currentWebsiteSslEndDateString.equals(pinnedDomainSslEndDateString)) {
1804 // The pinned SSL certificate doesn't match the current domain certificate.
1805 //Display the pinned SSL certificate mismatch `AlertDialog`.
1806 AppCompatDialogFragment pinnedSslCertificateMismatchDialogFragment = new PinnedSslCertificateMismatchDialog();
1807 pinnedSslCertificateMismatchDialogFragment.show(getSupportFragmentManager(), getString(R.string.ssl_certificate_mismatch));
1813 // Handle SSL Certificate errors.
1815 public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
1816 // Get the current website SSL certificate.
1817 SslCertificate currentWebsiteSslCertificate = error.getCertificate();
1819 // Extract the individual pieces of information from the current website SSL certificate.
1820 String currentWebsiteIssuedToCName = currentWebsiteSslCertificate.getIssuedTo().getCName();
1821 String currentWebsiteIssuedToOName = currentWebsiteSslCertificate.getIssuedTo().getOName();
1822 String currentWebsiteIssuedToUName = currentWebsiteSslCertificate.getIssuedTo().getUName();
1823 String currentWebsiteIssuedByCName = currentWebsiteSslCertificate.getIssuedBy().getCName();
1824 String currentWebsiteIssuedByOName = currentWebsiteSslCertificate.getIssuedBy().getOName();
1825 String currentWebsiteIssuedByUName = currentWebsiteSslCertificate.getIssuedBy().getUName();
1826 Date currentWebsiteSslStartDate = currentWebsiteSslCertificate.getValidNotBeforeDate();
1827 Date currentWebsiteSslEndDate = currentWebsiteSslCertificate.getValidNotAfterDate();
1829 // Proceed to the website if the current SSL website certificate matches the pinned domain certificate.
1830 if (pinnedDomainSslCertificate &&
1831 currentWebsiteIssuedToCName.equals(pinnedDomainSslIssuedToCNameString) && currentWebsiteIssuedToOName.equals(pinnedDomainSslIssuedToONameString) &&
1832 currentWebsiteIssuedToUName.equals(pinnedDomainSslIssuedToUNameString) && currentWebsiteIssuedByCName.equals(pinnedDomainSslIssuedByCNameString) &&
1833 currentWebsiteIssuedByOName.equals(pinnedDomainSslIssuedByONameString) && currentWebsiteIssuedByUName.equals(pinnedDomainSslIssuedByUNameString) &&
1834 currentWebsiteSslStartDate.equals(pinnedDomainSslStartDate) && currentWebsiteSslEndDate.equals(pinnedDomainSslEndDate)) {
1835 // An SSL certificate is pinned and matches the current domain certificate.
1836 // Proceed to the website without displaying an error.
1838 } else { // Either there isn't a pinned SSL certificate or it doesn't match the current website certificate.
1839 // Store `handler` so it can be accesses from `onSslErrorCancel()` and `onSslErrorProceed()`.
1840 sslErrorHandler = handler;
1842 // Display the SSL error `AlertDialog`.
1843 AppCompatDialogFragment sslCertificateErrorDialogFragment = SslCertificateErrorDialog.displayDialog(error);
1844 sslCertificateErrorDialogFragment.show(getSupportFragmentManager(), getString(R.string.ssl_certificate_error));
1849 // Get the intent that started the app.
1850 Intent launchingIntent = getIntent();
1852 // Get the information from the intent.
1853 String launchingIntentAction = launchingIntent.getAction();
1854 Uri launchingIntentUriData = launchingIntent.getData();
1856 // If the intent action is a web search, perform the search.
1857 if ((launchingIntentAction != null) && launchingIntentAction.equals(Intent.ACTION_WEB_SEARCH)) {
1858 // Create an encoded URL string.
1859 String encodedUrlString;
1861 // Sanitize the search input and convert it to a search.
1863 encodedUrlString = URLEncoder.encode(launchingIntent.getStringExtra(SearchManager.QUERY), "UTF-8");
1864 } catch (UnsupportedEncodingException exception) {
1865 encodedUrlString = "";
1868 // Add the base search URL.
1869 formattedUrlString = searchURL + encodedUrlString;
1870 } else if (launchingIntentUriData != null){ // Check to see if the intent contains a new URL.
1871 // Set the formatted URL string.
1872 formattedUrlString = launchingIntentUriData.toString();
1875 // Load the website if not waiting for Orbot to connect.
1876 if (!waitingForOrbot) {
1877 loadUrl(formattedUrlString);
1882 protected void onNewIntent(Intent intent) {
1883 // Sets the new intent as the activity intent, so that any future `getIntent()`s pick up this one instead of creating a new activity.
1886 // Get the information from the intent.
1887 String intentAction = intent.getAction();
1888 Uri intentUriData = intent.getData();
1890 // If the intent action is a web search, perform the search.
1891 if ((intentAction != null) && intentAction.equals(Intent.ACTION_WEB_SEARCH)) {
1892 // Create an encoded URL string.
1893 String encodedUrlString;
1895 // Sanitize the search input and convert it to a search.
1897 encodedUrlString = URLEncoder.encode(intent.getStringExtra(SearchManager.QUERY), "UTF-8");
1898 } catch (UnsupportedEncodingException exception) {
1899 encodedUrlString = "";
1902 // Add the base search URL.
1903 formattedUrlString = searchURL + encodedUrlString;
1904 } else if (intentUriData != null){ // Check to see if the intent contains a new URL.
1905 // Set the formatted URL string.
1906 formattedUrlString = intentUriData.toString();
1910 loadUrl(formattedUrlString);
1912 // Close the navigation drawer if it is open.
1913 if (drawerLayout.isDrawerVisible(GravityCompat.START)) {
1914 drawerLayout.closeDrawer(GravityCompat.START);
1917 // Close the bookmarks drawer if it is open.
1918 if (drawerLayout.isDrawerVisible(GravityCompat.END)) {
1919 drawerLayout.closeDrawer(GravityCompat.END);
1922 // Clear the keyboard if displayed and remove the focus on the urlTextBar if it has it.
1923 mainWebView.requestFocus();
1927 public void onRestart() {
1928 // Run the default commands.
1931 // Make sure Orbot is running if Privacy Browser is proxying through Orbot.
1932 if (proxyThroughOrbot) {
1933 // Request Orbot to start. If Orbot is already running no hard will be caused by this request.
1934 Intent orbotIntent = new Intent("org.torproject.android.intent.action.START");
1936 // Send the intent to the Orbot package.
1937 orbotIntent.setPackage("org.torproject.android");
1940 sendBroadcast(orbotIntent);
1943 // Apply the app settings if returning from the Settings activity..
1944 if (reapplyAppSettingsOnRestart) {
1945 // Apply the app settings.
1948 // Reload the webpage if displaying of images has been disabled in the Settings activity.
1949 if (reloadOnRestart) {
1950 // Reload `mainWebView`.
1951 mainWebView.reload();
1953 // Reset `reloadOnRestartBoolean`.
1954 reloadOnRestart = false;
1957 // Reset the return from settings flag.
1958 reapplyAppSettingsOnRestart = false;
1961 // Apply the domain settings if returning from the Domains activity.
1962 if (reapplyDomainSettingsOnRestart) {
1963 // Reapply the domain settings.
1964 applyDomainSettings(formattedUrlString, false, true);
1966 // Reset `reapplyDomainSettingsOnRestart`.
1967 reapplyDomainSettingsOnRestart = false;
1970 // Load the URL on restart to apply changes to night mode.
1971 if (loadUrlOnRestart) {
1972 // Load the current `formattedUrlString`.
1973 loadUrl(formattedUrlString);
1975 // Reset `loadUrlOnRestart.
1976 loadUrlOnRestart = false;
1979 // Update the bookmarks drawer if returning from the Bookmarks activity.
1980 if (restartFromBookmarksActivity) {
1981 // Close the bookmarks drawer.
1982 drawerLayout.closeDrawer(GravityCompat.END);
1984 // Reload the bookmarks drawer.
1985 loadBookmarksFolder();
1987 // Reset `restartFromBookmarksActivity`.
1988 restartFromBookmarksActivity = false;
1991 // Update the privacy icon. `true` runs `invalidateOptionsMenu` as the last step. This can be important if the screen was rotated.
1992 updatePrivacyIcons(true);
1995 // `onResume()` runs after `onStart()`, which runs after `onCreate()` and `onRestart()`.
1997 public void onResume() {
1998 // Run the default commands.
2001 // Resume JavaScript (if enabled).
2002 mainWebView.resumeTimers();
2004 // Resume `mainWebView`.
2005 mainWebView.onResume();
2007 // Resume the adView for the free flavor.
2008 if (BuildConfig.FLAVOR.contentEquals("free")) {
2010 AdHelper.resumeAd(findViewById(R.id.adview));
2013 // Display a message to the user if waiting for Orbot.
2014 if (waitingForOrbot && !orbotStatus.equals("ON")) {
2015 // Disable the wide view port so that the waiting for Orbot text is displayed correctly.
2016 mainWebView.getSettings().setUseWideViewPort(false);
2018 // Load a waiting page. `null` specifies no encoding, which defaults to ASCII.
2019 mainWebView.loadData(waitingForOrbotHtmlString, "text/html", null);
2022 if (displayingFullScreenVideo) {
2023 // Remove the translucent overlays.
2024 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
2026 // Remove the translucent status bar overlay on the `Drawer Layout`, which is special and needs its own command.
2027 drawerLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
2029 /* SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
2030 * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
2031 * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
2033 rootCoordinatorLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
2038 public void onPause() {
2039 // Run the default commands.
2042 // Pause `mainWebView`.
2043 mainWebView.onPause();
2045 // Stop all JavaScript.
2046 mainWebView.pauseTimers();
2048 // Pause the ad or it will continue to consume resources in the background on the free flavor.
2049 if (BuildConfig.FLAVOR.contentEquals("free")) {
2051 AdHelper.pauseAd(findViewById(R.id.adview));
2056 public void onDestroy() {
2057 // Unregister the Orbot status broadcast receiver.
2058 this.unregisterReceiver(orbotStatusBroadcastReceiver);
2060 // Close the bookmarks cursor and database.
2061 bookmarksCursor.close();
2062 bookmarksDatabaseHelper.close();
2064 // Run the default commands.
2069 public boolean onCreateOptionsMenu(Menu menu) {
2070 // Inflate the menu; this adds items to the action bar if it is present.
2071 getMenuInflater().inflate(R.menu.webview_options_menu, menu);
2073 // Set mainMenu so it can be used by `onOptionsItemSelected()` and `updatePrivacyIcons`.
2076 // Set the initial status of the privacy icons. `false` does not call `invalidateOptionsMenu` as the last step.
2077 updatePrivacyIcons(false);
2079 // Get handles for the menu items.
2080 MenuItem toggleFirstPartyCookiesMenuItem = menu.findItem(R.id.toggle_first_party_cookies);
2081 MenuItem toggleThirdPartyCookiesMenuItem = menu.findItem(R.id.toggle_third_party_cookies);
2082 MenuItem toggleDomStorageMenuItem = menu.findItem(R.id.toggle_dom_storage);
2083 MenuItem toggleSaveFormDataMenuItem = menu.findItem(R.id.toggle_save_form_data); // Form data can be removed once the minimum API >= 26.
2084 MenuItem clearFormDataMenuItem = menu.findItem(R.id.clear_form_data); // Form data can be removed once the minimum API >= 26.
2085 refreshMenuItem = menu.findItem(R.id.refresh);
2086 blocklistsMenuItem = menu.findItem(R.id.blocklists);
2087 easyListMenuItem = menu.findItem(R.id.easylist);
2088 easyPrivacyMenuItem = menu.findItem(R.id.easyprivacy);
2089 fanboysAnnoyanceListMenuItem = menu.findItem(R.id.fanboys_annoyance_list);
2090 fanboysSocialBlockingListMenuItem = menu.findItem(R.id.fanboys_social_blocking_list);
2091 ultraPrivacyMenuItem = menu.findItem(R.id.ultraprivacy);
2092 blockAllThirdPartyRequestsMenuItem = menu.findItem(R.id.block_all_third_party_requests);
2093 MenuItem adConsentMenuItem = menu.findItem(R.id.ad_consent);
2095 // Only display third-party cookies if API >= 21
2096 toggleThirdPartyCookiesMenuItem.setVisible(Build.VERSION.SDK_INT >= 21);
2098 // Only display the form data menu items if the API < 26.
2099 toggleSaveFormDataMenuItem.setVisible(Build.VERSION.SDK_INT < 26);
2100 clearFormDataMenuItem.setVisible(Build.VERSION.SDK_INT < 26);
2102 // Only show Ad Consent if this is the free flavor.
2103 adConsentMenuItem.setVisible(BuildConfig.FLAVOR.contentEquals("free"));
2105 // Get the shared preference values.
2106 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
2108 // Get the status of the additional AppBar icons.
2109 displayAdditionalAppBarIcons = sharedPreferences.getBoolean("display_additional_app_bar_icons", false);
2111 // Set the status of the additional app bar icons. Setting the refresh menu item to `SHOW_AS_ACTION_ALWAYS` makes it appear even on small devices like phones.
2112 if (displayAdditionalAppBarIcons) {
2113 toggleFirstPartyCookiesMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
2114 toggleDomStorageMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
2115 refreshMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
2116 } else { //Do not display the additional icons.
2117 toggleFirstPartyCookiesMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
2118 toggleDomStorageMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
2119 refreshMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
2122 // Replace Refresh with Stop if a URL is already loading.
2125 refreshMenuItem.setTitle(R.string.stop);
2127 // If the icon is displayed in the AppBar, set it according to the theme.
2128 if (displayAdditionalAppBarIcons) {
2130 refreshMenuItem.setIcon(R.drawable.close_dark);
2132 refreshMenuItem.setIcon(R.drawable.close_light);
2141 public boolean onPrepareOptionsMenu(Menu menu) {
2142 // Get handles for the menu items.
2143 MenuItem addOrEditDomain = menu.findItem(R.id.add_or_edit_domain);
2144 MenuItem toggleFirstPartyCookiesMenuItem = menu.findItem(R.id.toggle_first_party_cookies);
2145 MenuItem toggleThirdPartyCookiesMenuItem = menu.findItem(R.id.toggle_third_party_cookies);
2146 MenuItem toggleDomStorageMenuItem = menu.findItem(R.id.toggle_dom_storage);
2147 MenuItem toggleSaveFormDataMenuItem = menu.findItem(R.id.toggle_save_form_data); // Form data can be removed once the minimum API >= 26.
2148 MenuItem clearDataMenuItem = menu.findItem(R.id.clear_data);
2149 MenuItem clearCookiesMenuItem = menu.findItem(R.id.clear_cookies);
2150 MenuItem clearDOMStorageMenuItem = menu.findItem(R.id.clear_dom_storage);
2151 MenuItem clearFormDataMenuItem = menu.findItem(R.id.clear_form_data); // Form data can be removed once the minimum API >= 26.
2152 MenuItem fontSizeMenuItem = menu.findItem(R.id.font_size);
2153 MenuItem swipeToRefreshMenuItem = menu.findItem(R.id.swipe_to_refresh);
2154 MenuItem displayImagesMenuItem = menu.findItem(R.id.display_images);
2155 MenuItem nightModeMenuItem = menu.findItem(R.id.night_mode);
2156 MenuItem proxyThroughOrbotMenuItem = menu.findItem(R.id.proxy_through_orbot);
2158 // Set the text for the domain menu item.
2159 if (domainSettingsApplied) {
2160 addOrEditDomain.setTitle(R.string.edit_domain_settings);
2162 addOrEditDomain.setTitle(R.string.add_domain_settings);
2165 // Set the status of the menu item checkboxes.
2166 toggleFirstPartyCookiesMenuItem.setChecked(firstPartyCookiesEnabled);
2167 toggleThirdPartyCookiesMenuItem.setChecked(thirdPartyCookiesEnabled);
2168 toggleDomStorageMenuItem.setChecked(domStorageEnabled);
2169 toggleSaveFormDataMenuItem.setChecked(saveFormDataEnabled); // Form data can be removed once the minimum API >= 26.
2170 easyListMenuItem.setChecked(easyListEnabled);
2171 easyPrivacyMenuItem.setChecked(easyPrivacyEnabled);
2172 fanboysAnnoyanceListMenuItem.setChecked(fanboysAnnoyanceListEnabled);
2173 fanboysSocialBlockingListMenuItem.setChecked(fanboysSocialBlockingListEnabled);
2174 ultraPrivacyMenuItem.setChecked(ultraPrivacyEnabled);
2175 blockAllThirdPartyRequestsMenuItem.setChecked(blockAllThirdPartyRequests);
2176 swipeToRefreshMenuItem.setChecked(swipeRefreshLayout.isEnabled());
2177 displayImagesMenuItem.setChecked(mainWebView.getSettings().getLoadsImagesAutomatically());
2178 nightModeMenuItem.setChecked(nightMode);
2179 proxyThroughOrbotMenuItem.setChecked(proxyThroughOrbot);
2181 // Enable third-party cookies if first-party cookies are enabled.
2182 toggleThirdPartyCookiesMenuItem.setEnabled(firstPartyCookiesEnabled);
2184 // Enable DOM Storage if JavaScript is enabled.
2185 toggleDomStorageMenuItem.setEnabled(javaScriptEnabled);
2187 // Enable Clear Cookies if there are any.
2188 clearCookiesMenuItem.setEnabled(cookieManager.hasCookies());
2190 // Get a count of the number of files in the Local Storage directory.
2191 File localStorageDirectory = new File (privateDataDirectoryString + "/app_webview/Local Storage/");
2192 int localStorageDirectoryNumberOfFiles = 0;
2193 if (localStorageDirectory.exists()) {
2194 localStorageDirectoryNumberOfFiles = localStorageDirectory.list().length;
2197 // Get a count of the number of files in the IndexedDB directory.
2198 File indexedDBDirectory = new File (privateDataDirectoryString + "/app_webview/IndexedDB");
2199 int indexedDBDirectoryNumberOfFiles = 0;
2200 if (indexedDBDirectory.exists()) {
2201 indexedDBDirectoryNumberOfFiles = indexedDBDirectory.list().length;
2204 // Enable Clear DOM Storage if there is any.
2205 clearDOMStorageMenuItem.setEnabled(localStorageDirectoryNumberOfFiles > 0 || indexedDBDirectoryNumberOfFiles > 0);
2207 // Enable Clear Form Data is there is any. This can be removed once the minimum API >= 26.
2208 if (Build.VERSION.SDK_INT < 26) {
2209 WebViewDatabase mainWebViewDatabase = WebViewDatabase.getInstance(this);
2210 clearFormDataMenuItem.setEnabled(mainWebViewDatabase.hasFormData());
2212 // Disable clear form data because it is not supported on current version of Android.
2213 clearFormDataMenuItem.setEnabled(false);
2216 // Enable Clear Data if any of the submenu items are enabled.
2217 clearDataMenuItem.setEnabled(clearCookiesMenuItem.isEnabled() || clearDOMStorageMenuItem.isEnabled() || clearFormDataMenuItem.isEnabled());
2219 // Disable Fanboy's Social Blocking List if Fanboy's Annoyance List is checked.
2220 fanboysSocialBlockingListMenuItem.setEnabled(!fanboysAnnoyanceListEnabled);
2222 // Initialize the display names for the blocklists with the number of blocked requests.
2223 blocklistsMenuItem.setTitle(getString(R.string.blocklists) + " - " + blockedRequests);
2224 easyListMenuItem.setTitle(easyListBlockedRequests + " - " + getString(R.string.easylist));
2225 easyPrivacyMenuItem.setTitle(easyPrivacyBlockedRequests + " - " + getString(R.string.easyprivacy));
2226 fanboysAnnoyanceListMenuItem.setTitle(fanboysAnnoyanceListBlockedRequests + " - " + getString(R.string.fanboys_annoyance_list));
2227 fanboysSocialBlockingListMenuItem.setTitle(fanboysSocialBlockingListBlockedRequests + " - " + getString(R.string.fanboys_social_blocking_list));
2228 ultraPrivacyMenuItem.setTitle(ultraPrivacyBlockedRequests + " - " + getString(R.string.ultraprivacy));
2229 blockAllThirdPartyRequestsMenuItem.setTitle(thirdPartyBlockedRequests + " - " + getString(R.string.block_all_third_party_requests));
2231 // Get the current user agent.
2232 String currentUserAgent = mainWebView.getSettings().getUserAgentString();
2234 // Select the current user agent menu item. A switch statement cannot be used because the user agents are not compile time constants.
2235 if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[0])) { // Privacy Browser.
2236 menu.findItem(R.id.user_agent_privacy_browser).setChecked(true);
2237 } else if (currentUserAgent.equals(webViewDefaultUserAgent)) { // WebView Default.
2238 menu.findItem(R.id.user_agent_webview_default).setChecked(true);
2239 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[2])) { // Firefox on Android.
2240 menu.findItem(R.id.user_agent_firefox_on_android).setChecked(true);
2241 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[3])) { // Chrome on Android.
2242 menu.findItem(R.id.user_agent_chrome_on_android).setChecked(true);
2243 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[4])) { // Safari on iOS.
2244 menu.findItem(R.id.user_agent_safari_on_ios).setChecked(true);
2245 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[5])) { // Firefox on Linux.
2246 menu.findItem(R.id.user_agent_firefox_on_linux).setChecked(true);
2247 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[6])) { // Chromium on Linux.
2248 menu.findItem(R.id.user_agent_chromium_on_linux).setChecked(true);
2249 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[7])) { // Firefox on Windows.
2250 menu.findItem(R.id.user_agent_firefox_on_windows).setChecked(true);
2251 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[8])) { // Chrome on Windows.
2252 menu.findItem(R.id.user_agent_chrome_on_windows).setChecked(true);
2253 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[9])) { // Edge on Windows.
2254 menu.findItem(R.id.user_agent_edge_on_windows).setChecked(true);
2255 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[10])) { // Internet Explorer on Windows.
2256 menu.findItem(R.id.user_agent_internet_explorer_on_windows).setChecked(true);
2257 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[11])) { // Safari on macOS.
2258 menu.findItem(R.id.user_agent_safari_on_macos).setChecked(true);
2259 } else { // Custom user agent.
2260 menu.findItem(R.id.user_agent_custom).setChecked(true);
2263 // Initialize font size variables.
2264 int fontSize = mainWebView.getSettings().getTextZoom();
2265 String fontSizeTitle;
2266 MenuItem selectedFontSizeMenuItem;
2268 // Prepare the font size title and current size menu item.
2271 fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.twenty_five_percent);
2272 selectedFontSizeMenuItem = menu.findItem(R.id.font_size_twenty_five_percent);
2276 fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.fifty_percent);
2277 selectedFontSizeMenuItem = menu.findItem(R.id.font_size_fifty_percent);
2281 fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.seventy_five_percent);
2282 selectedFontSizeMenuItem = menu.findItem(R.id.font_size_seventy_five_percent);
2286 fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.one_hundred_percent);
2287 selectedFontSizeMenuItem = menu.findItem(R.id.font_size_one_hundred_percent);
2291 fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.one_hundred_twenty_five_percent);
2292 selectedFontSizeMenuItem = menu.findItem(R.id.font_size_one_hundred_twenty_five_percent);
2296 fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.one_hundred_fifty_percent);
2297 selectedFontSizeMenuItem = menu.findItem(R.id.font_size_one_hundred_fifty_percent);
2301 fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.one_hundred_seventy_five_percent);
2302 selectedFontSizeMenuItem = menu.findItem(R.id.font_size_one_hundred_seventy_five_percent);
2306 fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.two_hundred_percent);
2307 selectedFontSizeMenuItem = menu.findItem(R.id.font_size_two_hundred_percent);
2311 fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.one_hundred_percent);
2312 selectedFontSizeMenuItem = menu.findItem(R.id.font_size_one_hundred_percent);
2316 // Set the font size title and select the current size menu item.
2317 fontSizeMenuItem.setTitle(fontSizeTitle);
2318 selectedFontSizeMenuItem.setChecked(true);
2320 // Run all the other default commands.
2321 super.onPrepareOptionsMenu(menu);
2323 // Display the menu.
2328 // Remove Android Studio's warning about the dangers of using SetJavaScriptEnabled.
2329 @SuppressLint("SetJavaScriptEnabled")
2330 // removeAllCookies is deprecated, but it is required for API < 21.
2331 @SuppressWarnings("deprecation")
2332 public boolean onOptionsItemSelected(MenuItem menuItem) {
2333 // Get the selected menu item ID.
2334 int menuItemId = menuItem.getItemId();
2336 // Set the commands that relate to the menu entries.
2337 switch (menuItemId) {
2338 case R.id.toggle_javascript:
2339 // Switch the status of javaScriptEnabled.
2340 javaScriptEnabled = !javaScriptEnabled;
2342 // Apply the new JavaScript status.
2343 mainWebView.getSettings().setJavaScriptEnabled(javaScriptEnabled);
2345 // Update the privacy icon. `true` runs `invalidateOptionsMenu` as the last step.
2346 updatePrivacyIcons(true);
2348 // Display a `Snackbar`.
2349 if (javaScriptEnabled) { // JavaScrip is enabled.
2350 Snackbar.make(findViewById(R.id.main_webview), R.string.javascript_enabled, Snackbar.LENGTH_SHORT).show();
2351 } else if (firstPartyCookiesEnabled) { // JavaScript is disabled, but first-party cookies are enabled.
2352 Snackbar.make(findViewById(R.id.main_webview), R.string.javascript_disabled, Snackbar.LENGTH_SHORT).show();
2353 } else { // Privacy mode.
2354 Snackbar.make(findViewById(R.id.main_webview), R.string.privacy_mode, Snackbar.LENGTH_SHORT).show();
2357 // Reload the WebView.
2358 mainWebView.reload();
2361 case R.id.add_or_edit_domain:
2362 if (domainSettingsApplied) { // Edit the current domain settings.
2363 // Reapply the domain settings on returning to `MainWebViewActivity`.
2364 reapplyDomainSettingsOnRestart = true;
2365 currentDomainName = "";
2367 // Create an intent to launch the domains activity.
2368 Intent domainsIntent = new Intent(this, DomainsActivity.class);
2370 // Put extra information instructing the domains activity to directly load the current domain and close on back instead of returning to the domains list.
2371 domainsIntent.putExtra("loadDomain", domainSettingsDatabaseId);
2372 domainsIntent.putExtra("closeOnBack", true);
2375 startActivity(domainsIntent);
2376 } else { // Add a new domain.
2377 // Apply the new domain settings on returning to `MainWebViewActivity`.
2378 reapplyDomainSettingsOnRestart = true;
2379 currentDomainName = "";
2381 // Get the current domain
2382 Uri currentUri = Uri.parse(formattedUrlString);
2383 String currentDomain = currentUri.getHost();
2385 // Initialize the database handler. The `0` specifies the database version, but that is ignored and set instead using a constant in `DomainsDatabaseHelper`.
2386 DomainsDatabaseHelper domainsDatabaseHelper = new DomainsDatabaseHelper(this, null, null, 0);
2388 // Create the domain and store the database ID.
2389 int newDomainDatabaseId = domainsDatabaseHelper.addDomain(currentDomain);
2391 // Create an intent to launch the domains activity.
2392 Intent domainsIntent = new Intent(this, DomainsActivity.class);
2394 // Put extra information instructing the domains activity to directly load the new domain and close on back instead of returning to the domains list.
2395 domainsIntent.putExtra("loadDomain", newDomainDatabaseId);
2396 domainsIntent.putExtra("closeOnBack", true);
2399 startActivity(domainsIntent);
2403 case R.id.toggle_first_party_cookies:
2404 // Switch the status of firstPartyCookiesEnabled.
2405 firstPartyCookiesEnabled = !firstPartyCookiesEnabled;
2407 // Update the menu checkbox.
2408 menuItem.setChecked(firstPartyCookiesEnabled);
2410 // Apply the new cookie status.
2411 cookieManager.setAcceptCookie(firstPartyCookiesEnabled);
2413 // Update the privacy icon. `true` runs `invalidateOptionsMenu` as the last step.
2414 updatePrivacyIcons(true);
2416 // Display a `Snackbar`.
2417 if (firstPartyCookiesEnabled) { // First-party cookies are enabled.
2418 Snackbar.make(findViewById(R.id.main_webview), R.string.first_party_cookies_enabled, Snackbar.LENGTH_SHORT).show();
2419 } else if (javaScriptEnabled) { // JavaScript is still enabled.
2420 Snackbar.make(findViewById(R.id.main_webview), R.string.first_party_cookies_disabled, Snackbar.LENGTH_SHORT).show();
2421 } else { // Privacy mode.
2422 Snackbar.make(findViewById(R.id.main_webview), R.string.privacy_mode, Snackbar.LENGTH_SHORT).show();
2425 // Reload the WebView.
2426 mainWebView.reload();
2429 case R.id.toggle_third_party_cookies:
2430 if (Build.VERSION.SDK_INT >= 21) {
2431 // Switch the status of thirdPartyCookiesEnabled.
2432 thirdPartyCookiesEnabled = !thirdPartyCookiesEnabled;
2434 // Update the menu checkbox.
2435 menuItem.setChecked(thirdPartyCookiesEnabled);
2437 // Apply the new cookie status.
2438 cookieManager.setAcceptThirdPartyCookies(mainWebView, thirdPartyCookiesEnabled);
2440 // Display a `Snackbar`.
2441 if (thirdPartyCookiesEnabled) {
2442 Snackbar.make(findViewById(R.id.main_webview), R.string.third_party_cookies_enabled, Snackbar.LENGTH_SHORT).show();
2444 Snackbar.make(findViewById(R.id.main_webview), R.string.third_party_cookies_disabled, Snackbar.LENGTH_SHORT).show();
2447 // Reload the WebView.
2448 mainWebView.reload();
2449 } // Else do nothing because SDK < 21.
2452 case R.id.toggle_dom_storage:
2453 // Switch the status of domStorageEnabled.
2454 domStorageEnabled = !domStorageEnabled;
2456 // Update the menu checkbox.
2457 menuItem.setChecked(domStorageEnabled);
2459 // Apply the new DOM Storage status.
2460 mainWebView.getSettings().setDomStorageEnabled(domStorageEnabled);
2462 // Update the privacy icon. `true` runs `invalidateOptionsMenu` as the last step.
2463 updatePrivacyIcons(true);
2465 // Display a `Snackbar`.
2466 if (domStorageEnabled) {
2467 Snackbar.make(findViewById(R.id.main_webview), R.string.dom_storage_enabled, Snackbar.LENGTH_SHORT).show();
2469 Snackbar.make(findViewById(R.id.main_webview), R.string.dom_storage_disabled, Snackbar.LENGTH_SHORT).show();
2472 // Reload the WebView.
2473 mainWebView.reload();
2476 // Form data can be removed once the minimum API >= 26.
2477 case R.id.toggle_save_form_data:
2478 // Switch the status of saveFormDataEnabled.
2479 saveFormDataEnabled = !saveFormDataEnabled;
2481 // Update the menu checkbox.
2482 menuItem.setChecked(saveFormDataEnabled);
2484 // Apply the new form data status.
2485 mainWebView.getSettings().setSaveFormData(saveFormDataEnabled);
2487 // Display a `Snackbar`.
2488 if (saveFormDataEnabled) {
2489 Snackbar.make(findViewById(R.id.main_webview), R.string.form_data_enabled, Snackbar.LENGTH_SHORT).show();
2491 Snackbar.make(findViewById(R.id.main_webview), R.string.form_data_disabled, Snackbar.LENGTH_SHORT).show();
2494 // Update the privacy icon. `true` runs `invalidateOptionsMenu` as the last step.
2495 updatePrivacyIcons(true);
2497 // Reload the WebView.
2498 mainWebView.reload();
2501 case R.id.clear_cookies:
2502 Snackbar.make(findViewById(R.id.main_webview), R.string.cookies_deleted, Snackbar.LENGTH_LONG)
2503 .setAction(R.string.undo, v -> {
2504 // Do nothing because everything will be handled by `onDismissed()` below.
2506 .addCallback(new Snackbar.Callback() {
2508 public void onDismissed(Snackbar snackbar, int event) {
2510 // The user pushed the `Undo` button.
2511 case Snackbar.Callback.DISMISS_EVENT_ACTION:
2515 // The `Snackbar` was dismissed without the `Undo` button being pushed.
2517 // `cookieManager.removeAllCookie()` varies by SDK.
2518 if (Build.VERSION.SDK_INT < 21) {
2519 cookieManager.removeAllCookie();
2521 // `null` indicates no callback.
2522 cookieManager.removeAllCookies(null);
2530 case R.id.clear_dom_storage:
2531 Snackbar.make(findViewById(R.id.main_webview), R.string.dom_storage_deleted, Snackbar.LENGTH_LONG)
2532 .setAction(R.string.undo, v -> {
2533 // Do nothing because everything will be handled by `onDismissed()` below.
2535 .addCallback(new Snackbar.Callback() {
2537 public void onDismissed(Snackbar snackbar, int event) {
2539 // The user pushed the `Undo` button.
2540 case Snackbar.Callback.DISMISS_EVENT_ACTION:
2544 // The `Snackbar` was dismissed without the `Undo` button being pushed.
2546 // Delete the DOM Storage.
2547 WebStorage webStorage = WebStorage.getInstance();
2548 webStorage.deleteAllData();
2550 // Initialize a handler to manually delete the DOM storage files and directories.
2551 Handler deleteDomStorageHandler = new Handler();
2553 // Setup a runnable to manually delete the DOM storage files and directories.
2554 Runnable deleteDomStorageRunnable = () -> {
2556 // A `String[]` must be used because the directory contains a space and `Runtime.exec` will otherwise not escape the string correctly.
2557 privacyBrowserRuntime.exec(new String[]{"rm", "-rf", privateDataDirectoryString + "/app_webview/Local Storage/"});
2559 // Multiple commands must be used because `Runtime.exec()` does not like `*`.
2560 privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/IndexedDB");
2561 privacyBrowserRuntime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager");
2562 privacyBrowserRuntime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager-journal");
2563 privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/databases");
2564 } catch (IOException e) {
2565 // Do nothing if an error is thrown.
2569 // Manually delete the DOM storage files after 200 milliseconds.
2570 deleteDomStorageHandler.postDelayed(deleteDomStorageRunnable, 200);
2577 // Form data can be remove once the minimum API >= 26.
2578 case R.id.clear_form_data:
2579 Snackbar.make(findViewById(R.id.main_webview), R.string.form_data_deleted, Snackbar.LENGTH_LONG)
2580 .setAction(R.string.undo, v -> {
2581 // Do nothing because everything will be handled by `onDismissed()` below.
2583 .addCallback(new Snackbar.Callback() {
2585 public void onDismissed(Snackbar snackbar, int event) {
2587 // The user pushed the `Undo` button.
2588 case Snackbar.Callback.DISMISS_EVENT_ACTION:
2592 // The `Snackbar` was dismissed without the `Undo` button being pushed.
2594 // Delete the form data.
2595 WebViewDatabase mainWebViewDatabase = WebViewDatabase.getInstance(getApplicationContext());
2596 mainWebViewDatabase.clearFormData();
2604 // Toggle the EasyList status.
2605 easyListEnabled = !easyListEnabled;
2607 // Update the menu checkbox.
2608 menuItem.setChecked(easyListEnabled);
2610 // Reload the main WebView.
2611 mainWebView.reload();
2614 case R.id.easyprivacy:
2615 // Toggle the EasyPrivacy status.
2616 easyPrivacyEnabled = !easyPrivacyEnabled;
2618 // Update the menu checkbox.
2619 menuItem.setChecked(easyPrivacyEnabled);
2621 // Reload the main WebView.
2622 mainWebView.reload();
2625 case R.id.fanboys_annoyance_list:
2626 // Toggle Fanboy's Annoyance List status.
2627 fanboysAnnoyanceListEnabled = !fanboysAnnoyanceListEnabled;
2629 // Update the menu checkbox.
2630 menuItem.setChecked(fanboysAnnoyanceListEnabled);
2632 // Update the staus of Fanboy's Social Blocking List.
2633 MenuItem fanboysSocialBlockingListMenuItem = mainMenu.findItem(R.id.fanboys_social_blocking_list);
2634 fanboysSocialBlockingListMenuItem.setEnabled(!fanboysAnnoyanceListEnabled);
2636 // Reload the main WebView.
2637 mainWebView.reload();
2640 case R.id.fanboys_social_blocking_list:
2641 // Toggle Fanboy's Social Blocking List status.
2642 fanboysSocialBlockingListEnabled = !fanboysSocialBlockingListEnabled;
2644 // Update the menu checkbox.
2645 menuItem.setChecked(fanboysSocialBlockingListEnabled);
2647 // Reload the main WebView.
2648 mainWebView.reload();
2651 case R.id.ultraprivacy:
2652 // Toggle the UltraPrivacy status.
2653 ultraPrivacyEnabled = !ultraPrivacyEnabled;
2655 // Update the menu checkbox.
2656 menuItem.setChecked(ultraPrivacyEnabled);
2658 // Reload the main WebView.
2659 mainWebView.reload();
2662 case R.id.block_all_third_party_requests:
2663 //Toggle the third-party requests blocker status.
2664 blockAllThirdPartyRequests = !blockAllThirdPartyRequests;
2666 // Update the menu checkbox.
2667 menuItem.setChecked(blockAllThirdPartyRequests);
2669 // Reload the main WebView.
2670 mainWebView.reload();
2673 case R.id.user_agent_privacy_browser:
2674 // Update the user agent.
2675 mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[0]);
2677 // Reload the WebView.
2678 mainWebView.reload();
2681 case R.id.user_agent_webview_default:
2682 // Update the user agent.
2683 mainWebView.getSettings().setUserAgentString("");
2685 // Reload the WebView.
2686 mainWebView.reload();
2689 case R.id.user_agent_firefox_on_android:
2690 // Update the user agent.
2691 mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[2]);
2693 // Reload the WebView.
2694 mainWebView.reload();
2697 case R.id.user_agent_chrome_on_android:
2698 // Update the user agent.
2699 mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[3]);
2701 // Reload the WebView.
2702 mainWebView.reload();
2705 case R.id.user_agent_safari_on_ios:
2706 // Update the user agent.
2707 mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[4]);
2709 // Reload the WebView.
2710 mainWebView.reload();
2713 case R.id.user_agent_firefox_on_linux:
2714 // Update the user agent.
2715 mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[5]);
2717 // Reload the WebView.
2718 mainWebView.reload();
2721 case R.id.user_agent_chromium_on_linux:
2722 // Update the user agent.
2723 mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[6]);
2725 // Reload the WebView.
2726 mainWebView.reload();
2729 case R.id.user_agent_firefox_on_windows:
2730 // Update the user agent.
2731 mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[7]);
2733 // Reload the WebView.
2734 mainWebView.reload();
2737 case R.id.user_agent_chrome_on_windows:
2738 // Update the user agent.
2739 mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[8]);
2741 // Reload the WebView.
2742 mainWebView.reload();
2745 case R.id.user_agent_edge_on_windows:
2746 // Update the user agent.
2747 mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[9]);
2749 // Reload the WebView.
2750 mainWebView.reload();
2753 case R.id.user_agent_internet_explorer_on_windows:
2754 // Update the user agent.
2755 mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[10]);
2757 // Reload the WebView.
2758 mainWebView.reload();
2761 case R.id.user_agent_safari_on_macos:
2762 // Update the user agent.
2763 mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[11]);
2765 // Reload the WebView.
2766 mainWebView.reload();
2769 case R.id.user_agent_custom:
2770 // Update the user agent.
2771 mainWebView.getSettings().setUserAgentString(defaultCustomUserAgentString);
2773 // Reload the WebView.
2774 mainWebView.reload();
2777 case R.id.font_size_twenty_five_percent:
2778 mainWebView.getSettings().setTextZoom(25);
2781 case R.id.font_size_fifty_percent:
2782 mainWebView.getSettings().setTextZoom(50);
2785 case R.id.font_size_seventy_five_percent:
2786 mainWebView.getSettings().setTextZoom(75);
2789 case R.id.font_size_one_hundred_percent:
2790 mainWebView.getSettings().setTextZoom(100);
2793 case R.id.font_size_one_hundred_twenty_five_percent:
2794 mainWebView.getSettings().setTextZoom(125);
2797 case R.id.font_size_one_hundred_fifty_percent:
2798 mainWebView.getSettings().setTextZoom(150);
2801 case R.id.font_size_one_hundred_seventy_five_percent:
2802 mainWebView.getSettings().setTextZoom(175);
2805 case R.id.font_size_two_hundred_percent:
2806 mainWebView.getSettings().setTextZoom(200);
2809 case R.id.swipe_to_refresh:
2810 // Toggle swipe to refresh.
2811 swipeRefreshLayout.setEnabled(!swipeRefreshLayout.isEnabled());
2814 case R.id.display_images:
2815 if (mainWebView.getSettings().getLoadsImagesAutomatically()) { // Images are currently loaded automatically.
2816 mainWebView.getSettings().setLoadsImagesAutomatically(false);
2817 mainWebView.reload();
2818 } else { // Images are not currently loaded automatically.
2819 mainWebView.getSettings().setLoadsImagesAutomatically(true);
2823 case R.id.night_mode:
2824 // Toggle night mode.
2825 nightMode = !nightMode;
2827 // Enable or disable JavaScript according to night mode, the global preference, and any domain settings.
2828 if (nightMode) { // Night mode is enabled. Enable JavaScript.
2829 // Update the global variable.
2830 javaScriptEnabled = true;
2831 } else if (domainSettingsApplied) { // Night mode is disabled and domain settings are applied. Set JavaScript according to the domain settings.
2832 // Get the JavaScript preference that was stored the last time domain settings were loaded.
2833 javaScriptEnabled = domainSettingsJavaScriptEnabled;
2834 } else { // Night mode is disabled and domain settings are not applied. Set JavaScript according to the global preference.
2835 // Get a handle for the shared preference.
2836 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
2838 // Get the JavaScript preference.
2839 javaScriptEnabled = sharedPreferences.getBoolean("javascript", false);
2842 // Apply the JavaScript setting to the WebView.
2843 mainWebView.getSettings().setJavaScriptEnabled(javaScriptEnabled);
2845 // Update the privacy icons.
2846 updatePrivacyIcons(false);
2848 // Reload the website.
2849 mainWebView.reload();
2853 // Get a `PrintManager` instance.
2854 PrintManager printManager = (PrintManager) getSystemService(Context.PRINT_SERVICE);
2856 // Convert `mainWebView` to `printDocumentAdapter`.
2857 PrintDocumentAdapter printDocumentAdapter = mainWebView.createPrintDocumentAdapter();
2859 // Remove the lint error below that `printManager` might be `null`.
2860 assert printManager != null;
2862 // Print the document. The print attributes are `null`.
2863 printManager.print(getString(R.string.privacy_browser_web_page), printDocumentAdapter, null);
2866 case R.id.find_on_page:
2867 // Hide the URL app bar.
2868 supportAppBar.setVisibility(View.GONE);
2870 // Show the Find on Page `RelativeLayout`.
2871 findOnPageLinearLayout.setVisibility(View.VISIBLE);
2873 // Display the keyboard. We have to wait 200 ms before running the command to work around a bug in Android.
2874 // http://stackoverflow.com/questions/5520085/android-show-softkeyboard-with-showsoftinput-is-not-working
2875 findOnPageEditText.postDelayed(() -> {
2876 // Set the focus on `findOnPageEditText`.
2877 findOnPageEditText.requestFocus();
2879 // Display the keyboard. `0` sets no input flags.
2880 inputMethodManager.showSoftInput(findOnPageEditText, 0);
2884 case R.id.add_to_homescreen:
2885 // Show the alert dialog.
2886 AppCompatDialogFragment createHomeScreenShortcutDialogFragment = new CreateHomeScreenShortcutDialog();
2887 createHomeScreenShortcutDialogFragment.show(getSupportFragmentManager(), getString(R.string.create_shortcut));
2889 //Everything else will be handled by the alert dialog and the associated listener below.
2892 case R.id.view_source:
2893 // Launch the View Source activity.
2894 Intent viewSourceIntent = new Intent(this, ViewSourceActivity.class);
2895 startActivity(viewSourceIntent);
2898 case R.id.share_url:
2899 // Setup the share string.
2900 String shareString = webViewTitle + " – " + urlTextBox.getText().toString();
2902 // Create the share intent.
2903 Intent shareIntent = new Intent(Intent.ACTION_SEND);
2904 shareIntent.putExtra(Intent.EXTRA_TEXT, shareString);
2905 shareIntent.setType("text/plain");
2908 startActivity(Intent.createChooser(shareIntent, getString(R.string.share_url)));
2911 case R.id.open_with_app:
2912 // Create the open with intent with `ACTION_VIEW`.
2913 Intent openWithAppIntent = new Intent(Intent.ACTION_VIEW);
2915 // Set the URI but not the MIME type. This should open all available apps.
2916 openWithAppIntent.setData(Uri.parse(formattedUrlString));
2918 // Flag the intent to open in a new task.
2919 openWithAppIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
2921 // Show the chooser.
2922 startActivity(Intent.createChooser(openWithAppIntent, getString(R.string.open_with)));
2925 case R.id.open_with_browser:
2926 // Create the open with intent with `ACTION_VIEW`.
2927 Intent openWithBrowserIntent = new Intent(Intent.ACTION_VIEW);
2929 // Set the URI and the MIME type. `"text/html"` should load browser options.
2930 openWithBrowserIntent.setDataAndType(Uri.parse(formattedUrlString), "text/html");
2932 // Flag the intent to open in a new task.
2933 openWithBrowserIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
2935 // Show the chooser.
2936 startActivity(Intent.createChooser(openWithBrowserIntent, getString(R.string.open_with)));
2939 case R.id.proxy_through_orbot:
2940 // Toggle the proxy through Orbot variable.
2941 proxyThroughOrbot = !proxyThroughOrbot;
2943 // Apply the proxy through Orbot settings.
2944 applyProxyThroughOrbot(true);
2948 if (menuItem.getTitle().equals(getString(R.string.refresh))) { // The refresh button was pushed.
2949 // Reload the WebView.
2950 mainWebView.reload();
2951 } else { // The stop button was pushed.
2952 // Stop the loading of the WebView.
2953 mainWebView.stopLoading();
2957 case R.id.ad_consent:
2958 // Display the ad consent dialog.
2959 DialogFragment adConsentDialogFragment = new AdConsentDialog();
2960 adConsentDialogFragment.show(getFragmentManager(), getString(R.string.ad_consent));
2964 // Don't consume the event.
2965 return super.onOptionsItemSelected(menuItem);
2969 // removeAllCookies is deprecated, but it is required for API < 21.
2970 @SuppressWarnings("deprecation")
2972 public boolean onNavigationItemSelected(@NonNull MenuItem menuItem) {
2973 int menuItemId = menuItem.getItemId();
2975 switch (menuItemId) {
2981 if (mainWebView.canGoBack()) {
2982 // Reset the formatted URL string so the page will load correctly if blocking of third-party requests is enabled.
2983 formattedUrlString = "";
2985 // Set `navigatingHistory` so that the domain settings are applied when the new URL is loaded.
2986 navigatingHistory = true;
2988 // Load the previous website in the history.
2989 mainWebView.goBack();
2994 if (mainWebView.canGoForward()) {
2995 // Reset the formatted URL string so the page will load correctly if blocking of third-party requests is enabled.
2996 formattedUrlString = "";
2998 // Set `navigatingHistory` so that the domain settings are applied when the new URL is loaded.
2999 navigatingHistory = true;
3001 // Load the next website in the history.
3002 mainWebView.goForward();
3007 // Get the `WebBackForwardList`.
3008 WebBackForwardList webBackForwardList = mainWebView.copyBackForwardList();
3010 // Show the `UrlHistoryDialog` `AlertDialog` and name this instance `R.string.history`. `this` is the `Context`.
3011 AppCompatDialogFragment urlHistoryDialogFragment = UrlHistoryDialog.loadBackForwardList(this, webBackForwardList);
3012 urlHistoryDialogFragment.show(getSupportFragmentManager(), getString(R.string.history));
3016 // Launch the requests activity.
3017 Intent requestsIntent = new Intent(this, RequestsActivity.class);
3018 startActivity(requestsIntent);
3021 case R.id.downloads:
3022 // Launch the system Download Manager.
3023 Intent downloadManagerIntent = new Intent(DownloadManager.ACTION_VIEW_DOWNLOADS);
3025 // Launch as a new task so that Download Manager and Privacy Browser show as separate windows in the recent tasks list.
3026 downloadManagerIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
3028 startActivity(downloadManagerIntent);
3032 // Set the flag to reapply the domain settings on restart when returning from Domain Settings.
3033 reapplyDomainSettingsOnRestart = true;
3034 currentDomainName = "";
3036 // Launch the domains activity.
3037 Intent domainsIntent = new Intent(this, DomainsActivity.class);
3038 startActivity(domainsIntent);
3042 // Set the flag to reapply app settings on restart when returning from Settings.
3043 reapplyAppSettingsOnRestart = true;
3045 // Set the flag to reapply the domain settings on restart when returning from Settings.
3046 reapplyDomainSettingsOnRestart = true;
3047 currentDomainName = "";
3049 // Launch the settings activity.
3050 Intent settingsIntent = new Intent(this, SettingsActivity.class);
3051 startActivity(settingsIntent);
3054 case R.id.import_export:
3055 // Launch the import/export activity.
3056 Intent importExportIntent = new Intent (this, ImportExportActivity.class);
3057 startActivity(importExportIntent);
3061 // Launch `GuideActivity`.
3062 Intent guideIntent = new Intent(this, GuideActivity.class);
3063 startActivity(guideIntent);
3067 // Launch `AboutActivity`.
3068 Intent aboutIntent = new Intent(this, AboutActivity.class);
3069 startActivity(aboutIntent);
3072 case R.id.clearAndExit:
3073 // Close the bookmarks cursor and database.
3074 bookmarksCursor.close();
3075 bookmarksDatabaseHelper.close();
3077 // Get a handle for `sharedPreferences`. `this` references the current context.
3078 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
3080 boolean clearEverything = sharedPreferences.getBoolean("clear_everything", true);
3083 if (clearEverything || sharedPreferences.getBoolean("clear_cookies", true)) {
3084 // The command to remove cookies changed slightly in API 21.
3085 if (Build.VERSION.SDK_INT >= 21) {
3086 cookieManager.removeAllCookies(null);
3088 cookieManager.removeAllCookie();
3091 // Manually delete the cookies database, as `CookieManager` sometimes will not flush its changes to disk before `System.exit(0)` is run.
3093 // We have to use two commands because `Runtime.exec()` does not like `*`.
3094 privacyBrowserRuntime.exec("rm -f " + privateDataDirectoryString + "/app_webview/Cookies");
3095 privacyBrowserRuntime.exec("rm -f " + privateDataDirectoryString + "/app_webview/Cookies-journal");
3096 } catch (IOException e) {
3097 // Do nothing if an error is thrown.
3101 // Clear DOM storage.
3102 if (clearEverything || sharedPreferences.getBoolean("clear_dom_storage", true)) {
3103 // Ask `WebStorage` to clear the DOM storage.
3104 WebStorage webStorage = WebStorage.getInstance();
3105 webStorage.deleteAllData();
3107 // Manually delete the DOM storage files and directories, as `WebStorage` sometimes will not flush its changes to disk before `System.exit(0)` is run.
3109 // A `String[]` must be used because the directory contains a space and `Runtime.exec` will otherwise not escape the string correctly.
3110 privacyBrowserRuntime.exec(new String[] {"rm", "-rf", privateDataDirectoryString + "/app_webview/Local Storage/"});
3112 // Multiple commands must be used because `Runtime.exec()` does not like `*`.
3113 privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/IndexedDB");
3114 privacyBrowserRuntime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager");
3115 privacyBrowserRuntime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager-journal");
3116 privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/databases");
3117 } catch (IOException e) {
3118 // Do nothing if an error is thrown.
3122 // Clear form data if the API < 26.
3123 if ((Build.VERSION.SDK_INT < 26) && (clearEverything || sharedPreferences.getBoolean("clear_form_data", true))) {
3124 WebViewDatabase webViewDatabase = WebViewDatabase.getInstance(this);
3125 webViewDatabase.clearFormData();
3127 // Manually delete the form data database, as `WebViewDatabase` sometimes will not flush its changes to disk before `System.exit(0)` is run.
3129 // We have to use a `String[]` because the database contains a space and `Runtime.exec` will not escape the string correctly otherwise.
3130 privacyBrowserRuntime.exec(new String[] {"rm", "-f", privateDataDirectoryString + "/app_webview/Web Data"});
3131 privacyBrowserRuntime.exec(new String[] {"rm", "-f", privateDataDirectoryString + "/app_webview/Web Data-journal"});
3132 } catch (IOException e) {
3133 // Do nothing if an error is thrown.
3138 if (clearEverything || sharedPreferences.getBoolean("clear_cache", true)) {
3139 // `true` includes disk files.
3140 mainWebView.clearCache(true);
3142 // Manually delete the cache directories.
3144 // Delete the main cache directory.
3145 privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/cache");
3147 // Delete the secondary `Service Worker` cache directory.
3148 // A `String[]` must be used because the directory contains a space and `Runtime.exec` will not escape the string correctly otherwise.
3149 privacyBrowserRuntime.exec(new String[] {"rm", "-rf", privateDataDirectoryString + "/app_webview/Service Worker/"});
3150 } catch (IOException e) {
3151 // Do nothing if an error is thrown.
3155 // Clear SSL certificate preferences.
3156 mainWebView.clearSslPreferences();
3158 // Clear the back/forward history.
3159 mainWebView.clearHistory();
3161 // Clear `formattedUrlString`.
3162 formattedUrlString = null;
3164 // Clear `customHeaders`.
3165 customHeaders.clear();
3167 // Detach all views from `mainWebViewRelativeLayout`.
3168 mainWebViewRelativeLayout.removeAllViews();
3170 // Destroy the internal state of `mainWebView`.
3171 mainWebView.destroy();
3173 // Manually delete the `app_webview` folder, which contains the cookies, DOM storage, form data, and `Service Worker` cache.
3174 // See `https://code.google.com/p/android/issues/detail?id=233826&thanks=233826&ts=1486670530`.
3175 if (clearEverything) {
3177 privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/app_webview");
3178 } catch (IOException e) {
3179 // Do nothing if an error is thrown.
3183 // Close Privacy Browser. `finishAndRemoveTask` also removes Privacy Browser from the recent app list.
3184 if (Build.VERSION.SDK_INT >= 21) {
3185 finishAndRemoveTask();
3190 // Remove the terminated program from RAM. The status code is `0`.
3195 // Close the navigation drawer.
3196 drawerLayout.closeDrawer(GravityCompat.START);
3201 public void onPostCreate(Bundle savedInstanceState) {
3202 super.onPostCreate(savedInstanceState);
3204 // Sync the state of the DrawerToggle after onRestoreInstanceState has finished.
3205 drawerToggle.syncState();
3209 public void onConfigurationChanged(Configuration newConfig) {
3210 super.onConfigurationChanged(newConfig);
3212 // Get the status bar pixel size.
3213 int statusBarResourceId = getResources().getIdentifier("status_bar_height", "dimen", "android");
3214 int statusBarPixelSize = getResources().getDimensionPixelSize(statusBarResourceId);
3216 // Get the resource density.
3217 float screenDensity = getResources().getDisplayMetrics().density;
3219 // Recalculate the drawer header padding.
3220 drawerHeaderPaddingLeftAndRight = (int) (15 * screenDensity);
3221 drawerHeaderPaddingTop = statusBarPixelSize + (int) (4 * screenDensity);
3222 drawerHeaderPaddingBottom = (int) (8 * screenDensity);
3224 // Reload the ad for the free flavor if not in full screen mode.
3225 if (BuildConfig.FLAVOR.contentEquals("free") && !inFullScreenBrowsingMode) {
3226 // Reload the ad. The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations.
3227 AdHelper.loadAd(findViewById(R.id.adview), getApplicationContext(), getString(R.string.ad_unit_id));
3230 // `invalidateOptionsMenu` should recalculate the number of action buttons from the menu to display on the app bar, but it doesn't because of the this bug:
3231 // https://code.google.com/p/android/issues/detail?id=20493#c8
3232 // ActivityCompat.invalidateOptionsMenu(this);
3236 public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.ContextMenuInfo menuInfo) {
3237 // Store the `HitTestResult`.
3238 final WebView.HitTestResult hitTestResult = mainWebView.getHitTestResult();
3241 final String imageUrl;
3242 final String linkUrl;
3244 // Get a handle for the `ClipboardManager`.
3245 final ClipboardManager clipboardManager = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
3247 // Remove the lint errors below that `clipboardManager` might be `null`.
3248 assert clipboardManager != null;
3250 switch (hitTestResult.getType()) {
3251 // `SRC_ANCHOR_TYPE` is a link.
3252 case WebView.HitTestResult.SRC_ANCHOR_TYPE:
3253 // Get the target URL.
3254 linkUrl = hitTestResult.getExtra();
3256 // Set the target URL as the title of the `ContextMenu`.
3257 menu.setHeaderTitle(linkUrl);
3259 // Add a Load URL entry.
3260 menu.add(R.string.load_url).setOnMenuItemClickListener((MenuItem item) -> {
3265 // Add a Copy URL entry.
3266 menu.add(R.string.copy_url).setOnMenuItemClickListener((MenuItem item) -> {
3267 // Save the link URL in a `ClipData`.
3268 ClipData srcAnchorTypeClipData = ClipData.newPlainText(getString(R.string.url), linkUrl);
3270 // Set the `ClipData` as the clipboard's primary clip.
3271 clipboardManager.setPrimaryClip(srcAnchorTypeClipData);
3275 // Add a Download URL entry.
3276 menu.add(R.string.download_url).setOnMenuItemClickListener((MenuItem item) -> {
3277 // Check if the download should be processed by an external app.
3278 if (downloadWithExternalApp) { // Download with an external app.
3279 openUrlWithExternalApp(linkUrl);
3280 } else { // Download with Android's download manager.
3281 // Check to see if the storage permission has already been granted.
3282 if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) { // The storage permission needs to be requested.
3283 // Store the variables for future use by `onRequestPermissionsResult()`.
3284 downloadUrl = linkUrl;
3285 downloadContentDisposition = "none";
3286 downloadContentLength = -1;
3288 // Show a dialog if the user has previously denied the permission.
3289 if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) { // Show a dialog explaining the request first.
3290 // Instantiate the download location permission alert dialog and set the download type to DOWNLOAD_FILE.
3291 DialogFragment downloadLocationPermissionDialogFragment = DownloadLocationPermissionDialog.downloadType(DownloadLocationPermissionDialog.DOWNLOAD_FILE);
3293 // Show the download location permission alert dialog. The permission will be requested when the the dialog is closed.
3294 downloadLocationPermissionDialogFragment.show(getFragmentManager(), getString(R.string.download_location));
3295 } else { // Show the permission request directly.
3296 // Request the permission. The download dialog will be launched by `onRequestPermissionResult()`.
3297 ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_FILE_REQUEST_CODE);
3299 } else { // The storage permission has already been granted.
3300 // Get a handle for the download file alert dialog.
3301 AppCompatDialogFragment downloadFileDialogFragment = DownloadFileDialog.fromUrl(linkUrl, "none", -1);
3303 // Show the download file alert dialog.
3304 downloadFileDialogFragment.show(getSupportFragmentManager(), getString(R.string.download));
3310 // Add a `Cancel` entry, which by default closes the `ContextMenu`.
3311 menu.add(R.string.cancel);
3314 case WebView.HitTestResult.EMAIL_TYPE:
3315 // Get the target URL.
3316 linkUrl = hitTestResult.getExtra();
3318 // Set the target URL as the title of the `ContextMenu`.
3319 menu.setHeaderTitle(linkUrl);
3321 // Add a `Write Email` entry.
3322 menu.add(R.string.write_email).setOnMenuItemClickListener(item -> {
3323 // Use `ACTION_SENDTO` instead of `ACTION_SEND` so that only email programs are launched.
3324 Intent emailIntent = new Intent(Intent.ACTION_SENDTO);
3326 // Parse the url and set it as the data for the `Intent`.
3327 emailIntent.setData(Uri.parse("mailto:" + linkUrl));
3329 // `FLAG_ACTIVITY_NEW_TASK` opens the email program in a new task instead as part of Privacy Browser.
3330 emailIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
3333 startActivity(emailIntent);
3337 // Add a `Copy Email Address` entry.
3338 menu.add(R.string.copy_email_address).setOnMenuItemClickListener(item -> {
3339 // Save the email address in a `ClipData`.
3340 ClipData srcEmailTypeClipData = ClipData.newPlainText(getString(R.string.email_address), linkUrl);
3342 // Set the `ClipData` as the clipboard's primary clip.
3343 clipboardManager.setPrimaryClip(srcEmailTypeClipData);
3347 // Add a `Cancel` entry, which by default closes the `ContextMenu`.
3348 menu.add(R.string.cancel);
3351 // `IMAGE_TYPE` is an image.
3352 case WebView.HitTestResult.IMAGE_TYPE:
3353 // Get the image URL.
3354 imageUrl = hitTestResult.getExtra();
3356 // Set the image URL as the title of the `ContextMenu`.
3357 menu.setHeaderTitle(imageUrl);
3359 // Add a `View Image` entry.
3360 menu.add(R.string.view_image).setOnMenuItemClickListener(item -> {
3365 // Add a `Download Image` entry.
3366 menu.add(R.string.download_image).setOnMenuItemClickListener((MenuItem item) -> {
3367 // Check if the download should be processed by an external app.
3368 if (downloadWithExternalApp) { // Download with an external app.
3369 openUrlWithExternalApp(imageUrl);
3370 } else { // Download with Android's download manager.
3371 // Check to see if the storage permission has already been granted.
3372 if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) { // The storage permission needs to be requested.
3373 // Store the image URL for use by `onRequestPermissionResult()`.
3374 downloadImageUrl = imageUrl;
3376 // Show a dialog if the user has previously denied the permission.
3377 if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) { // Show a dialog explaining the request first.
3378 // Instantiate the download location permission alert dialog and set the download type to DOWNLOAD_IMAGE.
3379 DialogFragment downloadLocationPermissionDialogFragment = DownloadLocationPermissionDialog.downloadType(DownloadLocationPermissionDialog.DOWNLOAD_IMAGE);
3381 // Show the download location permission alert dialog. The permission will be requested when the dialog is closed.
3382 downloadLocationPermissionDialogFragment.show(getFragmentManager(), getString(R.string.download_location));
3383 } else { // Show the permission request directly.
3384 // Request the permission. The download dialog will be launched by `onRequestPermissionResult().
3385 ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_IMAGE_REQUEST_CODE);
3387 } else { // The storage permission has already been granted.
3388 // Get a handle for the download image alert dialog.
3389 AppCompatDialogFragment downloadImageDialogFragment = DownloadImageDialog.imageUrl(imageUrl);
3391 // Show the download image alert dialog.
3392 downloadImageDialogFragment.show(getSupportFragmentManager(), getString(R.string.download));
3398 // Add a `Copy URL` entry.
3399 menu.add(R.string.copy_url).setOnMenuItemClickListener(item -> {
3400 // Save the image URL in a `ClipData`.
3401 ClipData srcImageTypeClipData = ClipData.newPlainText(getString(R.string.url), imageUrl);
3403 // Set the `ClipData` as the clipboard's primary clip.
3404 clipboardManager.setPrimaryClip(srcImageTypeClipData);
3408 // Add a `Cancel` entry, which by default closes the `ContextMenu`.
3409 menu.add(R.string.cancel);
3413 // `SRC_IMAGE_ANCHOR_TYPE` is an image that is also a link.
3414 case WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE:
3415 // Get the image URL.
3416 imageUrl = hitTestResult.getExtra();
3418 // Set the image URL as the title of the `ContextMenu`.
3419 menu.setHeaderTitle(imageUrl);
3421 // Add a `View Image` entry.
3422 menu.add(R.string.view_image).setOnMenuItemClickListener(item -> {
3427 // Add a `Download Image` entry.
3428 menu.add(R.string.download_image).setOnMenuItemClickListener((MenuItem item) -> {
3429 // Check if the download should be processed by an external app.
3430 if (downloadWithExternalApp) { // Download with an external app.
3431 openUrlWithExternalApp(imageUrl);
3432 } else { // Download with Android's download manager.
3433 // Check to see if the storage permission has already been granted.
3434 if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) { // The storage permission needs to be requested.
3435 // Store the image URL for use by `onRequestPermissionResult()`.
3436 downloadImageUrl = imageUrl;
3438 // Show a dialog if the user has previously denied the permission.
3439 if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) { // Show a dialog explaining the request first.
3440 // Instantiate the download location permission alert dialog and set the download type to DOWNLOAD_IMAGE.
3441 DialogFragment downloadLocationPermissionDialogFragment = DownloadLocationPermissionDialog.downloadType(DownloadLocationPermissionDialog.DOWNLOAD_IMAGE);
3443 // Show the download location permission alert dialog. The permission will be requested when the dialog is closed.
3444 downloadLocationPermissionDialogFragment.show(getFragmentManager(), getString(R.string.download_location));
3445 } else { // Show the permission request directly.
3446 // Request the permission. The download dialog will be launched by `onRequestPermissionResult().
3447 ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_IMAGE_REQUEST_CODE);
3449 } else { // The storage permission has already been granted.
3450 // Get a handle for the download image alert dialog.
3451 AppCompatDialogFragment downloadImageDialogFragment = DownloadImageDialog.imageUrl(imageUrl);
3453 // Show the download image alert dialog.
3454 downloadImageDialogFragment.show(getSupportFragmentManager(), getString(R.string.download));
3460 // Add a `Copy URL` entry.
3461 menu.add(R.string.copy_url).setOnMenuItemClickListener(item -> {
3462 // Save the image URL in a `ClipData`.
3463 ClipData srcImageAnchorTypeClipData = ClipData.newPlainText(getString(R.string.url), imageUrl);
3465 // Set the `ClipData` as the clipboard's primary clip.
3466 clipboardManager.setPrimaryClip(srcImageAnchorTypeClipData);
3470 // Add a `Cancel` entry, which by default closes the `ContextMenu`.
3471 menu.add(R.string.cancel);
3477 public void onCreateBookmark(AppCompatDialogFragment dialogFragment) {
3478 // Get the `EditTexts` from the `dialogFragment`.
3479 EditText createBookmarkNameEditText = dialogFragment.getDialog().findViewById(R.id.create_bookmark_name_edittext);
3480 EditText createBookmarkUrlEditText = dialogFragment.getDialog().findViewById(R.id.create_bookmark_url_edittext);
3482 // Extract the strings from the `EditTexts`.
3483 String bookmarkNameString = createBookmarkNameEditText.getText().toString();
3484 String bookmarkUrlString = createBookmarkUrlEditText.getText().toString();
3486 // Convert the favoriteIcon Bitmap to a byte array. `0` is for lossless compression (the only option for a PNG).
3487 ByteArrayOutputStream favoriteIconByteArrayOutputStream = new ByteArrayOutputStream();
3488 favoriteIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, favoriteIconByteArrayOutputStream);
3489 byte[] favoriteIconByteArray = favoriteIconByteArrayOutputStream.toByteArray();
3491 // Display the new bookmark below the current items in the (0 indexed) list.
3492 int newBookmarkDisplayOrder = bookmarksListView.getCount();
3494 // Create the bookmark.
3495 bookmarksDatabaseHelper.createBookmark(bookmarkNameString, bookmarkUrlString, currentBookmarksFolder, newBookmarkDisplayOrder, favoriteIconByteArray);
3497 // Update the bookmarks cursor with the current contents of this folder.
3498 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder);
3500 // Update the `ListView`.
3501 bookmarksCursorAdapter.changeCursor(bookmarksCursor);
3503 // Scroll to the new bookmark.
3504 bookmarksListView.setSelection(newBookmarkDisplayOrder);
3508 public void onCreateBookmarkFolder(AppCompatDialogFragment dialogFragment) {
3509 // Get handles for the views in `dialogFragment`.
3510 EditText createFolderNameEditText = dialogFragment.getDialog().findViewById(R.id.create_folder_name_edittext);
3511 RadioButton defaultFolderIconRadioButton = dialogFragment.getDialog().findViewById(R.id.create_folder_default_icon_radiobutton);
3512 ImageView folderIconImageView = dialogFragment.getDialog().findViewById(R.id.create_folder_default_icon);
3514 // Get new folder name string.
3515 String folderNameString = createFolderNameEditText.getText().toString();
3517 // Get the new folder icon `Bitmap`.
3518 Bitmap folderIconBitmap;
3519 if (defaultFolderIconRadioButton.isChecked()) { // Use the default folder icon.
3520 // Get the default folder icon and convert it to a `Bitmap`.
3521 Drawable folderIconDrawable = folderIconImageView.getDrawable();
3522 BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable;
3523 folderIconBitmap = folderIconBitmapDrawable.getBitmap();
3524 } else { // Use the `WebView` favorite icon.
3525 folderIconBitmap = favoriteIconBitmap;
3528 // Convert `folderIconBitmap` to a byte array. `0` is for lossless compression (the only option for a PNG).
3529 ByteArrayOutputStream folderIconByteArrayOutputStream = new ByteArrayOutputStream();
3530 folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, folderIconByteArrayOutputStream);
3531 byte[] folderIconByteArray = folderIconByteArrayOutputStream.toByteArray();
3533 // Move all the bookmarks down one in the display order.
3534 for (int i = 0; i < bookmarksListView.getCount(); i++) {
3535 int databaseId = (int) bookmarksListView.getItemIdAtPosition(i);
3536 bookmarksDatabaseHelper.updateDisplayOrder(databaseId, i + 1);
3539 // Create the folder, which will be placed at the top of the `ListView`.
3540 bookmarksDatabaseHelper.createFolder(folderNameString, currentBookmarksFolder, folderIconByteArray);
3542 // Update the bookmarks cursor with the current contents of this folder.
3543 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder);
3545 // Update the `ListView`.
3546 bookmarksCursorAdapter.changeCursor(bookmarksCursor);
3548 // Scroll to the new folder.
3549 bookmarksListView.setSelection(0);
3553 public void onCreateHomeScreenShortcut(AppCompatDialogFragment dialogFragment) {
3554 // Get the shortcut name.
3555 EditText shortcutNameEditText = dialogFragment.getDialog().findViewById(R.id.shortcut_name_edittext);
3556 String shortcutNameString = shortcutNameEditText.getText().toString();
3558 // Convert the favorite icon bitmap to an `Icon`. `IconCompat` is required until API >= 26.
3559 IconCompat favoriteIcon = IconCompat.createWithBitmap(favoriteIconBitmap);
3561 // Setup the shortcut intent.
3562 Intent shortcutIntent = new Intent(Intent.ACTION_VIEW);
3563 shortcutIntent.setData(Uri.parse(formattedUrlString));
3565 // Create a shortcut info builder. The shortcut name becomes the shortcut ID.
3566 ShortcutInfoCompat.Builder shortcutInfoBuilder = new ShortcutInfoCompat.Builder(this, shortcutNameString);
3568 // Add the required fields to the shortcut info builder.
3569 shortcutInfoBuilder.setIcon(favoriteIcon);
3570 shortcutInfoBuilder.setIntent(shortcutIntent);
3571 shortcutInfoBuilder.setShortLabel(shortcutNameString);
3573 // Request the pin. `ShortcutManagerCompat` can be switched to `ShortcutManager` once API >= 26.
3574 ShortcutManagerCompat.requestPinShortcut(this, shortcutInfoBuilder.build(), null);
3578 public void onCloseDownloadLocationPermissionDialog(int downloadType) {
3579 switch (downloadType) {
3580 case DownloadLocationPermissionDialog.DOWNLOAD_FILE:
3581 // Request the WRITE_EXTERNAL_STORAGE permission with a file request code.
3582 ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_FILE_REQUEST_CODE);
3585 case DownloadLocationPermissionDialog.DOWNLOAD_IMAGE:
3586 // Request the WRITE_EXTERNAL_STORAGE permission with an image request code.
3587 ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_IMAGE_REQUEST_CODE);
3593 public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
3594 switch (requestCode) {
3595 case DOWNLOAD_FILE_REQUEST_CODE:
3596 // Show the download file alert dialog. When the dialog closes, the correct command will be used based on the permission status.
3597 AppCompatDialogFragment downloadFileDialogFragment = DownloadFileDialog.fromUrl(downloadUrl, downloadContentDisposition, downloadContentLength);
3599 // On API 23, displaying the fragment must be delayed or the app will crash.
3600 if (Build.VERSION.SDK_INT == 23) {
3601 new Handler().postDelayed(() -> downloadFileDialogFragment.show(getSupportFragmentManager(), getString(R.string.download)), 500);
3603 downloadFileDialogFragment.show(getSupportFragmentManager(), getString(R.string.download));
3606 // Reset the download variables.
3608 downloadContentDisposition = "";
3609 downloadContentLength = 0;
3612 case DOWNLOAD_IMAGE_REQUEST_CODE:
3613 // Show the download image alert dialog. When the dialog closes, the correct command will be used based on the permission status.
3614 AppCompatDialogFragment downloadImageDialogFragment = DownloadImageDialog.imageUrl(downloadImageUrl);
3616 // On API 23, displaying the fragment must be delayed or the app will crash.
3617 if (Build.VERSION.SDK_INT == 23) {
3618 new Handler().postDelayed(() -> downloadImageDialogFragment.show(getSupportFragmentManager(), getString(R.string.download)), 500);
3620 downloadImageDialogFragment.show(getSupportFragmentManager(), getString(R.string.download));
3623 // Reset the image URL variable.
3624 downloadImageUrl = "";
3630 public void onDownloadImage(AppCompatDialogFragment dialogFragment, String imageUrl) {
3631 // Download the image if it has an HTTP or HTTPS URI.
3632 if (imageUrl.startsWith("http")) {
3633 // Get a handle for the system `DOWNLOAD_SERVICE`.
3634 DownloadManager downloadManager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
3636 // Parse `imageUrl`.
3637 DownloadManager.Request downloadRequest = new DownloadManager.Request(Uri.parse(imageUrl));
3639 // Pass cookies to download manager if cookies are enabled. This is required to download images from websites that require a login.
3640 // Code contributed 2017 Hendrik Knackstedt. Copyright assigned to Soren Stoutner <soren@stoutner.com>.
3641 if (firstPartyCookiesEnabled) {
3642 // Get the cookies for `imageUrl`.
3643 String cookies = cookieManager.getCookie(imageUrl);
3645 // Add the cookies to `downloadRequest`. In the HTTP request header, cookies are named `Cookie`.
3646 downloadRequest.addRequestHeader("Cookie", cookies);
3649 // Get the file name from the dialog fragment.
3650 EditText downloadImageNameEditText = dialogFragment.getDialog().findViewById(R.id.download_image_name);
3651 String imageName = downloadImageNameEditText.getText().toString();
3653 // Specify the download location.
3654 if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { // External write permission granted.
3655 // Download to the public download directory.
3656 downloadRequest.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, imageName);
3657 } else { // External write permission denied.
3658 // Download to the app's external download directory.
3659 downloadRequest.setDestinationInExternalFilesDir(this, Environment.DIRECTORY_DOWNLOADS, imageName);
3662 // Allow `MediaScanner` to index the download if it is a media file.
3663 downloadRequest.allowScanningByMediaScanner();
3665 // Add the URL as the description for the download.
3666 downloadRequest.setDescription(imageUrl);
3668 // Show the download notification after the download is completed.
3669 downloadRequest.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
3671 // Remove the lint warning below that `downloadManager` might be `null`.
3672 assert downloadManager != null;
3674 // Initiate the download.
3675 downloadManager.enqueue(downloadRequest);
3676 } else { // The image is not an HTTP or HTTPS URI.
3677 Snackbar.make(mainWebView, R.string.cannot_download_image, Snackbar.LENGTH_INDEFINITE).show();
3682 public void onDownloadFile(AppCompatDialogFragment dialogFragment, String downloadUrl) {
3683 // Download the file if it has an HTTP or HTTPS URI.
3684 if (downloadUrl.startsWith("http")) {
3685 // Get a handle for the system `DOWNLOAD_SERVICE`.
3686 DownloadManager downloadManager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
3688 // Parse `downloadUrl`.
3689 DownloadManager.Request downloadRequest = new DownloadManager.Request(Uri.parse(downloadUrl));
3691 // Pass cookies to download manager if cookies are enabled. This is required to download files from websites that require a login.
3692 // Code contributed 2017 Hendrik Knackstedt. Copyright assigned to Soren Stoutner <soren@stoutner.com>.
3693 if (firstPartyCookiesEnabled) {
3694 // Get the cookies for `downloadUrl`.
3695 String cookies = cookieManager.getCookie(downloadUrl);
3697 // Add the cookies to `downloadRequest`. In the HTTP request header, cookies are named `Cookie`.
3698 downloadRequest.addRequestHeader("Cookie", cookies);
3701 // Get the file name from the dialog fragment.
3702 EditText downloadFileNameEditText = dialogFragment.getDialog().findViewById(R.id.download_file_name);
3703 String fileName = downloadFileNameEditText.getText().toString();
3705 // Specify the download location.
3706 if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { // External write permission granted.
3707 // Download to the public download directory.
3708 downloadRequest.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, fileName);
3709 } else { // External write permission denied.
3710 // Download to the app's external download directory.
3711 downloadRequest.setDestinationInExternalFilesDir(this, Environment.DIRECTORY_DOWNLOADS, fileName);
3714 // Allow `MediaScanner` to index the download if it is a media file.
3715 downloadRequest.allowScanningByMediaScanner();
3717 // Add the URL as the description for the download.
3718 downloadRequest.setDescription(downloadUrl);
3720 // Show the download notification after the download is completed.
3721 downloadRequest.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
3723 // Remove the lint warning below that `downloadManager` might be `null`.
3724 assert downloadManager != null;
3726 // Initiate the download.
3727 downloadManager.enqueue(downloadRequest);
3728 } else { // The download is not an HTTP or HTTPS URI.
3729 Snackbar.make(mainWebView, R.string.cannot_download_file, Snackbar.LENGTH_INDEFINITE).show();
3734 public void onSaveBookmark(AppCompatDialogFragment dialogFragment, int selectedBookmarkDatabaseId) {
3735 // Get handles for the views from `dialogFragment`.
3736 EditText editBookmarkNameEditText = dialogFragment.getDialog().findViewById(R.id.edit_bookmark_name_edittext);
3737 EditText editBookmarkUrlEditText = dialogFragment.getDialog().findViewById(R.id.edit_bookmark_url_edittext);
3738 RadioButton currentBookmarkIconRadioButton = dialogFragment.getDialog().findViewById(R.id.edit_bookmark_current_icon_radiobutton);
3740 // Store the bookmark strings.
3741 String bookmarkNameString = editBookmarkNameEditText.getText().toString();
3742 String bookmarkUrlString = editBookmarkUrlEditText.getText().toString();
3744 // Update the bookmark.
3745 if (currentBookmarkIconRadioButton.isChecked()) { // Update the bookmark without changing the favorite icon.
3746 bookmarksDatabaseHelper.updateBookmark(selectedBookmarkDatabaseId, bookmarkNameString, bookmarkUrlString);
3747 } else { // Update the bookmark using the `WebView` favorite icon.
3748 // Convert the favorite icon to a byte array. `0` is for lossless compression (the only option for a PNG).
3749 ByteArrayOutputStream newFavoriteIconByteArrayOutputStream = new ByteArrayOutputStream();
3750 favoriteIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, newFavoriteIconByteArrayOutputStream);
3751 byte[] newFavoriteIconByteArray = newFavoriteIconByteArrayOutputStream.toByteArray();
3753 // Update the bookmark and the favorite icon.
3754 bookmarksDatabaseHelper.updateBookmark(selectedBookmarkDatabaseId, bookmarkNameString, bookmarkUrlString, newFavoriteIconByteArray);
3757 // Update the bookmarks cursor with the current contents of this folder.
3758 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder);
3760 // Update the `ListView`.
3761 bookmarksCursorAdapter.changeCursor(bookmarksCursor);
3765 public void onSaveBookmarkFolder(AppCompatDialogFragment dialogFragment, int selectedFolderDatabaseId) {
3766 // Get handles for the views from `dialogFragment`.
3767 EditText editFolderNameEditText = dialogFragment.getDialog().findViewById(R.id.edit_folder_name_edittext);
3768 RadioButton currentFolderIconRadioButton = dialogFragment.getDialog().findViewById(R.id.edit_folder_current_icon_radiobutton);
3769 RadioButton defaultFolderIconRadioButton = dialogFragment.getDialog().findViewById(R.id.edit_folder_default_icon_radiobutton);
3770 ImageView folderIconImageView = dialogFragment.getDialog().findViewById(R.id.edit_folder_default_icon_imageview);
3772 // Get the new folder name.
3773 String newFolderNameString = editFolderNameEditText.getText().toString();
3775 // Check if the favorite icon has changed.
3776 if (currentFolderIconRadioButton.isChecked()) { // Only the name has changed.
3777 // Update the name in the database.
3778 bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, oldFolderNameString, newFolderNameString);
3779 } else if (!currentFolderIconRadioButton.isChecked() && newFolderNameString.equals(oldFolderNameString)) { // Only the icon has changed.
3780 // Get the new folder icon `Bitmap`.
3781 Bitmap folderIconBitmap;
3782 if (defaultFolderIconRadioButton.isChecked()) {
3783 // Get the default folder icon and convert it to a `Bitmap`.
3784 Drawable folderIconDrawable = folderIconImageView.getDrawable();
3785 BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable;
3786 folderIconBitmap = folderIconBitmapDrawable.getBitmap();
3787 } else { // Use the `WebView` favorite icon.
3788 folderIconBitmap = favoriteIconBitmap;
3791 // Convert the folder `Bitmap` to a byte array. `0` is for lossless compression (the only option for a PNG).
3792 ByteArrayOutputStream folderIconByteArrayOutputStream = new ByteArrayOutputStream();
3793 folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, folderIconByteArrayOutputStream);
3794 byte[] folderIconByteArray = folderIconByteArrayOutputStream.toByteArray();
3796 // Update the folder icon in the database.
3797 bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, folderIconByteArray);
3798 } else { // The folder icon and the name have changed.
3799 // Get the new folder icon `Bitmap`.
3800 Bitmap folderIconBitmap;
3801 if (defaultFolderIconRadioButton.isChecked()) {
3802 // Get the default folder icon and convert it to a `Bitmap`.
3803 Drawable folderIconDrawable = folderIconImageView.getDrawable();
3804 BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable;
3805 folderIconBitmap = folderIconBitmapDrawable.getBitmap();
3806 } else { // Use the `WebView` favorite icon.
3807 folderIconBitmap = MainWebViewActivity.favoriteIconBitmap;
3810 // Convert the folder `Bitmap` to a byte array. `0` is for lossless compression (the only option for a PNG).
3811 ByteArrayOutputStream folderIconByteArrayOutputStream = new ByteArrayOutputStream();
3812 folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, folderIconByteArrayOutputStream);
3813 byte[] folderIconByteArray = folderIconByteArrayOutputStream.toByteArray();
3815 // Update the folder name and icon in the database.
3816 bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, oldFolderNameString, newFolderNameString, folderIconByteArray);
3819 // Update the bookmarks cursor with the current contents of this folder.
3820 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder);
3822 // Update the `ListView`.
3823 bookmarksCursorAdapter.changeCursor(bookmarksCursor);
3827 public void onHttpAuthenticationCancel() {
3828 // Cancel the `HttpAuthHandler`.
3829 httpAuthHandler.cancel();
3833 public void onHttpAuthenticationProceed(AppCompatDialogFragment dialogFragment) {
3834 // Get handles for the `EditTexts`.
3835 EditText usernameEditText = dialogFragment.getDialog().findViewById(R.id.http_authentication_username);
3836 EditText passwordEditText = dialogFragment.getDialog().findViewById(R.id.http_authentication_password);
3838 // Proceed with the HTTP authentication.
3839 httpAuthHandler.proceed(usernameEditText.getText().toString(), passwordEditText.getText().toString());
3842 public void viewSslCertificate(View view) {
3843 // Show the `ViewSslCertificateDialog` `AlertDialog` and name this instance `@string/view_ssl_certificate`.
3844 DialogFragment viewSslCertificateDialogFragment = new ViewSslCertificateDialog();
3845 viewSslCertificateDialogFragment.show(getFragmentManager(), getString(R.string.view_ssl_certificate));
3849 public void onSslErrorCancel() {
3850 sslErrorHandler.cancel();
3854 public void onSslErrorProceed() {
3855 sslErrorHandler.proceed();
3859 public void onSslMismatchBack() {
3860 if (mainWebView.canGoBack()) { // There is a back page in the history.
3861 // Reset the formatted URL string so the page will load correctly if blocking of third-party requests is enabled.
3862 formattedUrlString = "";
3864 // Set `navigatingHistory` so that the domain settings are applied when the new URL is loaded.
3865 navigatingHistory = true;
3868 mainWebView.goBack();
3869 } else { // There are no pages to go back to.
3870 // Load a blank page
3876 public void onSslMismatchProceed() {
3877 // Do not check the pinned SSL certificate for this domain again until the domain changes.
3878 ignorePinnedSslCertificate = true;
3882 public void onUrlHistoryEntrySelected(int moveBackOrForwardSteps) {
3883 // Reset the formatted URL string so the page will load correctly if blocking of third-party requests is enabled.
3884 formattedUrlString = "";
3886 // Set `navigatingHistory` so that the domain settings are applied when the new URL is loaded.
3887 navigatingHistory = true;
3889 // Load the history entry.
3890 mainWebView.goBackOrForward(moveBackOrForwardSteps);
3894 public void onClearHistory() {
3895 // Clear the history.
3896 mainWebView.clearHistory();
3899 // Override `onBackPressed` to handle the navigation drawer and `mainWebView`.
3901 public void onBackPressed() {
3902 if (drawerLayout.isDrawerVisible(GravityCompat.START)) { // The navigation drawer is open.
3903 // Close the navigation drawer.
3904 drawerLayout.closeDrawer(GravityCompat.START);
3905 } else if (drawerLayout.isDrawerVisible(GravityCompat.END)){ // The bookmarks drawer is open.
3906 if (currentBookmarksFolder.isEmpty()) { // The home folder is displayed.
3907 // close the bookmarks drawer.
3908 drawerLayout.closeDrawer(GravityCompat.END);
3909 } else { // A subfolder is displayed.
3910 // Place the former parent folder in `currentFolder`.
3911 currentBookmarksFolder = bookmarksDatabaseHelper.getParentFolderName(currentBookmarksFolder);
3913 // Load the new folder.
3914 loadBookmarksFolder();
3917 } else if (mainWebView.canGoBack()) { // There is at least one item in the `WebView` history.
3918 // Reset the formatted URL string so the page will load correctly if blocking of third-party requests is enabled.
3919 formattedUrlString = "";
3921 // Set `navigatingHistory` so that the domain settings are applied when the new URL is loaded.
3922 navigatingHistory = true;
3925 mainWebView.goBack();
3926 } else { // There isn't anything to do in Privacy Browser.
3927 // Pass `onBackPressed()` to the system.
3928 super.onBackPressed();
3932 // Process the results of an upload file chooser. Currently there is only one `startActivityForResult` in this activity, so the request code, used to differentiate them, is ignored.
3934 public void onActivityResult(int requestCode, int resultCode, Intent data) {
3935 // File uploads only work on API >= 21.
3936 if (Build.VERSION.SDK_INT >= 21) {
3937 // Pass the file to the WebView.
3938 fileChooserCallback.onReceiveValue(WebChromeClient.FileChooserParams.parseResult(resultCode, data));
3942 private void loadUrlFromTextBox() {
3943 // Get the text from urlTextBox and convert it to a string. trim() removes white spaces from the beginning and end of the string.
3944 String unformattedUrlString = urlTextBox.getText().toString().trim();
3946 // Check to see if `unformattedUrlString` is a valid URL. Otherwise, convert it into a search.
3947 if (unformattedUrlString.startsWith("content://")) {
3948 // Load the entire content URL.
3949 formattedUrlString = unformattedUrlString;
3950 } else if (Patterns.WEB_URL.matcher(unformattedUrlString).matches() || unformattedUrlString.startsWith("http://") || unformattedUrlString.startsWith("https://")
3951 || unformattedUrlString.startsWith("file://")) {
3952 // Add `https://` at the beginning if there is no protocol. Otherwise the app will segfault.
3953 if (!unformattedUrlString.startsWith("http") && !unformattedUrlString.startsWith("file://") && !unformattedUrlString.startsWith("content://")) {
3954 unformattedUrlString = "https://" + unformattedUrlString;
3957 // Initialize `unformattedUrl`.
3958 URL unformattedUrl = null;
3960 // Convert `unformattedUrlString` to a `URL`, then to a `URI`, and then back to a `String`, which sanitizes the input and adds in any missing components.
3962 unformattedUrl = new URL(unformattedUrlString);
3963 } catch (MalformedURLException e) {
3964 e.printStackTrace();
3967 // The ternary operator (? :) makes sure that a null pointer exception is not thrown, which would happen if `.get` was called on a `null` value.
3968 String scheme = unformattedUrl != null ? unformattedUrl.getProtocol() : null;
3969 String authority = unformattedUrl != null ? unformattedUrl.getAuthority() : null;
3970 String path = unformattedUrl != null ? unformattedUrl.getPath() : null;
3971 String query = unformattedUrl != null ? unformattedUrl.getQuery() : null;
3972 String fragment = unformattedUrl != null ? unformattedUrl.getRef() : null;
3975 Uri.Builder formattedUri = new Uri.Builder();
3976 formattedUri.scheme(scheme).authority(authority).path(path).query(query).fragment(fragment);
3978 // Decode `formattedUri` as a `String` in `UTF-8`.
3980 formattedUrlString = URLDecoder.decode(formattedUri.build().toString(), "UTF-8");
3981 } catch (UnsupportedEncodingException exception) {
3982 // Load a blank string.
3983 formattedUrlString = "";
3985 } else if (unformattedUrlString.isEmpty()){ // Load a blank web site.
3986 // Load a blank string.
3987 formattedUrlString = "";
3988 } else { // Search for the contents of the URL box.
3989 // Create an encoded URL String.
3990 String encodedUrlString;
3992 // Sanitize the search input.
3994 encodedUrlString = URLEncoder.encode(unformattedUrlString, "UTF-8");
3995 } catch (UnsupportedEncodingException exception) {
3996 encodedUrlString = "";
3999 // Add the base search URL.
4000 formattedUrlString = searchURL + encodedUrlString;
4003 // Clear the focus from the URL text box. Otherwise, proximate typing in the box will retain the colorized formatting instead of being reset during refocus.
4004 urlTextBox.clearFocus();
4007 loadUrl(formattedUrlString);
4010 private void loadUrl(String url) {// Apply any custom domain settings.
4011 // Set the URL as the formatted URL string so that checking third-party requests works correctly.
4012 formattedUrlString = url;
4014 // Apply the domain settings.
4015 applyDomainSettings(url, true, false);
4017 // If loading a website, set `urlIsLoading` to prevent changes in the user agent on websites with redirects from reloading the current website.
4018 urlIsLoading = !url.equals("");
4021 mainWebView.loadUrl(url, customHeaders);
4024 public void findPreviousOnPage(View view) {
4025 // Go to the previous highlighted phrase on the page. `false` goes backwards instead of forwards.
4026 mainWebView.findNext(false);
4029 public void findNextOnPage(View view) {
4030 // Go to the next highlighted phrase on the page. `true` goes forwards instead of backwards.
4031 mainWebView.findNext(true);
4034 public void closeFindOnPage(View view) {
4035 // Delete the contents of `find_on_page_edittext`.
4036 findOnPageEditText.setText(null);
4038 // Clear the highlighted phrases.
4039 mainWebView.clearMatches();
4041 // Hide the Find on Page `RelativeLayout`.
4042 findOnPageLinearLayout.setVisibility(View.GONE);
4044 // Show the URL app bar.
4045 supportAppBar.setVisibility(View.VISIBLE);
4047 // Hide the keyboard so we can see the webpage. `0` indicates no additional flags.
4048 inputMethodManager.hideSoftInputFromWindow(mainWebView.getWindowToken(), 0);
4051 private void applyAppSettings() {
4052 // Get a handle for the shared preferences.
4053 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
4055 // Store the values from the shared preferences in variables.
4056 incognitoModeEnabled = sharedPreferences.getBoolean("incognito_mode", false);
4057 boolean doNotTrackEnabled = sharedPreferences.getBoolean("do_not_track", false);
4058 proxyThroughOrbot = sharedPreferences.getBoolean("proxy_through_orbot", false);
4059 fullScreenBrowsingModeEnabled = sharedPreferences.getBoolean("full_screen_browsing_mode", false);
4060 hideSystemBarsOnFullscreen = sharedPreferences.getBoolean("hide_system_bars", false);
4061 translucentNavigationBarOnFullscreen = sharedPreferences.getBoolean("translucent_navigation_bar", true);
4062 downloadWithExternalApp = sharedPreferences.getBoolean("download_with_external_app", false);
4064 // Apply the proxy through Orbot settings.
4065 applyProxyThroughOrbot(false);
4067 // Set Do Not Track status.
4068 if (doNotTrackEnabled) {
4069 customHeaders.put("DNT", "1");
4071 customHeaders.remove("DNT");
4074 // Apply the appropriate full screen mode the `SYSTEM_UI` flags.
4075 if (fullScreenBrowsingModeEnabled && inFullScreenBrowsingMode) { // Privacy Browser is currently in full screen browsing mode.
4076 if (hideSystemBarsOnFullscreen) { // Hide everything.
4077 // Remove the translucent navigation setting if it is currently flagged.
4078 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
4080 // Remove the translucent status bar overlay.
4081 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
4083 // Remove the translucent status bar overlay on the `Drawer Layout`, which is special and needs its own command.
4084 drawerLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
4086 /* SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
4087 * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
4088 * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
4090 rootCoordinatorLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
4091 } else { // Hide everything except the status and navigation bars.
4092 // Remove any `SYSTEM_UI` flags from `rootCoordinatorLayout`.
4093 rootCoordinatorLayout.setSystemUiVisibility(0);
4095 // Add the translucent status flag if it is unset.
4096 getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
4098 if (translucentNavigationBarOnFullscreen) {
4099 // Set the navigation bar to be translucent.
4100 getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
4102 // Set the navigation bar to be black.
4103 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
4106 } else { // Privacy Browser is not in full screen browsing mode.
4107 // Reset the full screen tracker, which could be true if Privacy Browser was in full screen mode before entering settings and full screen browsing was disabled.
4108 inFullScreenBrowsingMode = false;
4110 // Show the `appBar` if `findOnPageLinearLayout` is not visible.
4111 if (findOnPageLinearLayout.getVisibility() == View.GONE) {
4115 // Show the `BannerAd` in the free flavor.
4116 if (BuildConfig.FLAVOR.contentEquals("free")) {
4117 // Initialize the ad. The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations.
4118 AdHelper.initializeAds(findViewById(R.id.adview), getApplicationContext(), getFragmentManager(), getString(R.string.google_app_id), getString(R.string.ad_unit_id));
4121 // Remove any `SYSTEM_UI` flags from `rootCoordinatorLayout`.
4122 rootCoordinatorLayout.setSystemUiVisibility(0);
4124 // Remove the translucent navigation bar flag if it is set.
4125 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
4127 // Add the translucent status flag if it is unset. This also resets `drawerLayout's` `View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN`.
4128 getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
4130 // Constrain `rootCoordinatorLayout` inside the status and navigation bars.
4131 rootCoordinatorLayout.setFitsSystemWindows(true);
4135 // `reloadWebsite` is used if returning from the Domains activity. Otherwise JavaScript might not function correctly if it is newly enabled.
4136 // The deprecated `.getDrawable()` must be used until the minimum API >= 21.
4137 @SuppressWarnings("deprecation")
4138 private boolean applyDomainSettings(String url, boolean resetFavoriteIcon, boolean reloadWebsite) {
4139 // Get the current user agent.
4140 String initialUserAgent = mainWebView.getSettings().getUserAgentString();
4142 // Initialize a variable to track if the user agent changes.
4143 boolean userAgentChanged = false;
4145 // Parse the URL into a URI.
4146 Uri uri = Uri.parse(url);
4148 // Extract the domain from `uri`.
4149 String hostName = uri.getHost();
4151 // Initialize `loadingNewDomainName`.
4152 boolean loadingNewDomainName;
4154 // If either `hostName` or `currentDomainName` are `null`, run the options for loading a new domain name.
4155 // The lint suggestion to simplify the `if` statement is incorrect, because `hostName.equals(currentDomainName)` can produce a `null object reference.`
4156 //noinspection SimplifiableIfStatement
4157 if ((hostName == null) || (currentDomainName == null)) {
4158 loadingNewDomainName = true;
4159 } else { // Determine if `hostName` equals `currentDomainName`.
4160 loadingNewDomainName = !hostName.equals(currentDomainName);
4163 // Strings don't like to be null.
4164 if (hostName == null) {
4168 // Only apply the domain settings if a new domain is being loaded. This allows the user to set temporary settings for JavaScript, cookies, DOM storage, etc.
4169 if (loadingNewDomainName) {
4170 // Set the new `hostname` as the `currentDomainName`.
4171 currentDomainName = hostName;
4173 // Reset `ignorePinnedSslCertificate`.
4174 ignorePinnedSslCertificate = false;
4176 // Reset the favorite icon if specified.
4177 if (resetFavoriteIcon) {
4178 favoriteIconBitmap = favoriteIconDefaultBitmap;
4179 favoriteIconImageView.setImageBitmap(Bitmap.createScaledBitmap(favoriteIconBitmap, 64, 64, true));
4182 // Initialize the database handler. The `0` specifies the database version, but that is ignored and set instead using a constant in `DomainsDatabaseHelper`.
4183 DomainsDatabaseHelper domainsDatabaseHelper = new DomainsDatabaseHelper(this, null, null, 0);
4185 // Get a full cursor from `domainsDatabaseHelper`.
4186 Cursor domainNameCursor = domainsDatabaseHelper.getDomainNameCursorOrderedByDomain();
4188 // Initialize `domainSettingsSet`.
4189 Set<String> domainSettingsSet = new HashSet<>();
4191 // Get the domain name column index.
4192 int domainNameColumnIndex = domainNameCursor.getColumnIndex(DomainsDatabaseHelper.DOMAIN_NAME);
4194 // Populate `domainSettingsSet`.
4195 for (int i = 0; i < domainNameCursor.getCount(); i++) {
4196 // Move `domainsCursor` to the current row.
4197 domainNameCursor.moveToPosition(i);
4199 // Store the domain name in `domainSettingsSet`.
4200 domainSettingsSet.add(domainNameCursor.getString(domainNameColumnIndex));
4203 // Close `domainNameCursor.
4204 domainNameCursor.close();
4206 // Initialize variables to track if domain settings will be applied and, if so, under which name.
4207 domainSettingsApplied = false;
4208 String domainNameInDatabase = null;
4210 // Check the hostname.
4211 if (domainSettingsSet.contains(hostName)) {
4212 domainSettingsApplied = true;
4213 domainNameInDatabase = hostName;
4216 // Check all the subdomains of the host name against wildcard domains in the domain cursor.
4217 while (!domainSettingsApplied && hostName.contains(".")) { // Stop checking if domain settings are already applied or there are no more `.` in the host name.
4218 if (domainSettingsSet.contains("*." + hostName)) { // Check the host name prepended by `*.`.
4219 // Apply the domain settings.
4220 domainSettingsApplied = true;
4222 // Store the applied domain names as it appears in the database.
4223 domainNameInDatabase = "*." + hostName;
4226 // Strip out the lowest subdomain of of the host name.
4227 hostName = hostName.substring(hostName.indexOf(".") + 1);
4231 // Get a handle for the shared preference.
4232 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
4234 // Store the general preference information.
4235 String defaultFontSizeString = sharedPreferences.getString("font_size", getString(R.string.font_size_default_value));
4236 String defaultUserAgentName = sharedPreferences.getString("user_agent", getString(R.string.user_agent_default_value));
4237 defaultCustomUserAgentString = sharedPreferences.getString("custom_user_agent", getString(R.string.custom_user_agent_default_value));
4238 boolean defaultSwipeToRefresh = sharedPreferences.getBoolean("swipe_to_refresh", true);
4239 nightMode = sharedPreferences.getBoolean("night_mode", false);
4240 boolean displayWebpageImages = sharedPreferences.getBoolean("display_webpage_images", true);
4242 if (domainSettingsApplied) { // The url has custom domain settings.
4243 // Get a cursor for the current host and move it to the first position.
4244 Cursor currentHostDomainSettingsCursor = domainsDatabaseHelper.getCursorForDomainName(domainNameInDatabase);
4245 currentHostDomainSettingsCursor.moveToFirst();
4247 // Get the settings from the cursor.
4248 domainSettingsDatabaseId = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper._ID)));
4249 javaScriptEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_JAVASCRIPT)) == 1);
4250 firstPartyCookiesEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FIRST_PARTY_COOKIES)) == 1);
4251 thirdPartyCookiesEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_THIRD_PARTY_COOKIES)) == 1);
4252 domStorageEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_DOM_STORAGE)) == 1);
4253 // Form data can be removed once the minimum API >= 26.
4254 saveFormDataEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FORM_DATA)) == 1);
4255 easyListEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_EASYLIST)) == 1);
4256 easyPrivacyEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_EASYPRIVACY)) == 1);
4257 fanboysAnnoyanceListEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FANBOYS_ANNOYANCE_LIST)) == 1);
4258 fanboysSocialBlockingListEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FANBOYS_SOCIAL_BLOCKING_LIST)) == 1);
4259 ultraPrivacyEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_ULTRAPRIVACY)) == 1);
4260 blockAllThirdPartyRequests = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.BLOCK_ALL_THIRD_PARTY_REQUESTS)) == 1);
4261 String userAgentName = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.USER_AGENT));
4262 int fontSize = currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.FONT_SIZE));
4263 int swipeToRefreshInt = currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SWIPE_TO_REFRESH));
4264 int nightModeInt = currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.NIGHT_MODE));
4265 int displayWebpageImagesInt = currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.DISPLAY_IMAGES));
4266 pinnedDomainSslCertificate = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.PINNED_SSL_CERTIFICATE)) == 1);
4267 pinnedDomainSslIssuedToCNameString = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_TO_COMMON_NAME));
4268 pinnedDomainSslIssuedToONameString = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_TO_ORGANIZATION));
4269 pinnedDomainSslIssuedToUNameString = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_TO_ORGANIZATIONAL_UNIT));
4270 pinnedDomainSslIssuedByCNameString = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_BY_COMMON_NAME));
4271 pinnedDomainSslIssuedByONameString = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_BY_ORGANIZATION));
4272 pinnedDomainSslIssuedByUNameString = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_BY_ORGANIZATIONAL_UNIT));
4274 // Set `nightMode` according to `nightModeInt`. If `nightModeInt` is `DomainsDatabaseHelper.NIGHT_MODE_SYSTEM_DEFAULT` the current setting from `sharedPreferences` will be used.
4275 switch (nightModeInt) {
4276 case DomainsDatabaseHelper.NIGHT_MODE_ENABLED:
4280 case DomainsDatabaseHelper.NIGHT_MODE_DISABLED:
4285 // Store the domain JavaScript status. This is used by the options menu night mode toggle.
4286 domainSettingsJavaScriptEnabled = javaScriptEnabled;
4288 // Enable JavaScript if night mode is enabled.
4290 javaScriptEnabled = true;
4293 // Set the pinned SSL certificate start date to `null` if the saved date `long` is 0.
4294 if (currentHostDomainSettingsCursor.getLong(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_START_DATE)) == 0) {
4295 pinnedDomainSslStartDate = null;
4297 pinnedDomainSslStartDate = new Date(currentHostDomainSettingsCursor.getLong(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_START_DATE)));
4300 // Set the pinned SSL certificate end date to `null` if the saved date `long` is 0.
4301 if (currentHostDomainSettingsCursor.getLong(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_END_DATE)) == 0) {
4302 pinnedDomainSslEndDate = null;
4304 pinnedDomainSslEndDate = new Date(currentHostDomainSettingsCursor.getLong(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_END_DATE)));
4307 // Close `currentHostDomainSettingsCursor`.
4308 currentHostDomainSettingsCursor.close();
4310 // Apply the domain settings.
4311 mainWebView.getSettings().setJavaScriptEnabled(javaScriptEnabled);
4312 cookieManager.setAcceptCookie(firstPartyCookiesEnabled);
4313 mainWebView.getSettings().setDomStorageEnabled(domStorageEnabled);
4315 // Apply the form data setting if the API < 26.
4316 if (Build.VERSION.SDK_INT < 26) {
4317 mainWebView.getSettings().setSaveFormData(saveFormDataEnabled);
4320 // Apply the font size.
4321 if (fontSize == 0) { // Apply the default font size.
4322 mainWebView.getSettings().setTextZoom(Integer.valueOf(defaultFontSizeString));
4323 } else { // Apply the specified font size.
4324 mainWebView.getSettings().setTextZoom(fontSize);
4327 // Set third-party cookies status if API >= 21.
4328 if (Build.VERSION.SDK_INT >= 21) {
4329 cookieManager.setAcceptThirdPartyCookies(mainWebView, thirdPartyCookiesEnabled);
4332 // Only set the user agent if the webpage is not currently loading. Otherwise, changing the user agent on redirects can cause the original website to reload.
4333 // <https://redmine.stoutner.com/issues/160>
4334 if (!urlIsLoading) {
4335 // Set the user agent.
4336 if (userAgentName.equals(getString(R.string.system_default_user_agent))) { // Use the system default user agent.
4337 // Get the array position of the default user agent name.
4338 int defaultUserAgentArrayPosition = userAgentNamesArray.getPosition(defaultUserAgentName);
4340 // Set the user agent according to the system default.
4341 switch (defaultUserAgentArrayPosition) {
4342 case UNRECOGNIZED_USER_AGENT: // The default user agent name is not on the canonical list.
4343 // This is probably because it was set in an older version of Privacy Browser before the switch to persistent user agent names.
4344 mainWebView.getSettings().setUserAgentString(defaultUserAgentName);
4347 case SETTINGS_WEBVIEW_DEFAULT_USER_AGENT:
4348 // Set the user agent to `""`, which uses the default value.
4349 mainWebView.getSettings().setUserAgentString("");
4352 case SETTINGS_CUSTOM_USER_AGENT:
4353 // Set the custom user agent.
4354 mainWebView.getSettings().setUserAgentString(defaultCustomUserAgentString);
4358 // Get the user agent string from the user agent data array
4359 mainWebView.getSettings().setUserAgentString(userAgentDataArray[defaultUserAgentArrayPosition]);
4361 } else { // Set the user agent according to the stored name.
4362 // Get the array position of the user agent name.
4363 int userAgentArrayPosition = userAgentNamesArray.getPosition(userAgentName);
4365 switch (userAgentArrayPosition) {
4366 case UNRECOGNIZED_USER_AGENT: // The user agent name contains a custom user agent.
4367 mainWebView.getSettings().setUserAgentString(userAgentName);
4370 case SETTINGS_WEBVIEW_DEFAULT_USER_AGENT:
4371 // Set the user agent to `""`, which uses the default value.
4372 mainWebView.getSettings().setUserAgentString("");
4376 // Get the user agent string from the user agent data array.
4377 mainWebView.getSettings().setUserAgentString(userAgentDataArray[userAgentArrayPosition]);
4381 // Store the applied user agent string, which is used in the View Source activity.
4382 appliedUserAgentString = mainWebView.getSettings().getUserAgentString();
4384 // Update the user agent change tracker.
4385 userAgentChanged = !appliedUserAgentString.equals(initialUserAgent);
4388 // Set swipe to refresh.
4389 switch (swipeToRefreshInt) {
4390 case DomainsDatabaseHelper.SWIPE_TO_REFRESH_SYSTEM_DEFAULT:
4391 // Set swipe to refresh according to the default.
4392 swipeRefreshLayout.setEnabled(defaultSwipeToRefresh);
4395 case DomainsDatabaseHelper.SWIPE_TO_REFRESH_ENABLED:
4396 // Enable swipe to refresh.
4397 swipeRefreshLayout.setEnabled(true);
4400 case DomainsDatabaseHelper.SWIPE_TO_REFRESH_DISABLED:
4401 // Disable swipe to refresh.
4402 swipeRefreshLayout.setEnabled(false);
4405 // Set the loading of webpage images.
4406 switch (displayWebpageImagesInt) {
4407 case DomainsDatabaseHelper.DISPLAY_WEBPAGE_IMAGES_SYSTEM_DEFAULT:
4408 mainWebView.getSettings().setLoadsImagesAutomatically(displayWebpageImages);
4411 case DomainsDatabaseHelper.DISPLAY_WEBPAGE_IMAGES_ENABLED:
4412 mainWebView.getSettings().setLoadsImagesAutomatically(true);
4415 case DomainsDatabaseHelper.DISPLAY_WEBPAGE_IMAGES_DISABLED:
4416 mainWebView.getSettings().setLoadsImagesAutomatically(false);
4420 // Set a green background on `urlTextBox` to indicate that custom domain settings are being used. We have to use the deprecated `.getDrawable()` until the minimum API >= 21.
4422 urlAppBarRelativeLayout.setBackground(getResources().getDrawable(R.drawable.url_bar_background_dark_blue));
4424 urlAppBarRelativeLayout.setBackground(getResources().getDrawable(R.drawable.url_bar_background_light_green));
4426 } else { // The new URL does not have custom domain settings. Load the defaults.
4427 // Store the values from `sharedPreferences` in variables.
4428 javaScriptEnabled = sharedPreferences.getBoolean("javascript", false);
4429 firstPartyCookiesEnabled = sharedPreferences.getBoolean("first_party_cookies", false);
4430 thirdPartyCookiesEnabled = sharedPreferences.getBoolean("third_party_cookies", false);
4431 domStorageEnabled = sharedPreferences.getBoolean("dom_storage", false);
4432 saveFormDataEnabled = sharedPreferences.getBoolean("save_form_data", false); // Form data can be removed once the minimum API >= 26.
4433 easyListEnabled = sharedPreferences.getBoolean("easylist", true);
4434 easyPrivacyEnabled = sharedPreferences.getBoolean("easyprivacy", true);
4435 fanboysAnnoyanceListEnabled = sharedPreferences.getBoolean("fanboys_annoyance_list", true);
4436 fanboysSocialBlockingListEnabled = sharedPreferences.getBoolean("fanboys_social_blocking_list", true);
4437 ultraPrivacyEnabled = sharedPreferences.getBoolean("ultraprivacy", true);
4438 blockAllThirdPartyRequests = sharedPreferences.getBoolean("block_all_third_party_requests", false);
4440 // Set `javaScriptEnabled` to be `true` if `night_mode` is `true`.
4442 javaScriptEnabled = true;
4445 // Apply the default settings.
4446 mainWebView.getSettings().setJavaScriptEnabled(javaScriptEnabled);
4447 cookieManager.setAcceptCookie(firstPartyCookiesEnabled);
4448 mainWebView.getSettings().setDomStorageEnabled(domStorageEnabled);
4449 mainWebView.getSettings().setTextZoom(Integer.valueOf(defaultFontSizeString));
4450 swipeRefreshLayout.setEnabled(defaultSwipeToRefresh);
4452 // Apply the form data setting if the API < 26.
4453 if (Build.VERSION.SDK_INT < 26) {
4454 mainWebView.getSettings().setSaveFormData(saveFormDataEnabled);
4457 // Reset the pinned SSL certificate information.
4458 domainSettingsDatabaseId = -1;
4459 pinnedDomainSslCertificate = false;
4460 pinnedDomainSslIssuedToCNameString = "";
4461 pinnedDomainSslIssuedToONameString = "";
4462 pinnedDomainSslIssuedToUNameString = "";
4463 pinnedDomainSslIssuedByCNameString = "";
4464 pinnedDomainSslIssuedByONameString = "";
4465 pinnedDomainSslIssuedByUNameString = "";
4466 pinnedDomainSslStartDate = null;
4467 pinnedDomainSslEndDate = null;
4469 // Set third-party cookies status if API >= 21.
4470 if (Build.VERSION.SDK_INT >= 21) {
4471 cookieManager.setAcceptThirdPartyCookies(mainWebView, thirdPartyCookiesEnabled);
4474 // Only set the user agent if the webpage is not currently loading. Otherwise, changing the user agent on redirects can cause the original website to reload.
4475 // <https://redmine.stoutner.com/issues/160>
4476 if (!urlIsLoading) {
4477 // Get the array position of the user agent name.
4478 int userAgentArrayPosition = userAgentNamesArray.getPosition(defaultUserAgentName);
4480 // Set the user agent.
4481 switch (userAgentArrayPosition) {
4482 case UNRECOGNIZED_USER_AGENT: // The default user agent name is not on the canonical list.
4483 // This is probably because it was set in an older version of Privacy Browser before the switch to persistent user agent names.
4484 mainWebView.getSettings().setUserAgentString(defaultUserAgentName);
4487 case SETTINGS_WEBVIEW_DEFAULT_USER_AGENT:
4488 // Set the user agent to `""`, which uses the default value.
4489 mainWebView.getSettings().setUserAgentString("");
4492 case SETTINGS_CUSTOM_USER_AGENT:
4493 // Set the custom user agent.
4494 mainWebView.getSettings().setUserAgentString(defaultCustomUserAgentString);
4498 // Get the user agent string from the user agent data array
4499 mainWebView.getSettings().setUserAgentString(userAgentDataArray[userAgentArrayPosition]);
4502 // Store the applied user agent string, which is used in the View Source activity.
4503 appliedUserAgentString = mainWebView.getSettings().getUserAgentString();
4505 // Update the user agent change tracker.
4506 userAgentChanged = !appliedUserAgentString.equals(initialUserAgent);
4509 // Set the loading of webpage images.
4510 mainWebView.getSettings().setLoadsImagesAutomatically(displayWebpageImages);
4512 // Set a transparent background on `urlTextBox`. We have to use the deprecated `.getDrawable()` until the minimum API >= 21.
4513 urlAppBarRelativeLayout.setBackgroundDrawable(getResources().getDrawable(R.color.transparent));
4516 // Close the domains database helper.
4517 domainsDatabaseHelper.close();
4519 // Update the privacy icons, but only if `mainMenu` has already been populated.
4520 if (mainMenu != null) {
4521 updatePrivacyIcons(true);
4525 // Reload the website if returning from the Domains activity.
4526 if (reloadWebsite) {
4527 mainWebView.reload();
4530 // Return the user agent changed status.
4531 return userAgentChanged;
4534 private void applyProxyThroughOrbot(boolean reloadWebsite) {
4535 // Get a handle for the shared preferences.
4536 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
4538 // Get the search preferences.
4539 String homepageString = sharedPreferences.getString("homepage", getString(R.string.homepage_default_value));
4540 String torHomepageString = sharedPreferences.getString("tor_homepage", getString(R.string.tor_homepage_default_value));
4541 String torSearchString = sharedPreferences.getString("tor_search", getString(R.string.tor_search_default_value));
4542 String torSearchCustomUrlString = sharedPreferences.getString("tor_search_custom_url", getString(R.string.tor_search_custom_url_default_value));
4543 String searchString = sharedPreferences.getString("search", getString(R.string.search_default_value));
4544 String searchCustomUrlString = sharedPreferences.getString("search_custom_url", getString(R.string.search_custom_url_default_value));
4546 // Set the homepage, search, and proxy options.
4547 if (proxyThroughOrbot) { // Set the Tor options.
4548 // Set `torHomepageString` as `homepage`.
4549 homepage = torHomepageString;
4551 // If formattedUrlString is null assign the homepage to it.
4552 if (formattedUrlString == null) {
4553 formattedUrlString = homepage;
4556 // Set the search URL.
4557 if (torSearchString.equals("Custom URL")) { // Get the custom URL string.
4558 searchURL = torSearchCustomUrlString;
4559 } else { // Use the string from the pre-built list.
4560 searchURL = torSearchString;
4563 // Set the proxy. `this` refers to the current activity where an `AlertDialog` might be displayed.
4564 OrbotProxyHelper.setProxy(getApplicationContext(), this, "localhost", "8118");
4566 // Set the `appBar` background to indicate proxying through Orbot is enabled. `this` refers to the context.
4568 appBar.setBackgroundDrawable(ContextCompat.getDrawable(this, R.color.dark_blue_30));
4570 appBar.setBackgroundDrawable(ContextCompat.getDrawable(this, R.color.blue_50));
4573 // Check to see if Orbot is ready.
4574 if (!orbotStatus.equals("ON")) { // Orbot is not ready.
4575 // Set `waitingForOrbot`.
4576 waitingForOrbot = true;
4578 // Disable the wide view port so that the waiting for Orbot text is displayed correctly.
4579 mainWebView.getSettings().setUseWideViewPort(false);
4581 // Load a waiting page. `null` specifies no encoding, which defaults to ASCII.
4582 mainWebView.loadData(waitingForOrbotHtmlString, "text/html", null);
4583 } else if (reloadWebsite) { // Orbot is ready and the website should be reloaded.
4584 // Reload the website.
4585 mainWebView.reload();
4587 } else { // Set the non-Tor options.
4588 // Set `homepageString` as `homepage`.
4589 homepage = homepageString;
4591 // If formattedUrlString is null assign the homepage to it.
4592 if (formattedUrlString == null) {
4593 formattedUrlString = homepage;
4596 // Set the search URL.
4597 if (searchString.equals("Custom URL")) { // Get the custom URL string.
4598 searchURL = searchCustomUrlString;
4599 } else { // Use the string from the pre-built list.
4600 searchURL = searchString;
4603 // Reset the proxy to default. The host is `""` and the port is `"0"`.
4604 OrbotProxyHelper.setProxy(getApplicationContext(), this, "", "0");
4606 // Set the default `appBar` background. `this` refers to the context.
4608 appBar.setBackgroundDrawable(ContextCompat.getDrawable(this, R.color.gray_900));
4610 appBar.setBackgroundDrawable(ContextCompat.getDrawable(this, R.color.gray_100));
4613 // Reset `waitingForOrbot.
4614 waitingForOrbot = false;
4616 // Reload the website if requested.
4617 if (reloadWebsite) {
4618 mainWebView.reload();
4623 private void updatePrivacyIcons(boolean runInvalidateOptionsMenu) {
4624 // Get handles for the menu items.
4625 MenuItem privacyMenuItem = mainMenu.findItem(R.id.toggle_javascript);
4626 MenuItem firstPartyCookiesMenuItem = mainMenu.findItem(R.id.toggle_first_party_cookies);
4627 MenuItem domStorageMenuItem = mainMenu.findItem(R.id.toggle_dom_storage);
4628 MenuItem refreshMenuItem = mainMenu.findItem(R.id.refresh);
4630 // Update the privacy icon.
4631 if (javaScriptEnabled) { // JavaScript is enabled.
4632 privacyMenuItem.setIcon(R.drawable.javascript_enabled);
4633 } else if (firstPartyCookiesEnabled) { // JavaScript is disabled but cookies are enabled.
4634 privacyMenuItem.setIcon(R.drawable.warning);
4635 } else { // All the dangerous features are disabled.
4636 privacyMenuItem.setIcon(R.drawable.privacy_mode);
4639 // Update the first-party cookies icon.
4640 if (firstPartyCookiesEnabled) { // First-party cookies are enabled.
4641 firstPartyCookiesMenuItem.setIcon(R.drawable.cookies_enabled);
4642 } else { // First-party cookies are disabled.
4644 firstPartyCookiesMenuItem.setIcon(R.drawable.cookies_disabled_dark);
4646 firstPartyCookiesMenuItem.setIcon(R.drawable.cookies_disabled_light);
4650 // Update the DOM storage icon.
4651 if (javaScriptEnabled && domStorageEnabled) { // Both JavaScript and DOM storage are enabled.
4652 domStorageMenuItem.setIcon(R.drawable.dom_storage_enabled);
4653 } else if (javaScriptEnabled) { // JavaScript is enabled but DOM storage is disabled.
4655 domStorageMenuItem.setIcon(R.drawable.dom_storage_disabled_dark);
4657 domStorageMenuItem.setIcon(R.drawable.dom_storage_disabled_light);
4659 } else { // JavaScript is disabled, so DOM storage is ghosted.
4661 domStorageMenuItem.setIcon(R.drawable.dom_storage_ghosted_dark);
4663 domStorageMenuItem.setIcon(R.drawable.dom_storage_ghosted_light);
4667 // Update the refresh icon.
4669 refreshMenuItem.setIcon(R.drawable.refresh_enabled_dark);
4671 refreshMenuItem.setIcon(R.drawable.refresh_enabled_light);
4674 // `invalidateOptionsMenu` calls `onPrepareOptionsMenu()` and redraws the icons in the `AppBar`.
4675 if (runInvalidateOptionsMenu) {
4676 invalidateOptionsMenu();
4680 private void openUrlWithExternalApp(String url) {
4681 // Create a download intent. Not specifying the action type will display the maximum number of options.
4682 Intent downloadIntent = new Intent();
4684 // Set the URI and the MIME type. Specifying `text/html` displays a good number of options.
4685 downloadIntent.setDataAndType(Uri.parse(url), "text/html");
4687 // Flag the intent to open in a new task.
4688 downloadIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
4690 // Show the chooser.
4691 startActivity(Intent.createChooser(downloadIntent, getString(R.string.open_with)));
4694 private void highlightUrlText() {
4695 // Get the URL string.
4696 String urlString = urlTextBox.getText().toString();
4698 // Highlight the URL according to the protocol.
4699 if (urlString.startsWith("file://")) { // This is a file URL.
4700 // De-emphasize only the protocol.
4701 urlTextBox.getText().setSpan(initialGrayColorSpan, 0, 7, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
4702 } else if (urlString.startsWith("content://")) {
4703 // De-emphasize only the protocol.
4704 urlTextBox.getText().setSpan(initialGrayColorSpan, 0, 10, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
4705 } else { // This is a web URL.
4706 // Get the index of the `/` immediately after the domain name.
4707 int endOfDomainName = urlString.indexOf("/", (urlString.indexOf("//") + 2));
4709 // Create a base URL string.
4712 // Get the base URL.
4713 if (endOfDomainName > 0) { // There is at least one character after the base URL.
4714 // Get the base URL.
4715 baseUrl = urlString.substring(0, endOfDomainName);
4716 } else { // There are no characters after the base URL.
4717 // Set the base URL to be the entire URL string.
4718 baseUrl = urlString;
4721 // Get the index of the last `.` in the domain.
4722 int lastDotIndex = baseUrl.lastIndexOf(".");
4724 // Get the index of the penultimate `.` in the domain.
4725 int penultimateDotIndex = baseUrl.lastIndexOf(".", lastDotIndex - 1);
4727 // Markup the beginning of the URL.
4728 if (urlString.startsWith("http://")) { // Highlight the protocol of connections that are not encrypted.
4729 urlTextBox.getText().setSpan(redColorSpan, 0, 7, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
4731 // De-emphasize subdomains.
4732 if (penultimateDotIndex > 0) { // There is more than one subdomain in the domain name.
4733 urlTextBox.getText().setSpan(initialGrayColorSpan, 7, penultimateDotIndex + 1, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
4735 } else if (urlString.startsWith("https://")) { // De-emphasize the protocol of connections that are encrypted.
4736 if (penultimateDotIndex > 0) { // There is more than one subdomain in the domain name.
4737 // De-emphasize the protocol and the additional subdomains.
4738 urlTextBox.getText().setSpan(initialGrayColorSpan, 0, penultimateDotIndex + 1, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
4739 } else { // There is only one subdomain in the domain name.
4740 // De-emphasize only the protocol.
4741 urlTextBox.getText().setSpan(initialGrayColorSpan, 0, 8, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
4745 // De-emphasize the text after the domain name.
4746 if (endOfDomainName > 0) {
4747 urlTextBox.getText().setSpan(finalGrayColorSpan, endOfDomainName, urlString.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
4752 private void loadBookmarksFolder() {
4753 // Update the bookmarks cursor with the contents of the bookmarks database for the current folder.
4754 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder);
4756 // Populate the bookmarks cursor adapter. `this` specifies the `Context`. `false` disables `autoRequery`.
4757 bookmarksCursorAdapter = new CursorAdapter(this, bookmarksCursor, false) {
4759 public View newView(Context context, Cursor cursor, ViewGroup parent) {
4760 // Inflate the individual item layout. `false` does not attach it to the root.
4761 return getLayoutInflater().inflate(R.layout.bookmarks_drawer_item_linearlayout, parent, false);
4765 public void bindView(View view, Context context, Cursor cursor) {
4766 // Get handles for the views.
4767 ImageView bookmarkFavoriteIcon = view.findViewById(R.id.bookmark_favorite_icon);
4768 TextView bookmarkNameTextView = view.findViewById(R.id.bookmark_name);
4770 // Get the favorite icon byte array from the cursor.
4771 byte[] favoriteIconByteArray = cursor.getBlob(cursor.getColumnIndex(BookmarksDatabaseHelper.FAVORITE_ICON));
4773 // Convert the byte array to a `Bitmap` beginning at the first byte and ending at the last.
4774 Bitmap favoriteIconBitmap = BitmapFactory.decodeByteArray(favoriteIconByteArray, 0, favoriteIconByteArray.length);
4776 // Display the bitmap in `bookmarkFavoriteIcon`.
4777 bookmarkFavoriteIcon.setImageBitmap(favoriteIconBitmap);
4779 // Get the bookmark name from the cursor and display it in `bookmarkNameTextView`.
4780 String bookmarkNameString = cursor.getString(cursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME));
4781 bookmarkNameTextView.setText(bookmarkNameString);
4783 // Make the font bold for folders.
4784 if (cursor.getInt(cursor.getColumnIndex(BookmarksDatabaseHelper.IS_FOLDER)) == 1) {
4785 bookmarkNameTextView.setTypeface(Typeface.DEFAULT_BOLD);
4786 } else { // Reset the font to default for normal bookmarks.
4787 bookmarkNameTextView.setTypeface(Typeface.DEFAULT);
4792 // Populate the `ListView` with the adapter.
4793 bookmarksListView.setAdapter(bookmarksCursorAdapter);
4795 // Set the bookmarks drawer title.
4796 if (currentBookmarksFolder.isEmpty()) {
4797 bookmarksTitleTextView.setText(R.string.bookmarks);
4799 bookmarksTitleTextView.setText(currentBookmarksFolder);