2 * Copyright © 2015-2018 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.WebStorage;
99 import android.webkit.WebView;
100 import android.webkit.WebViewClient;
101 import android.webkit.WebViewDatabase;
102 import android.widget.ArrayAdapter;
103 import android.widget.CursorAdapter;
104 import android.widget.EditText;
105 import android.widget.FrameLayout;
106 import android.widget.ImageView;
107 import android.widget.LinearLayout;
108 import android.widget.ListView;
109 import android.widget.ProgressBar;
110 import android.widget.RadioButton;
111 import android.widget.RelativeLayout;
112 import android.widget.TextView;
114 import com.stoutner.privacybrowser.BuildConfig;
115 import com.stoutner.privacybrowser.R;
116 import com.stoutner.privacybrowser.dialogs.AdConsentDialog;
117 import com.stoutner.privacybrowser.dialogs.CreateBookmarkDialog;
118 import com.stoutner.privacybrowser.dialogs.CreateBookmarkFolderDialog;
119 import com.stoutner.privacybrowser.dialogs.CreateHomeScreenShortcutDialog;
120 import com.stoutner.privacybrowser.dialogs.DownloadImageDialog;
121 import com.stoutner.privacybrowser.dialogs.DownloadLocationPermissionDialog;
122 import com.stoutner.privacybrowser.dialogs.EditBookmarkDialog;
123 import com.stoutner.privacybrowser.dialogs.EditBookmarkFolderDialog;
124 import com.stoutner.privacybrowser.dialogs.HttpAuthenticationDialog;
125 import com.stoutner.privacybrowser.dialogs.PinnedSslCertificateMismatchDialog;
126 import com.stoutner.privacybrowser.dialogs.UrlHistoryDialog;
127 import com.stoutner.privacybrowser.dialogs.ViewSslCertificateDialog;
128 import com.stoutner.privacybrowser.helpers.AdHelper;
129 import com.stoutner.privacybrowser.helpers.BlockListHelper;
130 import com.stoutner.privacybrowser.helpers.BookmarksDatabaseHelper;
131 import com.stoutner.privacybrowser.helpers.DomainsDatabaseHelper;
132 import com.stoutner.privacybrowser.helpers.OrbotProxyHelper;
133 import com.stoutner.privacybrowser.dialogs.DownloadFileDialog;
134 import com.stoutner.privacybrowser.dialogs.SslCertificateErrorDialog;
136 import java.io.ByteArrayInputStream;
137 import java.io.ByteArrayOutputStream;
139 import java.io.IOException;
140 import java.io.UnsupportedEncodingException;
141 import java.net.MalformedURLException;
143 import java.net.URLDecoder;
144 import java.net.URLEncoder;
145 import java.util.ArrayList;
146 import java.util.Date;
147 import java.util.HashMap;
148 import java.util.HashSet;
149 import java.util.List;
150 import java.util.Map;
151 import java.util.Set;
153 // AppCompatActivity from android.support.v7.app.AppCompatActivity must be used to have access to the SupportActionBar until the minimum API is >= 21.
154 public class MainWebViewActivity extends AppCompatActivity implements CreateBookmarkDialog.CreateBookmarkListener, CreateBookmarkFolderDialog.CreateBookmarkFolderListener,
155 CreateHomeScreenShortcutDialog.CreateHomeScreenShortcutListener, DownloadFileDialog.DownloadFileListener, DownloadImageDialog.DownloadImageListener,
156 DownloadLocationPermissionDialog.DownloadLocationPermissionDialogListener, EditBookmarkDialog.EditBookmarkListener, EditBookmarkFolderDialog.EditBookmarkFolderListener,
157 HttpAuthenticationDialog.HttpAuthenticationListener, NavigationView.OnNavigationItemSelectedListener, PinnedSslCertificateMismatchDialog.PinnedSslCertificateMismatchListener,
158 SslCertificateErrorDialog.SslCertificateErrorListener, UrlHistoryDialog.UrlHistoryListener {
160 // `darkTheme` is public static so it can be accessed from everywhere.
161 public static boolean darkTheme;
163 // `allowScreenshots` is public static so it can be accessed from everywhere. It is also used in `onCreate()`.
164 public static boolean allowScreenshots;
166 // `favoriteIconBitmap` is public static so it can be accessed from `CreateHomeScreenShortcutDialog`, `BookmarksActivity`, `BookmarksDatabaseViewActivity`, `CreateBookmarkDialog`,
167 // `CreateBookmarkFolderDialog`, `EditBookmarkDialog`, `EditBookmarkFolderDialog`, `EditBookmarkDatabaseViewDialog`, and `ViewSslCertificateDialog`. It is also used in `onCreate()`,
168 // `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onCreateHomeScreenShortcutCreate()`, `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and `applyDomainSettings()`.
169 public static Bitmap favoriteIconBitmap;
171 // `formattedUrlString` is public static so it can be accessed from `BookmarksActivity`, `CreateBookmarkDialog`, and `AddDomainDialog`.
172 // It is also used in `onCreate()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onCreateHomeScreenShortcutCreate()`, `loadUrlFromTextBox()`, and `applyProxyThroughOrbot()`.
173 public static String formattedUrlString;
175 // `sslCertificate` is public static so it can be accessed from `DomainsActivity`, `DomainsListFragment`, `DomainSettingsFragment`, `PinnedSslCertificateMismatchDialog`,
176 // and `ViewSslCertificateDialog`. It is also used in `onCreate()`.
177 public static SslCertificate sslCertificate;
179 // `orbotStatus` is public static so it can be accessed from `OrbotProxyHelper`. It is also used in `onCreate()`, `onResume()`, and `applyProxyThroughOrbot()`.
180 public static String orbotStatus;
182 // `webViewTitle` is public static so it can be accessed from `CreateBookmarkDialog` and `CreateHomeScreenShortcutDialog`. It is also used in `onCreate()`.
183 public static String webViewTitle;
185 // `appliedUserAgentString` is public static so it can be accessed from `ViewSourceActivity`. It is also used in `applyDomainSettings()`.
186 public static String appliedUserAgentString;
188 // `reloadOnRestart` is public static so it can be accessed from `SettingsFragment`. It is also used in `onRestart()`
189 public static boolean reloadOnRestart;
191 // `reloadUrlOnRestart` is public static so it can be accessed from `SettingsFragment` and `BookmarksActivity`. It is also used in `onRestart()`.
192 public static boolean loadUrlOnRestart;
194 // `restartFromBookmarksActivity` is public static so it can be accessed from `BookmarksActivity`. It is also used in `onRestart()`.
195 public static boolean restartFromBookmarksActivity;
197 // The block list versions are public static so they can be accessed from `AboutTabFragment`. They are also used in `onCreate()`.
198 public static String easyListVersion;
199 public static String easyPrivacyVersion;
200 public static String fanboysAnnoyanceVersion;
201 public static String fanboysSocialVersion;
202 public static String ultraPrivacyVersion;
204 // The request items are public static so they can be accessed by `BlockListHelper`, `RequestsArrayAdapter`, and `ViewRequestsDialog`. They are also used in `onCreate()` and `onPrepareOptionsMenu()`.
205 public static List<String[]> resourceRequests;
206 public static String[] whiteListResultStringArray;
207 private int blockedRequests;
208 private int easyListBlockedRequests;
209 private int easyPrivacyBlockedRequests;
210 private int fanboysAnnoyanceListBlockedRequests;
211 private int fanboysSocialBlockingListBlockedRequests;
212 private int ultraPrivacyBlockedRequests;
213 private int thirdPartyBlockedRequests;
215 public final static int REQUEST_DISPOSITION = 0;
216 public final static int REQUEST_URL = 1;
217 public final static int REQUEST_BLOCKLIST = 2;
218 public final static int REQUEST_SUBLIST = 3;
219 public final static int REQUEST_BLOCKLIST_ENTRIES = 4;
220 public final static int REQUEST_BLOCKLIST_ORIGINAL_ENTRY = 5;
222 public final static int REQUEST_DEFAULT = 0;
223 public final static int REQUEST_ALLOWED = 1;
224 public final static int REQUEST_THIRD_PARTY = 2;
225 public final static int REQUEST_BLOCKED = 3;
227 public final static int MAIN_WHITELIST = 1;
228 public final static int FINAL_WHITELIST = 2;
229 public final static int DOMAIN_WHITELIST = 3;
230 public final static int DOMAIN_INITIAL_WHITELIST = 4;
231 public final static int DOMAIN_FINAL_WHITELIST = 5;
232 public final static int THIRD_PARTY_WHITELIST = 6;
233 public final static int THIRD_PARTY_DOMAIN_WHITELIST = 7;
234 public final static int THIRD_PARTY_DOMAIN_INITIAL_WHITELIST = 8;
236 public final static int MAIN_BLACKLIST = 9;
237 public final static int INITIAL_BLACKLIST = 10;
238 public final static int FINAL_BLACKLIST = 11;
239 public final static int DOMAIN_BLACKLIST = 12;
240 public final static int DOMAIN_INITIAL_BLACKLIST = 13;
241 public final static int DOMAIN_FINAL_BLACKLIST = 14;
242 public final static int DOMAIN_REGULAR_EXPRESSION_BLACKLIST = 15;
243 public final static int THIRD_PARTY_BLACKLIST = 16;
244 public final static int THIRD_PARTY_INITIAL_BLACKLIST = 17;
245 public final static int THIRD_PARTY_DOMAIN_BLACKLIST = 18;
246 public final static int THIRD_PARTY_DOMAIN_INITIAL_BLACKLIST = 19;
247 public final static int THIRD_PARTY_REGULAR_EXPRESSION_BLACKLIST = 20;
248 public final static int THIRD_PARTY_DOMAIN_REGULAR_EXPRESSION_BLACKLIST = 21;
249 public final static int REGULAR_EXPRESSION_BLACKLIST = 22;
251 // `blockAllThirdPartyRequests` is public static so it can be accessed from `RequestsActivity`.
252 // It is also used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, and `applyAppSettings()`
253 public static boolean blockAllThirdPartyRequests;
255 // `currentBookmarksFolder` is public static so it can be accessed from `BookmarksActivity`. It is also used in `onCreate()`, `onBackPressed()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`,
256 // `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and `loadBookmarksFolder()`.
257 public static String currentBookmarksFolder;
259 // `domainSettingsDatabaseId` is public static so it can be accessed from `PinnedSslCertificateMismatchDialog`. It is also used in `onCreate()`, `onOptionsItemSelected()`, and `applyDomainSettings()`.
260 public static int domainSettingsDatabaseId;
262 // The pinned domain SSL Certificate variables are public static so they can be accessed from `PinnedSslCertificateMismatchDialog`. They are also used in `onCreate()` and `applyDomainSettings()`.
263 public static String pinnedDomainSslIssuedToCNameString;
264 public static String pinnedDomainSslIssuedToONameString;
265 public static String pinnedDomainSslIssuedToUNameString;
266 public static String pinnedDomainSslIssuedByCNameString;
267 public static String pinnedDomainSslIssuedByONameString;
268 public static String pinnedDomainSslIssuedByUNameString;
269 public static Date pinnedDomainSslStartDate;
270 public static Date pinnedDomainSslEndDate;
272 // The user agent constants are public static so they can be accessed from `SettingsFragment`, `DomainsActivity`, and `DomainSettingsFragment`.
273 public final static int UNRECOGNIZED_USER_AGENT = -1;
274 public final static int SETTINGS_WEBVIEW_DEFAULT_USER_AGENT = 1;
275 public final static int SETTINGS_CUSTOM_USER_AGENT = 12;
276 public final static int DOMAINS_SYSTEM_DEFAULT_USER_AGENT = 0;
277 public final static int DOMAINS_WEBVIEW_DEFAULT_USER_AGENT = 2;
278 public final static int DOMAINS_CUSTOM_USER_AGENT = 13;
281 // `appBar` is used in `onCreate()`, `onOptionsItemSelected()`, `closeFindOnPage()`, `applyAppSettings()`, and `applyProxyThroughOrbot()`.
282 private ActionBar appBar;
284 // `navigatingHistory` is used in `onCreate()`, `onNavigationItemSelected()`, `onSslMismatchBack()`, and `applyDomainSettings()`.
285 private boolean navigatingHistory;
287 // `favoriteIconDefaultBitmap` is used in `onCreate()` and `applyDomainSettings`.
288 private Bitmap favoriteIconDefaultBitmap;
290 // `drawerLayout` is used in `onCreate()`, `onNewIntent()`, `onBackPressed()`, and `onRestart()`.
291 private DrawerLayout drawerLayout;
293 // `rootCoordinatorLayout` is used in `onCreate()` and `applyAppSettings()`.
294 private CoordinatorLayout rootCoordinatorLayout;
296 // `mainWebView` is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onRestart()`, `onCreateContextMenu()`, `findPreviousOnPage()`,
297 // `findNextOnPage()`, `closeFindOnPage()`, `loadUrlFromTextBox()`, `onSslMismatchBack()`, and `applyProxyThroughOrbot()`.
298 private WebView mainWebView;
300 // `fullScreenVideoFrameLayout` is used in `onCreate()` and `onConfigurationChanged()`.
301 private FrameLayout fullScreenVideoFrameLayout;
303 // `swipeRefreshLayout` is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, and `onRestart()`.
304 private SwipeRefreshLayout swipeRefreshLayout;
306 // `urlAppBarRelativeLayout` is used in `onCreate()` and `applyDomainSettings()`.
307 private RelativeLayout urlAppBarRelativeLayout;
309 // `favoriteIconImageView` is used in `onCreate()` and `applyDomainSettings()`
310 private ImageView favoriteIconImageView;
312 // `cookieManager` is used in `onCreate()`, `onOptionsItemSelected()`, and `onNavigationItemSelected()`, `loadUrlFromTextBox()`, `onDownloadImage()`, `onDownloadFile()`, and `onRestart()`.
313 private CookieManager cookieManager;
315 // `customHeader` is used in `onCreate()`, `onOptionsItemSelected()`, `onCreateContextMenu()`, and `loadUrl()`.
316 private final Map<String, String> customHeaders = new HashMap<>();
318 // `javaScriptEnabled` is also used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, `applyDomainSettings()`, and `updatePrivacyIcons()`.
319 private boolean javaScriptEnabled;
321 // `firstPartyCookiesEnabled` is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, `onDownloadImage()`, `onDownloadFile()`, and `applyDomainSettings()`.
322 private boolean firstPartyCookiesEnabled;
324 // `thirdPartyCookiesEnabled` used in `onCreate()`, `onPrepareOptionsMenu()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, and `applyDomainSettings()`.
325 private boolean thirdPartyCookiesEnabled;
327 // `domStorageEnabled` is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, and `applyDomainSettings()`.
328 private boolean domStorageEnabled;
330 // `saveFormDataEnabled` is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, and `applyDomainSettings()`. It can be removed once the minimum API >= 26.
331 private boolean saveFormDataEnabled;
333 // `nightMode` is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, and `applyDomainSettings()`.
334 private boolean nightMode;
336 // 'homepage' is used in `onCreate()`, `onNavigationItemSelected()`, and `applyProxyThroughOrbot()`.
337 private String homepage;
339 // `searchURL` is used in `loadURLFromTextBox()` and `applyProxyThroughOrbot()`.
340 private String searchURL;
342 // `mainMenu` is used in `onCreateOptionsMenu()` and `updatePrivacyIcons()`.
343 private Menu mainMenu;
345 // `refreshMenuItem` is used in `onCreate()` and `onCreateOptionsMenu()`.
346 private MenuItem refreshMenuItem;
348 // The blocklist menu items are used in `onCreate()`, `onCreateOptionsMenu()`, and `onPrepareOptionsMenu()`.
349 private MenuItem blocklistsMenuItem;
350 private MenuItem easyListMenuItem;
351 private MenuItem easyPrivacyMenuItem;
352 private MenuItem fanboysAnnoyanceListMenuItem;
353 private MenuItem fanboysSocialBlockingListMenuItem;
354 private MenuItem ultraPrivacyMenuItem;
355 private MenuItem blockAllThirdPartyRequestsMenuItem;
357 // The blocklist variables are used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, and `applyAppSettings()`.
358 private boolean easyListEnabled;
359 private boolean easyPrivacyEnabled;
360 private boolean fanboysAnnoyanceListEnabled;
361 private boolean fanboysSocialBlockingListEnabled;
362 private boolean ultraPrivacyEnabled;
364 // `webViewDefaultUserAgent` is used in `onCreate()` and `onPrepareOptionsMenu()`.
365 private String webViewDefaultUserAgent;
367 // `defaultCustomUserAgentString` is used in `onPrepareOptionsMenu()` and `applyDomainSettings()`.
368 private String defaultCustomUserAgentString;
370 // `privacyBrowserRuntime` is used in `onCreate()`, `onOptionsItemSelected()`, and `applyAppSettings()`.
371 private Runtime privacyBrowserRuntime;
373 // `proxyThroughOrbot` is used in `onRestart()`, `onOptionsItemSelected()`, `applyAppSettings()`, and `applyProxyThroughOrbot()`.
374 private boolean proxyThroughOrbot;
376 // `incognitoModeEnabled` is used in `onCreate()` and `applyAppSettings()`.
377 private boolean incognitoModeEnabled;
379 // `fullScreenBrowsingModeEnabled` is used in `onCreate()` and `applyAppSettings()`.
380 private boolean fullScreenBrowsingModeEnabled;
382 // `inFullScreenBrowsingMode` is used in `onCreate()`, `onConfigurationChanged()`, and `applyAppSettings()`.
383 private boolean inFullScreenBrowsingMode;
385 // `hideSystemBarsOnFullscreen` is used in `onCreate()` and `applyAppSettings()`.
386 private boolean hideSystemBarsOnFullscreen;
388 // `translucentNavigationBarOnFullscreen` is used in `onCreate()` and `applyAppSettings()`.
389 private boolean translucentNavigationBarOnFullscreen;
391 // `reapplyDomainSettingsOnRestart` is used in `onCreate()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onRestart()`, and `onAddDomain()`, .
392 private boolean reapplyDomainSettingsOnRestart;
394 // `reapplyAppSettingsOnRestart` is used in `onNavigationItemSelected()` and `onRestart()`.
395 private boolean reapplyAppSettingsOnRestart;
397 // `displayingFullScreenVideo` is used in `onCreate()` and `onResume()`.
398 private boolean displayingFullScreenVideo;
400 // `downloadWithExternalApp` is used in `onCreate()`, `onCreateContextMenu()`, and `applyDomainSettings()`.
401 private boolean downloadWithExternalApp;
403 // `currentDomainName` is used in `onCreate()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onAddDomain()`, and `applyDomainSettings()`.
404 private String currentDomainName;
406 // `ignorePinnedSslCertificateForDomain` is used in `onCreate()`, `onSslMismatchProceed()`, and `applyDomainSettings()`.
407 private boolean ignorePinnedSslCertificate;
409 // `orbotStatusBroadcastReceiver` is used in `onCreate()` and `onDestroy()`.
410 private BroadcastReceiver orbotStatusBroadcastReceiver;
412 // `waitingForOrbot` is used in `onCreate()`, `onResume()`, and `applyProxyThroughOrbot()`.
413 private boolean waitingForOrbot;
415 // `domainSettingsApplied` is used in `prepareOptionsMenu()` and `applyDomainSettings()`.
416 private boolean domainSettingsApplied;
418 // `domainSettingsJavaScriptEnabled` is used in `onOptionsItemSelected()` and `applyDomainSettings()`.
419 private Boolean domainSettingsJavaScriptEnabled;
421 // `waitingForOrbotHtmlString` is used in `onCreate()` and `applyProxyThroughOrbot()`.
422 private String waitingForOrbotHtmlString;
424 // `privateDataDirectoryString` is used in `onCreate()`, `onOptionsItemSelected()`, and `onNavigationItemSelected()`.
425 private String privateDataDirectoryString;
427 // `findOnPageLinearLayout` is used in `onCreate()`, `onOptionsItemSelected()`, and `closeFindOnPage()`.
428 private LinearLayout findOnPageLinearLayout;
430 // `findOnPageEditText` is used in `onCreate()`, `onOptionsItemSelected()`, and `closeFindOnPage()`.
431 private EditText findOnPageEditText;
433 // `displayAdditionalAppBarIcons` is used in `onCreate()` and `onCreateOptionsMenu()`.
434 private boolean displayAdditionalAppBarIcons;
436 // `drawerToggle` is used in `onCreate()`, `onPostCreate()`, `onConfigurationChanged()`, `onNewIntent()`, and `onNavigationItemSelected()`.
437 private ActionBarDrawerToggle drawerToggle;
439 // `supportAppBar` is used in `onCreate()`, `onOptionsItemSelected()`, and `closeFindOnPage()`.
440 private Toolbar supportAppBar;
442 // `urlTextBox` is used in `onCreate()`, `onOptionsItemSelected()`, `loadUrlFromTextBox()`, `loadUrl()`, and `highlightUrlText()`.
443 private EditText urlTextBox;
445 // The color spans are used in `onCreate()` and `highlightUrlText()`.
446 private ForegroundColorSpan redColorSpan;
447 private ForegroundColorSpan initialGrayColorSpan;
448 private ForegroundColorSpan finalGrayColorSpan;
450 // The drawer header padding variables are used in `onCreate()` and `onConfigurationChanged()`.
451 private int drawerHeaderPaddingLeftAndRight;
452 private int drawerHeaderPaddingTop;
453 private int drawerHeaderPaddingBottom;
455 // `sslErrorHandler` is used in `onCreate()`, `onSslErrorCancel()`, and `onSslErrorProceed`.
456 private SslErrorHandler sslErrorHandler;
458 // `httpAuthHandler` is used in `onCreate()`, `onHttpAuthenticationCancel()`, and `onHttpAuthenticationProceed()`.
459 private static HttpAuthHandler httpAuthHandler;
461 // `inputMethodManager` is used in `onOptionsItemSelected()`, `loadUrlFromTextBox()`, and `closeFindOnPage()`.
462 private InputMethodManager inputMethodManager;
464 // `mainWebViewRelativeLayout` is used in `onCreate()` and `onNavigationItemSelected()`.
465 private RelativeLayout mainWebViewRelativeLayout;
467 // `urlIsLoading` is used in `onCreate()`, `onCreateOptionsMenu()`, `loadUrl()`, and `applyDomainSettings()`.
468 private boolean urlIsLoading;
470 // `pinnedDomainSslCertificate` is used in `onCreate()` and `applyDomainSettings()`.
471 private boolean pinnedDomainSslCertificate;
473 // `bookmarksDatabaseHelper` is used in `onCreate()`, `onDestroy`, `onOptionsItemSelected()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`,
474 // and `loadBookmarksFolder()`.
475 private BookmarksDatabaseHelper bookmarksDatabaseHelper;
477 // `bookmarksListView` is used in `onCreate()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, and `loadBookmarksFolder()`.
478 private ListView bookmarksListView;
480 // `bookmarksTitleTextView` is used in `onCreate()` and `loadBookmarksFolder()`.
481 private TextView bookmarksTitleTextView;
483 // `bookmarksCursor` is used in `onDestroy()`, `onOptionsItemSelected()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and `loadBookmarksFolder()`.
484 private Cursor bookmarksCursor;
486 // `bookmarksCursorAdapter` is used in `onCreateBookmark()`, `onCreateBookmarkFolder()` `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and `loadBookmarksFolder()`.
487 private CursorAdapter bookmarksCursorAdapter;
489 // `oldFolderNameString` is used in `onCreate()` and `onSaveEditBookmarkFolder()`.
490 private String oldFolderNameString;
492 // `fileChooserCallback` is used in `onCreate()` and `onActivityResult()`.
493 private ValueCallback<Uri[]> fileChooserCallback;
495 // The download strings are used in `onCreate()` and `onRequestPermissionResult()`.
496 private String downloadUrl;
497 private String downloadContentDisposition;
498 private long downloadContentLength;
500 // `downloadImageUrl` is used in `onCreateContextMenu()` and `onRequestPermissionResult()`.
501 private String downloadImageUrl;
503 // The user agent variables are used in `onCreate()` and `applyDomainSettings()`.
504 private ArrayAdapter<CharSequence> userAgentNamesArray;
505 private String[] userAgentDataArray;
507 // The request codes are used in `onCreate()`, `onCreateContextMenu()`, `onCloseDownloadLocationPermissionDialog()`, and `onRequestPermissionResult()`.
508 private final int DOWNLOAD_FILE_REQUEST_CODE = 1;
509 private final int DOWNLOAD_IMAGE_REQUEST_CODE = 2;
512 // 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.
513 // Also, remove the warning about needing to override `performClick()` when using an `OnTouchListener` with `WebView`.
514 @SuppressLint({"SetJavaScriptEnabled", "ClickableViewAccessibility"})
515 // Remove Android Studio's warning about deprecations. The deprecated `getColor()` must be used until API >= 23.
516 @SuppressWarnings("deprecation")
517 protected void onCreate(Bundle savedInstanceState) {
518 // Get a handle for the shared preferences.
519 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
521 // Get the theme and screenshot preferences.
522 darkTheme = sharedPreferences.getBoolean("dark_theme", false);
523 allowScreenshots = sharedPreferences.getBoolean("allow_screenshots", false);
525 // Disable screenshots if not allowed.
526 if (!allowScreenshots) {
527 getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE);
530 // Set the activity theme.
532 setTheme(R.style.PrivacyBrowserDark);
534 setTheme(R.style.PrivacyBrowserLight);
537 // Run the default commands.
538 super.onCreate(savedInstanceState);
540 // Set the content view.
541 setContentView(R.layout.main_drawerlayout);
543 // Get a handle for the resources.
544 Resources resources = getResources();
546 // Get a handle for `inputMethodManager`.
547 inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
549 // `SupportActionBar` from `android.support.v7.app.ActionBar` must be used until the minimum API is >= 21.
550 supportAppBar = findViewById(R.id.app_bar);
551 setSupportActionBar(supportAppBar);
552 appBar = getSupportActionBar();
554 // This is needed to get rid of the Android Studio warning that `appBar` might be null.
555 assert appBar != null;
557 // Add the custom `url_app_bar` layout, which shows the favorite icon and the URL text bar.
558 appBar.setCustomView(R.layout.url_app_bar);
559 appBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM);
561 // Initialize the foreground color spans for highlighting the URLs. We have to use the deprecated `getColor()` until API >= 23.
562 redColorSpan = new ForegroundColorSpan(resources.getColor(R.color.red_a700));
563 initialGrayColorSpan = new ForegroundColorSpan(resources.getColor(R.color.gray_500));
564 finalGrayColorSpan = new ForegroundColorSpan(resources.getColor(R.color.gray_500));
566 // Get a handle for `urlTextBox`.
567 urlTextBox = findViewById(R.id.url_edittext);
569 // Remove the formatting from `urlTextBar` when the user is editing the text.
570 urlTextBox.setOnFocusChangeListener((View v, boolean hasFocus) -> {
571 if (hasFocus) { // The user is editing the URL text box.
572 // Remove the highlighting.
573 urlTextBox.getText().removeSpan(redColorSpan);
574 urlTextBox.getText().removeSpan(initialGrayColorSpan);
575 urlTextBox.getText().removeSpan(finalGrayColorSpan);
576 } else { // The user has stopped editing the URL text box.
577 // Move to the beginning of the string.
578 urlTextBox.setSelection(0);
580 // Reapply the highlighting.
585 // Set the go button on the keyboard to load the URL in `urlTextBox`.
586 urlTextBox.setOnKeyListener((View v, int keyCode, KeyEvent event) -> {
587 // If the event is a key-down event on the `enter` button, load the URL.
588 if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) {
589 // Load the URL into the mainWebView and consume the event.
590 loadUrlFromTextBox();
592 // If the enter key was pressed, consume the event.
595 // If any other key was pressed, do not consume the event.
600 // Set `waitingForOrbotHTMLString`.
601 waitingForOrbotHtmlString = "<html><body><br/><center><h1>" + getString(R.string.waiting_for_orbot) + "</h1></center></body></html>";
603 // Initialize `currentDomainName`, `orbotStatus`, and `waitingForOrbot`.
604 currentDomainName = "";
605 orbotStatus = "unknown";
606 waitingForOrbot = false;
608 // Create an Orbot status `BroadcastReceiver`.
609 orbotStatusBroadcastReceiver = new BroadcastReceiver() {
611 public void onReceive(Context context, Intent intent) {
612 // Store the content of the status message in `orbotStatus`.
613 orbotStatus = intent.getStringExtra("org.torproject.android.intent.extra.STATUS");
615 // If Privacy Browser is waiting on Orbot, load the website now that Orbot is connected.
616 if (orbotStatus.equals("ON") && waitingForOrbot) {
617 // Reset `waitingForOrbot`.
618 waitingForOrbot = false;
620 // Load `formattedUrlString
621 loadUrl(formattedUrlString);
626 // Register `orbotStatusBroadcastReceiver` on `this` context.
627 this.registerReceiver(orbotStatusBroadcastReceiver, new IntentFilter("org.torproject.android.intent.action.STATUS"));
629 // Get handles for views that need to be accessed.
630 drawerLayout = findViewById(R.id.drawerlayout);
631 rootCoordinatorLayout = findViewById(R.id.root_coordinatorlayout);
632 bookmarksListView = findViewById(R.id.bookmarks_drawer_listview);
633 bookmarksTitleTextView = findViewById(R.id.bookmarks_title_textview);
634 FloatingActionButton launchBookmarksActivityFab = findViewById(R.id.launch_bookmarks_activity_fab);
635 FloatingActionButton createBookmarkFolderFab = findViewById(R.id.create_bookmark_folder_fab);
636 FloatingActionButton createBookmarkFab = findViewById(R.id.create_bookmark_fab);
637 mainWebViewRelativeLayout = findViewById(R.id.main_webview_relativelayout);
638 mainWebView = findViewById(R.id.main_webview);
639 findOnPageLinearLayout = findViewById(R.id.find_on_page_linearlayout);
640 findOnPageEditText = findViewById(R.id.find_on_page_edittext);
641 fullScreenVideoFrameLayout = findViewById(R.id.full_screen_video_framelayout);
642 urlAppBarRelativeLayout = findViewById(R.id.url_app_bar_relativelayout);
643 favoriteIconImageView = findViewById(R.id.favorite_icon);
645 // 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.
647 launchBookmarksActivityFab.setImageDrawable(resources.getDrawable(R.drawable.bookmarks_dark));
648 createBookmarkFolderFab.setImageDrawable(resources.getDrawable(R.drawable.create_folder_dark));
649 createBookmarkFab.setImageDrawable(resources.getDrawable(R.drawable.create_bookmark_dark));
650 bookmarksListView.setBackgroundColor(resources.getColor(R.color.gray_850));
652 launchBookmarksActivityFab.setImageDrawable(resources.getDrawable(R.drawable.bookmarks_light));
653 createBookmarkFolderFab.setImageDrawable(resources.getDrawable(R.drawable.create_folder_light));
654 createBookmarkFab.setImageDrawable(resources.getDrawable(R.drawable.create_bookmark_light));
655 bookmarksListView.setBackgroundColor(resources.getColor(R.color.white));
658 // Set the launch bookmarks activity FAB to launch the bookmarks activity.
659 launchBookmarksActivityFab.setOnClickListener(v -> {
660 // Create an intent to launch the bookmarks activity.
661 Intent bookmarksIntent = new Intent(getApplicationContext(), BookmarksActivity.class);
663 // Include the current folder with the `Intent`.
664 bookmarksIntent.putExtra("Current Folder", currentBookmarksFolder);
667 startActivity(bookmarksIntent);
670 // Set the create new bookmark folder FAB to display an alert dialog.
671 createBookmarkFolderFab.setOnClickListener(v -> {
672 // Show the `CreateBookmarkFolderDialog` `AlertDialog` and name the instance `@string/create_folder`.
673 AppCompatDialogFragment createBookmarkFolderDialog = new CreateBookmarkFolderDialog();
674 createBookmarkFolderDialog.show(getSupportFragmentManager(), resources.getString(R.string.create_folder));
677 // Set the create new bookmark FAB to display an alert dialog.
678 createBookmarkFab.setOnClickListener(view -> {
679 // Show the `CreateBookmarkDialog` `AlertDialog` and name the instance `@string/create_bookmark`.
680 AppCompatDialogFragment createBookmarkDialog = new CreateBookmarkDialog();
681 createBookmarkDialog.show(getSupportFragmentManager(), resources.getString(R.string.create_bookmark));
684 // Create a double-tap listener to toggle full-screen mode.
685 final GestureDetector gestureDetector = new GestureDetector(this, new GestureDetector.SimpleOnGestureListener() {
686 // Override `onDoubleTap()`. All other events are handled using the default settings.
688 public boolean onDoubleTap(MotionEvent event) {
689 if (fullScreenBrowsingModeEnabled) { // Only process the double-tap if full screen browsing mode is enabled.
690 // Toggle `inFullScreenBrowsingMode`.
691 inFullScreenBrowsingMode = !inFullScreenBrowsingMode;
693 if (inFullScreenBrowsingMode) { // Switch to full screen mode.
694 // Hide the `appBar`.
697 // Hide the banner ad in the free flavor.
698 if (BuildConfig.FLAVOR.contentEquals("free")) {
699 // The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations.
700 AdHelper.hideAd(findViewById(R.id.adview));
703 // Modify the system bars.
704 if (hideSystemBarsOnFullscreen) { // Hide everything.
705 // Remove the translucent overlays.
706 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
708 // Remove the translucent status bar overlay on the `Drawer Layout`, which is special and needs its own command.
709 drawerLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
711 /* SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
712 * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
713 * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
715 rootCoordinatorLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
717 // Set `rootCoordinatorLayout` to fill the whole screen.
718 rootCoordinatorLayout.setFitsSystemWindows(false);
719 } else { // Hide everything except the status and navigation bars.
720 // Set `rootCoordinatorLayout` to fit under the status and navigation bars.
721 rootCoordinatorLayout.setFitsSystemWindows(false);
723 // 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.
724 if (translucentNavigationBarOnFullscreen) {
725 // Set the navigation bar to be translucent.
726 getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
729 } else { // Switch to normal viewing mode.
730 // Show the `appBar`.
733 // Show the `BannerAd` in the free flavor.
734 if (BuildConfig.FLAVOR.contentEquals("free")) {
735 // Reload the ad. The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations.
736 AdHelper.loadAd(findViewById(R.id.adview), getApplicationContext(), getString(R.string.ad_unit_id));
739 // Remove the translucent navigation bar flag if it is set.
740 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
742 // Add the translucent status flag if it is unset. This also resets `drawerLayout's` `View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN`.
743 getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
745 // Remove any `SYSTEM_UI` flags from `rootCoordinatorLayout`.
746 rootCoordinatorLayout.setSystemUiVisibility(0);
748 // Constrain `rootCoordinatorLayout` inside the status and navigation bars.
749 rootCoordinatorLayout.setFitsSystemWindows(true);
752 // Consume the double-tap.
754 } else { // Do not consume the double-tap because full screen browsing mode is disabled.
760 // Pass all touch events on `mainWebView` through `gestureDetector` to check for double-taps.
761 mainWebView.setOnTouchListener((View v, MotionEvent event) -> {
762 // Call `performClick()` on the view, which is required for accessibility.
765 // Send the `event` to `gestureDetector`.
766 return gestureDetector.onTouchEvent(event);
769 // Update `findOnPageCountTextView`.
770 mainWebView.setFindListener(new WebView.FindListener() {
771 // Get a handle for `findOnPageCountTextView`.
772 final TextView findOnPageCountTextView = (TextView) findViewById(R.id.find_on_page_count_textview);
775 public void onFindResultReceived(int activeMatchOrdinal, int numberOfMatches, boolean isDoneCounting) {
776 if ((isDoneCounting) && (numberOfMatches == 0)) { // There are no matches.
777 // Set `findOnPageCountTextView` to `0/0`.
778 findOnPageCountTextView.setText(R.string.zero_of_zero);
779 } else if (isDoneCounting) { // There are matches.
780 // `activeMatchOrdinal` is zero-based.
781 int activeMatch = activeMatchOrdinal + 1;
783 // Build the match string.
784 String matchString = activeMatch + "/" + numberOfMatches;
786 // Set `findOnPageCountTextView`.
787 findOnPageCountTextView.setText(matchString);
792 // Search for the string on the page whenever a character changes in the `findOnPageEditText`.
793 findOnPageEditText.addTextChangedListener(new TextWatcher() {
795 public void beforeTextChanged(CharSequence s, int start, int count, int after) {
800 public void onTextChanged(CharSequence s, int start, int before, int count) {
805 public void afterTextChanged(Editable s) {
806 // Search for the text in `mainWebView`.
807 mainWebView.findAllAsync(findOnPageEditText.getText().toString());
811 // Set the `check mark` button for the `findOnPageEditText` keyboard to close the soft keyboard.
812 findOnPageEditText.setOnKeyListener((v, keyCode, event) -> {
813 if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) { // The `enter` key was pressed.
814 // Hide the soft keyboard. `0` indicates no additional flags.
815 inputMethodManager.hideSoftInputFromWindow(mainWebView.getWindowToken(), 0);
817 // Consume the event.
819 } else { // A different key was pressed.
820 // Do not consume the event.
825 // Implement swipe to refresh.
826 swipeRefreshLayout = findViewById(R.id.swiperefreshlayout);
827 swipeRefreshLayout.setOnRefreshListener(() -> mainWebView.reload());
829 // Set the swipe to refresh color according to the theme.
831 swipeRefreshLayout.setColorSchemeResources(R.color.blue_600);
832 swipeRefreshLayout.setProgressBackgroundColorSchemeResource(R.color.gray_850);
834 swipeRefreshLayout.setColorSchemeResources(R.color.blue_700);
837 // `DrawerTitle` identifies the `DrawerLayouts` in accessibility mode.
838 drawerLayout.setDrawerTitle(GravityCompat.START, getString(R.string.navigation_drawer));
839 drawerLayout.setDrawerTitle(GravityCompat.END, getString(R.string.bookmarks));
841 // Listen for touches on the navigation menu.
842 final NavigationView navigationView = findViewById(R.id.navigationview);
843 navigationView.setNavigationItemSelectedListener(this);
845 // 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.
846 final Menu navigationMenu = navigationView.getMenu();
847 final MenuItem navigationBackMenuItem = navigationMenu.getItem(1);
848 final MenuItem navigationForwardMenuItem = navigationMenu.getItem(2);
849 final MenuItem navigationHistoryMenuItem = navigationMenu.getItem(3);
850 final MenuItem navigationRequestsMenuItem = navigationMenu.getItem(4);
852 // Initialize the bookmarks database helper. The `0` specifies a database version, but that is ignored and set instead using a constant in `BookmarksDatabaseHelper`.
853 bookmarksDatabaseHelper = new BookmarksDatabaseHelper(this, null, null, 0);
855 // Initialize `currentBookmarksFolder`. `""` is the home folder in the database.
856 currentBookmarksFolder = "";
858 // Load the home folder, which is `""` in the database.
859 loadBookmarksFolder();
861 bookmarksListView.setOnItemClickListener((parent, view, position, id) -> {
862 // Convert the id from long to int to match the format of the bookmarks database.
863 int databaseID = (int) id;
865 // Get the bookmark cursor for this ID and move it to the first row.
866 Cursor bookmarkCursor = bookmarksDatabaseHelper.getBookmarkCursor(databaseID);
867 bookmarkCursor.moveToFirst();
869 // Act upon the bookmark according to the type.
870 if (bookmarkCursor.getInt(bookmarkCursor.getColumnIndex(BookmarksDatabaseHelper.IS_FOLDER)) == 1) { // The selected bookmark is a folder.
871 // Store the new folder name in `currentBookmarksFolder`.
872 currentBookmarksFolder = bookmarkCursor.getString(bookmarkCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME));
874 // Load the new folder.
875 loadBookmarksFolder();
876 } else { // The selected bookmark is not a folder.
877 // Load the bookmark URL.
878 loadUrl(bookmarkCursor.getString(bookmarkCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_URL)));
880 // Close the bookmarks drawer.
881 drawerLayout.closeDrawer(GravityCompat.END);
884 // Close the `Cursor`.
885 bookmarkCursor.close();
888 bookmarksListView.setOnItemLongClickListener((parent, view, position, id) -> {
889 // Convert the database ID from `long` to `int`.
890 int databaseId = (int) id;
892 // Find out if the selected bookmark is a folder.
893 boolean isFolder = bookmarksDatabaseHelper.isFolder(databaseId);
896 // Save the current folder name, which is used in `onSaveEditBookmarkFolder()`.
897 oldFolderNameString = bookmarksCursor.getString(bookmarksCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME));
899 // Show the edit bookmark folder `AlertDialog` and name the instance `@string/edit_folder`.
900 AppCompatDialogFragment editFolderDialog = EditBookmarkFolderDialog.folderDatabaseId(databaseId);
901 editFolderDialog.show(getSupportFragmentManager(), resources.getString(R.string.edit_folder));
903 // Show the edit bookmark `AlertDialog` and name the instance `@string/edit_bookmark`.
904 AppCompatDialogFragment editBookmarkDialog = EditBookmarkDialog.bookmarkDatabaseId(databaseId);
905 editBookmarkDialog.show(getSupportFragmentManager(), resources.getString(R.string.edit_bookmark));
908 // Consume the event.
912 // Get the status bar pixel size.
913 int statusBarResourceId = resources.getIdentifier("status_bar_height", "dimen", "android");
914 int statusBarPixelSize = resources.getDimensionPixelSize(statusBarResourceId);
916 // Get the resource density.
917 float screenDensity = resources.getDisplayMetrics().density;
919 // Calculate the drawer header padding. This is used to move the text in the drawer headers below any cutouts.
920 drawerHeaderPaddingLeftAndRight = (int) (15 * screenDensity);
921 drawerHeaderPaddingTop = statusBarPixelSize + (int) (4 * screenDensity);
922 drawerHeaderPaddingBottom = (int) (8 * screenDensity);
924 // The drawer listener is used to update the navigation menu.`
925 drawerLayout.addDrawerListener(new DrawerLayout.DrawerListener() {
927 public void onDrawerSlide(@NonNull View drawerView, float slideOffset) {
931 public void onDrawerOpened(@NonNull View drawerView) {
935 public void onDrawerClosed(@NonNull View drawerView) {
939 public void onDrawerStateChanged(int newState) {
940 if ((newState == DrawerLayout.STATE_SETTLING) || (newState == DrawerLayout.STATE_DRAGGING)) { // A drawer is opening or closing.
941 // Get handles for the drawer headers.
942 TextView navigationHeaderTextView = findViewById(R.id.navigationText);
943 TextView bookmarksHeaderTextView = findViewById(R.id.bookmarks_title_textview);
945 // 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.
946 if (navigationHeaderTextView != null) {
947 navigationHeaderTextView.setPadding(drawerHeaderPaddingLeftAndRight, drawerHeaderPaddingTop, drawerHeaderPaddingLeftAndRight, drawerHeaderPaddingBottom);
950 // 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.
951 if (bookmarksHeaderTextView != null) {
952 bookmarksHeaderTextView.setPadding(drawerHeaderPaddingLeftAndRight, drawerHeaderPaddingTop, drawerHeaderPaddingLeftAndRight, drawerHeaderPaddingBottom);
955 // Update the back, forward, history, and requests menu items.
956 navigationBackMenuItem.setEnabled(mainWebView.canGoBack());
957 navigationForwardMenuItem.setEnabled(mainWebView.canGoForward());
958 navigationHistoryMenuItem.setEnabled((mainWebView.canGoBack() || mainWebView.canGoForward()));
959 navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
961 // Hide the keyboard (if displayed).
962 inputMethodManager.hideSoftInputFromWindow(mainWebView.getWindowToken(), 0);
964 // 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.
965 urlTextBox.clearFocus();
966 mainWebView.clearFocus();
971 // drawerToggle creates the hamburger icon at the start of the AppBar.
972 drawerToggle = new ActionBarDrawerToggle(this, drawerLayout, supportAppBar, R.string.open_navigation_drawer, R.string.close_navigation_drawer);
974 // Get a handle for the progress bar.
975 final ProgressBar progressBar = findViewById(R.id.progress_bar);
977 mainWebView.setWebChromeClient(new WebChromeClient() {
978 // Update the progress bar when a page is loading.
980 public void onProgressChanged(WebView view, int progress) {
981 // Inject the night mode CSS if night mode is enabled.
983 // `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
984 // used by WordPress. `text-decoration: none` removes all text underlines. `text-shadow: none` removes text shadows, which usually have a hard coded color.
985 // `border: none` removes all borders, which can also be used to underline text.
986 // `a {color: #1565C0}` sets links to be a dark blue. `!important` takes precedent over any existing sub-settings.
987 mainWebView.evaluateJavascript("(function() {var parent = document.getElementsByTagName('head').item(0); var style = document.createElement('style'); style.type = 'text/css'; " +
988 "style.innerHTML = '* {background-color: #212121 !important; color: #BDBDBD !important; box-shadow: none !important; text-decoration: none !important;" +
989 "text-shadow: none !important; border: none !important;} a {color: #1565C0 !important;}'; parent.appendChild(style)})()", value -> {
990 // Initialize a handler to display `mainWebView`.
991 Handler displayWebViewHandler = new Handler();
993 // Setup a runnable to display `mainWebView` after a delay to allow the CSS to be applied.
994 Runnable displayWebViewRunnable = () -> {
995 // Only display `mainWebView` if the progress bar is gone. This prevents the display of the `WebView` while it is still loading.
996 if (progressBar.getVisibility() == View.GONE) {
997 mainWebView.setVisibility(View.VISIBLE);
1001 // Displaying of `mainWebView` after 500 milliseconds.
1002 displayWebViewHandler.postDelayed(displayWebViewRunnable, 500);
1006 // Update the progress bar.
1007 progressBar.setProgress(progress);
1009 // Set the visibility of the progress bar.
1010 if (progress < 100) {
1011 // Show the progress bar.
1012 progressBar.setVisibility(View.VISIBLE);
1014 // Hide the progress bar.
1015 progressBar.setVisibility(View.GONE);
1017 // Display `mainWebView` if night mode is disabled.
1018 // 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
1019 // currently enabled.
1021 mainWebView.setVisibility(View.VISIBLE);
1024 //Stop the swipe to refresh indicator if it is running
1025 swipeRefreshLayout.setRefreshing(false);
1029 // Set the favorite icon when it changes.
1031 public void onReceivedIcon(WebView view, Bitmap icon) {
1032 // Only update the favorite icon if the website has finished loading.
1033 if (progressBar.getVisibility() == View.GONE) {
1034 // Save a copy of the favorite icon.
1035 favoriteIconBitmap = icon;
1037 // Place the favorite icon in the appBar.
1038 favoriteIconImageView.setImageBitmap(Bitmap.createScaledBitmap(icon, 64, 64, true));
1042 // Save a copy of the title when it changes.
1044 public void onReceivedTitle(WebView view, String title) {
1045 // Save a copy of the title.
1046 webViewTitle = title;
1049 // Enter full screen video.
1051 public void onShowCustomView(View view, CustomViewCallback callback) {
1052 // Set the full screen video flag.
1053 displayingFullScreenVideo = true;
1055 // Pause the ad if this is the free flavor.
1056 if (BuildConfig.FLAVOR.contentEquals("free")) {
1057 // The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations.
1058 AdHelper.pauseAd(findViewById(R.id.adview));
1061 // Remove the translucent overlays.
1062 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
1064 // Remove the translucent status bar overlay on the `Drawer Layout`, which is special and needs its own command.
1065 drawerLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
1067 /* SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
1068 * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
1069 * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
1071 rootCoordinatorLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
1073 // Set `rootCoordinatorLayout` to fill the entire screen.
1074 rootCoordinatorLayout.setFitsSystemWindows(false);
1076 // Disable the sliding drawers.
1077 drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED);
1079 // Add `view` to `fullScreenVideoFrameLayout` and display it on the screen.
1080 fullScreenVideoFrameLayout.addView(view);
1081 fullScreenVideoFrameLayout.setVisibility(View.VISIBLE);
1084 // Exit full screen video.
1086 public void onHideCustomView() {
1087 // Unset the full screen video flag.
1088 displayingFullScreenVideo = false;
1090 // Hide `fullScreenVideoFrameLayout`.
1091 fullScreenVideoFrameLayout.removeAllViews();
1092 fullScreenVideoFrameLayout.setVisibility(View.GONE);
1094 // Enable the sliding drawers.
1095 drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED);
1097 // Apply the appropriate full screen mode the `SYSTEM_UI` flags.
1098 if (fullScreenBrowsingModeEnabled && inFullScreenBrowsingMode) { // Privacy Browser is currently in full screen browsing mode.
1099 if (hideSystemBarsOnFullscreen) { // Hide everything.
1100 // Remove the translucent navigation setting if it is currently flagged.
1101 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
1103 // Remove the translucent status bar overlay.
1104 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
1106 // Remove the translucent status bar overlay on the `Drawer Layout`, which is special and needs its own command.
1107 drawerLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
1109 /* SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
1110 * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
1111 * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
1113 rootCoordinatorLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
1114 } else { // Hide everything except the status and navigation bars.
1115 // Remove any `SYSTEM_UI` flags from `rootCoordinatorLayout`.
1116 rootCoordinatorLayout.setSystemUiVisibility(0);
1118 // Add the translucent status flag if it is unset.
1119 getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
1121 if (translucentNavigationBarOnFullscreen) {
1122 // Set the navigation bar to be translucent. This also resets `drawerLayout's` `View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN`.
1123 getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
1125 // Set the navigation bar to be black.
1126 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
1129 } else { // Switch to normal viewing mode.
1130 // Show the `appBar` if `findOnPageLinearLayout` is not visible.
1131 if (findOnPageLinearLayout.getVisibility() == View.GONE) {
1135 // Show the `BannerAd` in the free flavor.
1136 if (BuildConfig.FLAVOR.contentEquals("free")) {
1137 // Initialize the ad. The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations.
1138 AdHelper.initializeAds(findViewById(R.id.adview), getApplicationContext(), getFragmentManager(), getString(R.string.google_app_id), getString(R.string.ad_unit_id));
1141 // Remove any `SYSTEM_UI` flags from `rootCoordinatorLayout`.
1142 rootCoordinatorLayout.setSystemUiVisibility(0);
1144 // Remove the translucent navigation bar flag if it is set.
1145 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
1147 // Add the translucent status flag if it is unset. This also resets `drawerLayout's` `View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN`.
1148 getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
1150 // Constrain `rootCoordinatorLayout` inside the status and navigation bars.
1151 rootCoordinatorLayout.setFitsSystemWindows(true);
1154 // Show the ad if this is the free flavor.
1155 if (BuildConfig.FLAVOR.contentEquals("free")) {
1156 // Reload the ad. The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations.
1157 AdHelper.loadAd(findViewById(R.id.adview), getApplicationContext(), getString(R.string.ad_unit_id));
1163 public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) {
1164 // Show the file chooser if the device is running API >= 21.
1165 if (Build.VERSION.SDK_INT >= 21) {
1166 // Store the file path callback.
1167 fileChooserCallback = filePathCallback;
1169 // Create an intent to open a chooser based ont the file chooser parameters.
1170 Intent fileChooserIntent = fileChooserParams.createIntent();
1172 // Open the file chooser. Currently only one `startActivityForResult` exists in this activity, so the request code, used to differentiate them, is simply `0`.
1173 startActivityForResult(fileChooserIntent, 0);
1179 // Register `mainWebView` for a context menu. This is used to see link targets and download images.
1180 registerForContextMenu(mainWebView);
1182 // Allow the downloading of files.
1183 mainWebView.setDownloadListener((String url, String userAgent, String contentDisposition, String mimetype, long contentLength) -> {
1184 // Check if the download should be processed by an external app.
1185 if (downloadWithExternalApp) { // Download with an external app.
1186 openUrlWithExternalApp(url);
1187 } else { // Download with Android's download manager.
1188 // Check to see if the WRITE_EXTERNAL_STORAGE permission has already been granted.
1189 if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) { // The storage permission has not been granted.
1190 // The WRITE_EXTERNAL_STORAGE permission needs to be requested.
1192 // Store the variables for future use by `onRequestPermissionsResult()`.
1194 downloadContentDisposition = contentDisposition;
1195 downloadContentLength = contentLength;
1197 // Show a dialog if the user has previously denied the permission.
1198 if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) { // Show a dialog explaining the request first.
1199 // Instantiate the download location permission alert dialog and set the download type to DOWNLOAD_FILE.
1200 DialogFragment downloadLocationPermissionDialogFragment = DownloadLocationPermissionDialog.downloadType(DownloadLocationPermissionDialog.DOWNLOAD_FILE);
1202 // Show the download location permission alert dialog. The permission will be requested when the the dialog is closed.
1203 downloadLocationPermissionDialogFragment.show(getFragmentManager(), getString(R.string.download_location));
1204 } else { // Show the permission request directly.
1205 // Request the permission. The download dialog will be launched by `onRequestPermissionResult()`.
1206 ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_FILE_REQUEST_CODE);
1208 } else { // The storage permission has already been granted.
1209 // Get a handle for the download file alert dialog.
1210 AppCompatDialogFragment downloadFileDialogFragment = DownloadFileDialog.fromUrl(url, contentDisposition, contentLength);
1212 // Show the download file alert dialog.
1213 downloadFileDialogFragment.show(getSupportFragmentManager(), getString(R.string.download));
1218 // Allow pinch to zoom.
1219 mainWebView.getSettings().setBuiltInZoomControls(true);
1221 // Hide zoom controls.
1222 mainWebView.getSettings().setDisplayZoomControls(false);
1224 // Set `mainWebView` to use a wide viewport. Otherwise, some web pages will be scrunched and some content will render outside the screen.
1225 mainWebView.getSettings().setUseWideViewPort(true);
1227 // Set `mainWebView` to load in overview mode (zoomed out to the maximum width).
1228 mainWebView.getSettings().setLoadWithOverviewMode(true);
1230 // Explicitly disable geolocation.
1231 mainWebView.getSettings().setGeolocationEnabled(false);
1233 // Initialize cookieManager.
1234 cookieManager = CookieManager.getInstance();
1236 // 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).
1237 customHeaders.put("X-Requested-With", "");
1239 // Initialize the default preference values the first time the program is run. `false` keeps this command from resetting any current preferences back to default.
1240 PreferenceManager.setDefaultValues(this, R.xml.preferences, false);
1242 // Get a handle for the `Runtime`.
1243 privacyBrowserRuntime = Runtime.getRuntime();
1245 // Store the application's private data directory.
1246 privateDataDirectoryString = getApplicationInfo().dataDir;
1247 // `dataDir` will vary, but will be something like `/data/user/0/com.stoutner.privacybrowser.standard`, which links to `/data/data/com.stoutner.privacybrowser.standard`.
1249 // Initialize `inFullScreenBrowsingMode`, which is always false at this point because Privacy Browser never starts in full screen browsing mode.
1250 inFullScreenBrowsingMode = false;
1252 // Initialize the privacy settings variables.
1253 javaScriptEnabled = false;
1254 firstPartyCookiesEnabled = false;
1255 thirdPartyCookiesEnabled = false;
1256 domStorageEnabled = false;
1257 saveFormDataEnabled = false; // Form data can be removed once the minimum API >= 26.
1260 // Store the default user agent.
1261 webViewDefaultUserAgent = mainWebView.getSettings().getUserAgentString();
1263 // Initialize the WebView title.
1264 webViewTitle = getString(R.string.no_title);
1266 // Initialize the favorite icon bitmap. `ContextCompat` must be used until API >= 21.
1267 Drawable favoriteIconDrawable = ContextCompat.getDrawable(getApplicationContext(), R.drawable.world);
1268 BitmapDrawable favoriteIconBitmapDrawable = (BitmapDrawable) favoriteIconDrawable;
1269 assert favoriteIconBitmapDrawable != null;
1270 favoriteIconDefaultBitmap = favoriteIconBitmapDrawable.getBitmap();
1272 // If the favorite icon is null, load the default.
1273 if (favoriteIconBitmap == null) {
1274 favoriteIconBitmap = favoriteIconDefaultBitmap;
1277 // Initialize the user agent array adapter and string array.
1278 userAgentNamesArray = ArrayAdapter.createFromResource(this, R.array.user_agent_names, R.layout.spinner_item);
1279 userAgentDataArray = resources.getStringArray(R.array.user_agent_data);
1281 // Apply the app settings from the shared preferences.
1284 // Instantiate the block list helper.
1285 BlockListHelper blockListHelper = new BlockListHelper();
1287 // Initialize the list of resource requests.
1288 resourceRequests = new ArrayList<>();
1290 // Parse the block lists.
1291 final ArrayList<List<String[]>> easyList = blockListHelper.parseBlockList(getAssets(), "blocklists/easylist.txt");
1292 final ArrayList<List<String[]>> easyPrivacy = blockListHelper.parseBlockList(getAssets(), "blocklists/easyprivacy.txt");
1293 final ArrayList<List<String[]>> fanboysAnnoyanceList = blockListHelper.parseBlockList(getAssets(), "blocklists/fanboy-annoyance.txt");
1294 final ArrayList<List<String[]>> fanboysSocialList = blockListHelper.parseBlockList(getAssets(), "blocklists/fanboy-social.txt");
1295 final ArrayList<List<String[]>> ultraPrivacy = blockListHelper.parseBlockList(getAssets(), "blocklists/ultraprivacy.txt");
1297 // Store the list versions.
1298 easyListVersion = easyList.get(0).get(0)[0];
1299 easyPrivacyVersion = easyPrivacy.get(0).get(0)[0];
1300 fanboysAnnoyanceVersion = fanboysAnnoyanceList.get(0).get(0)[0];
1301 fanboysSocialVersion = fanboysSocialList.get(0).get(0)[0];
1302 ultraPrivacyVersion = ultraPrivacy.get(0).get(0)[0];
1304 // Get a handle for the activity. This is used to update the requests counter while the navigation menu is open.
1305 Activity activity = this;
1307 mainWebView.setWebViewClient(new WebViewClient() {
1308 // `shouldOverrideUrlLoading` makes this `WebView` the default handler for URLs inside the app, so that links are not kicked out to other apps.
1309 // The deprecated `shouldOverrideUrlLoading` must be used until API >= 24.
1310 @SuppressWarnings("deprecation")
1312 public boolean shouldOverrideUrlLoading(WebView view, String url) {
1313 if (url.startsWith("http")) { // Load the URL in Privacy Browser.
1314 // Reset the formatted URL string so the page will load correctly if blocking of third-party requests is enabled.
1315 formattedUrlString = "";
1317 // Apply the domain settings for the new URL. `applyDomainSettings` doesn't do anything if the domain has not changed.
1318 boolean userAgentChanged = applyDomainSettings(url, true, false);
1320 // Check if the user agent has changed.
1321 if (userAgentChanged) {
1322 // Manually load the URL. The changing of the user agent will cause WebView to reload the previous URL.
1323 mainWebView.loadUrl(url, customHeaders);
1325 // Returning true indicates that Privacy Browser is manually handling the loading of the URL.
1328 // Returning false causes the current WebView to handle the URL and prevents it from adding redirects to the history list.
1331 } else if (url.startsWith("mailto:")) { // Load the email address in an external email program.
1332 // Use `ACTION_SENDTO` instead of `ACTION_SEND` so that only email programs are launched.
1333 Intent emailIntent = new Intent(Intent.ACTION_SENDTO);
1335 // Parse the url and set it as the data for the intent.
1336 emailIntent.setData(Uri.parse(url));
1338 // Open the email program in a new task instead of as part of Privacy Browser.
1339 emailIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
1342 startActivity(emailIntent);
1344 // Returning true indicates Privacy Browser is handling the URL by creating an intent.
1346 } else if (url.startsWith("tel:")) { // Load the phone number in the dialer.
1347 // Open the dialer and load the phone number, but wait for the user to place the call.
1348 Intent dialIntent = new Intent(Intent.ACTION_DIAL);
1350 // Add the phone number to the intent.
1351 dialIntent.setData(Uri.parse(url));
1353 // Open the dialer in a new task instead of as part of Privacy Browser.
1354 dialIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
1357 startActivity(dialIntent);
1359 // Returning true indicates Privacy Browser is handling the URL by creating an intent.
1361 } else { // Load a system chooser to select an app that can handle the URL.
1362 // Open an app that can handle the URL.
1363 Intent genericIntent = new Intent(Intent.ACTION_VIEW);
1365 // Add the URL to the intent.
1366 genericIntent.setData(Uri.parse(url));
1368 // List all apps that can handle the URL instead of just opening the first one.
1369 genericIntent.addCategory(Intent.CATEGORY_BROWSABLE);
1371 // Open the app in a new task instead of as part of Privacy Browser.
1372 genericIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
1374 // Start the app or display a snackbar if no app is available to handle the URL.
1376 startActivity(genericIntent);
1377 } catch (ActivityNotFoundException exception) {
1378 Snackbar.make(mainWebView, getString(R.string.unrecognized_url) + " " + url, Snackbar.LENGTH_SHORT).show();
1381 // Returning true indicates Privacy Browser is handling the URL by creating an intent.
1386 // Check requests against the block lists. The deprecated `shouldInterceptRequest()` must be used until minimum API >= 21.
1387 @SuppressWarnings("deprecation")
1389 public WebResourceResponse shouldInterceptRequest(WebView view, String url){
1390 // Create an empty web resource response to be used if the resource request is blocked.
1391 WebResourceResponse emptyWebResourceResponse = new WebResourceResponse("text/plain", "utf8", new ByteArrayInputStream("".getBytes()));
1393 // Reset the whitelist results tracker.
1394 whiteListResultStringArray = null;
1396 // Initialize the third party request tracker.
1397 boolean isThirdPartyRequest = false;
1399 // Initialize the current domain string.
1400 String currentDomain = "";
1402 // Nobody is happy when comparing null strings.
1403 if (!(formattedUrlString == null) && !(url == null)) {
1404 // Get the domain strings to URIs.
1405 Uri currentDomainUri = Uri.parse(formattedUrlString);
1406 Uri requestDomainUri = Uri.parse(url);
1408 // Get the domain host names.
1409 String currentBaseDomain = currentDomainUri.getHost();
1410 String requestBaseDomain = requestDomainUri.getHost();
1412 // Update the current domain variable.
1413 currentDomain = currentBaseDomain;
1415 // Only compare the current base domain and the request base domain if neither is null.
1416 if (!(currentBaseDomain == null) && !(requestBaseDomain == null)) {
1417 // Determine the current base domain.
1418 while (currentBaseDomain.indexOf(".", currentBaseDomain.indexOf(".") + 1) > 0) { // There is at least one subdomain.
1419 // Remove the first subdomain.
1420 currentBaseDomain = currentBaseDomain.substring(currentBaseDomain.indexOf(".") + 1);
1423 // Determine the request base domain.
1424 while (requestBaseDomain.indexOf(".", requestBaseDomain.indexOf(".") + 1) > 0) { // There is at least one subdomain.
1425 // Remove the first subdomain.
1426 requestBaseDomain = requestBaseDomain.substring(requestBaseDomain.indexOf(".") + 1);
1429 // Update the third party request tracker.
1430 isThirdPartyRequest = !currentBaseDomain.equals(requestBaseDomain);
1434 // Block third-party requests if enabled.
1435 if (isThirdPartyRequest && blockAllThirdPartyRequests) {
1436 // Increment the blocked requests counters.
1438 thirdPartyBlockedRequests++;
1440 // Update the titles of the blocklist menu items. This must be run from the UI thread.
1441 activity.runOnUiThread(() -> {
1442 navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
1443 blocklistsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
1444 blockAllThirdPartyRequestsMenuItem.setTitle(thirdPartyBlockedRequests + " - " + getString(R.string.block_all_third_party_requests));
1447 // Add the request to the log.
1448 resourceRequests.add(new String[]{String.valueOf(REQUEST_THIRD_PARTY), url});
1450 // Return an empty web resource response.
1451 return emptyWebResourceResponse;
1454 // Check UltraPrivacy if it is enabled.
1455 if (ultraPrivacyEnabled) {
1456 if (blockListHelper.isBlocked(currentDomain, url, isThirdPartyRequest, ultraPrivacy)) {
1457 // Increment the blocked requests counters.
1459 ultraPrivacyBlockedRequests++;
1461 // Update the titles of the blocklist menu items. This must be run from the UI thread.
1462 activity.runOnUiThread(() -> {
1463 navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
1464 blocklistsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
1465 ultraPrivacyMenuItem.setTitle(ultraPrivacyBlockedRequests + " - " + getString(R.string.ultraprivacy));
1468 // The resource request was blocked. Return an empty web resource response.
1469 return emptyWebResourceResponse;
1472 // If the whitelist result is not null, the request has been allowed by UltraPrivacy.
1473 if (whiteListResultStringArray != null) {
1474 // Add a whitelist entry to the resource requests array.
1475 resourceRequests.add(whiteListResultStringArray);
1477 // The resource request has been allowed by UltraPrivacy. `return null` loads the requested resource.
1482 // Check EasyList if it is enabled.
1483 if (easyListEnabled) {
1484 if (blockListHelper.isBlocked(currentDomain, url, isThirdPartyRequest, easyList)) {
1485 // Increment the blocked requests counters.
1487 easyListBlockedRequests++;
1489 // Update the titles of the blocklist menu items. This must be run from the UI thread.
1490 activity.runOnUiThread(() -> {
1491 navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
1492 blocklistsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
1493 easyListMenuItem.setTitle(easyListBlockedRequests + " - " + getString(R.string.easylist));
1496 // Reset the whitelist results tracker (because otherwise it will sometimes add results to the list due to a race condition).
1497 whiteListResultStringArray = null;
1499 // The resource request was blocked. Return an empty web resource response.
1500 return emptyWebResourceResponse;
1504 // Check EasyPrivacy if it is enabled.
1505 if (easyPrivacyEnabled) {
1506 if (blockListHelper.isBlocked(currentDomain, url, isThirdPartyRequest, easyPrivacy)) {
1507 // Increment the blocked requests counters.
1509 easyPrivacyBlockedRequests++;
1511 // Update the titles of the blocklist menu items. This must be run from the UI thread.
1512 activity.runOnUiThread(() -> {
1513 navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
1514 blocklistsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
1515 easyPrivacyMenuItem.setTitle(easyPrivacyBlockedRequests + " - " + getString(R.string.easyprivacy));
1518 // Reset the whitelist results tracker (because otherwise it will sometimes add results to the list due to a race condition).
1519 whiteListResultStringArray = null;
1521 // The resource request was blocked. Return an empty web resource response.
1522 return emptyWebResourceResponse;
1526 // Check Fanboy’s Annoyance List if it is enabled.
1527 if (fanboysAnnoyanceListEnabled) {
1528 if (blockListHelper.isBlocked(currentDomain, url, isThirdPartyRequest, fanboysAnnoyanceList)) {
1529 // Increment the blocked requests counters.
1531 fanboysAnnoyanceListBlockedRequests++;
1533 // Update the titles of the blocklist menu items. This must be run from the UI thread.
1534 activity.runOnUiThread(() -> {
1535 navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
1536 blocklistsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
1537 fanboysAnnoyanceListMenuItem.setTitle(fanboysAnnoyanceListBlockedRequests + " - " + getString(R.string.fanboys_annoyance_list));
1540 // Reset the whitelist results tracker (because otherwise it will sometimes add results to the list due to a race condition).
1541 whiteListResultStringArray = null;
1543 // The resource request was blocked. Return an empty web resource response.
1544 return emptyWebResourceResponse;
1546 } else if (fanboysSocialBlockingListEnabled){ // Only check Fanboy’s Social Blocking List if Fanboy’s Annoyance List is disabled.
1547 if (blockListHelper.isBlocked(currentDomain, url, isThirdPartyRequest, fanboysSocialList)) {
1548 // Increment the blocked requests counters.
1550 fanboysSocialBlockingListBlockedRequests++;
1552 // Update the titles of the blocklist menu items. This must be run from the UI thread.
1553 activity.runOnUiThread(() -> {
1554 navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
1555 blocklistsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
1556 fanboysSocialBlockingListMenuItem.setTitle(fanboysSocialBlockingListBlockedRequests + " - " + getString(R.string.fanboys_social_blocking_list));
1559 // Reset the whitelist results tracker (because otherwise it will sometimes add results to the list due to a race condition).
1560 whiteListResultStringArray = null;
1562 // The resource request was blocked. Return an empty web resource response.
1563 return emptyWebResourceResponse;
1567 // Add the request to the log because it hasn't been processed by any of the previous checks.
1568 if (whiteListResultStringArray != null ) { // The request was processed by a whitelist.
1569 resourceRequests.add(whiteListResultStringArray);
1570 } else { // The request didn't match any blocklist entry. Log it as a default request.
1571 resourceRequests.add(new String[]{String.valueOf(REQUEST_DEFAULT), url});
1574 // The resource request has not been blocked. `return null` loads the requested resource.
1578 // Handle HTTP authentication requests.
1580 public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, String host, String realm) {
1581 // Store `handler` so it can be accessed from `onHttpAuthenticationCancel()` and `onHttpAuthenticationProceed()`.
1582 httpAuthHandler = handler;
1584 // Display the HTTP authentication dialog.
1585 AppCompatDialogFragment httpAuthenticationDialogFragment = HttpAuthenticationDialog.displayDialog(host, realm);
1586 httpAuthenticationDialogFragment.show(getSupportFragmentManager(), getString(R.string.http_authentication));
1589 // Update the URL in urlTextBox when the page starts to load.
1591 public void onPageStarted(WebView view, String url, Bitmap favicon) {
1592 // Reset the list of resource requests.
1593 resourceRequests.clear();
1595 // Initialize the counters for requests blocked by each blocklist.
1596 blockedRequests = 0;
1597 easyListBlockedRequests = 0;
1598 easyPrivacyBlockedRequests = 0;
1599 fanboysAnnoyanceListBlockedRequests = 0;
1600 fanboysSocialBlockingListBlockedRequests = 0;
1601 ultraPrivacyBlockedRequests = 0;
1602 thirdPartyBlockedRequests = 0;
1604 // If night mode is enabled, hide `mainWebView` until after the night mode CSS is applied.
1606 mainWebView.setVisibility(View.INVISIBLE);
1609 // Hide the keyboard. `0` indicates no additional flags.
1610 inputMethodManager.hideSoftInputFromWindow(mainWebView.getWindowToken(), 0);
1612 // Check to see if Privacy Browser is waiting on Orbot.
1613 if (!waitingForOrbot) { // We are not waiting on Orbot, so we need to process the URL.
1614 // 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.
1615 formattedUrlString = url;
1617 // Display the formatted URL text.
1618 urlTextBox.setText(formattedUrlString);
1620 // Apply text highlighting to `urlTextBox`.
1623 // Apply any custom domain settings if the URL was loaded by navigating history.
1624 if (navigatingHistory) {
1625 // Apply the domain settings.
1626 boolean userAgentChanged = applyDomainSettings(url, true, false);
1628 // Reset `navigatingHistory`.
1629 navigatingHistory = false;
1631 // Manually load the URL if the user agent has changed, which will have caused the previous URL to be reloaded.
1632 if (userAgentChanged) {
1633 loadUrl(formattedUrlString);
1637 // 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.
1638 urlIsLoading = true;
1640 // Replace Refresh with Stop if the menu item has been created. (The WebView typically begins loading before the menu items are instantiated.)
1641 if (refreshMenuItem != null) {
1643 refreshMenuItem.setTitle(R.string.stop);
1645 // If the icon is displayed in the AppBar, set it according to the theme.
1646 if (displayAdditionalAppBarIcons) {
1648 refreshMenuItem.setIcon(R.drawable.close_dark);
1650 refreshMenuItem.setIcon(R.drawable.close_light);
1657 // It is necessary to update `formattedUrlString` and `urlTextBox` after the page finishes loading because the final URL can change during load.
1659 public void onPageFinished(WebView view, String url) {
1660 // Reset the wide view port if it has been turned off by the waiting for Orbot message.
1661 if (!waitingForOrbot) {
1662 // Only use a wide view port if the URL starts with `http`, not for `file://` and `content://`.
1663 mainWebView.getSettings().setUseWideViewPort(url.startsWith("http"));
1666 // Flush any cookies to persistent storage. `CookieManager` has become very lazy about flushing cookies in recent versions.
1667 if (firstPartyCookiesEnabled && Build.VERSION.SDK_INT >= 21) {
1668 cookieManager.flush();
1671 // Update the Refresh menu item if it has been created.
1672 if (refreshMenuItem != null) {
1673 // Reset the Refresh title.
1674 refreshMenuItem.setTitle(R.string.refresh);
1676 // If the icon is displayed in the AppBar, reset it according to the theme.
1677 if (displayAdditionalAppBarIcons) {
1679 refreshMenuItem.setIcon(R.drawable.refresh_enabled_dark);
1681 refreshMenuItem.setIcon(R.drawable.refresh_enabled_light);
1686 // Reset `urlIsLoading`, which is used to prevent reloads on redirect if the user agent changes.
1687 urlIsLoading = false;
1689 // Clear the cache and history if Incognito Mode is enabled.
1690 if (incognitoModeEnabled) {
1691 // Clear the cache. `true` includes disk files.
1692 mainWebView.clearCache(true);
1694 // Clear the back/forward history.
1695 mainWebView.clearHistory();
1697 // Manually delete cache folders.
1699 // Delete the main cache directory.
1700 privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/cache");
1702 // Delete the secondary `Service Worker` cache directory.
1703 // A `String[]` must be used because the directory contains a space and `Runtime.exec` will not escape the string correctly otherwise.
1704 privacyBrowserRuntime.exec(new String[] {"rm", "-rf", privateDataDirectoryString + "/app_webview/Service Worker/"});
1705 } catch (IOException e) {
1706 // Do nothing if an error is thrown.
1710 // Update `urlTextBox` and apply domain settings if not waiting on Orbot.
1711 if (!waitingForOrbot) {
1712 // Check to see if `WebView` has set `url` to be `about:blank`.
1713 if (url.equals("about:blank")) { // `WebView` is blank, so `formattedUrlString` should be `""` and `urlTextBox` should display a hint.
1714 // Set `formattedUrlString` to `""`.
1715 formattedUrlString = "";
1717 urlTextBox.setText(formattedUrlString);
1719 // Request focus for `urlTextBox`.
1720 urlTextBox.requestFocus();
1722 // Display the keyboard.
1723 inputMethodManager.showSoftInput(urlTextBox, 0);
1725 // Apply the domain settings. This clears any settings from the previous domain.
1726 applyDomainSettings(formattedUrlString, true, false);
1727 } else { // `WebView` has loaded a webpage.
1728 // 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.
1729 formattedUrlString = mainWebView.getUrl();
1731 // Only update the URL text box if the user is not typing in it.
1732 if (!urlTextBox.hasFocus()) {
1733 // Display the formatted URL text.
1734 urlTextBox.setText(formattedUrlString);
1736 // Apply text highlighting to `urlTextBox`.
1741 // Store the SSL certificate so it can be accessed from `ViewSslCertificateDialog` and `PinnedSslCertificateMismatchDialog`.
1742 sslCertificate = mainWebView.getCertificate();
1744 // 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.
1745 // Also ignore if changes in the user agent causes an error while navigating history.
1746 if (pinnedDomainSslCertificate && !ignorePinnedSslCertificate && navigatingHistory) {
1747 // Initialize the current SSL certificate variables.
1748 String currentWebsiteIssuedToCName = "";
1749 String currentWebsiteIssuedToOName = "";
1750 String currentWebsiteIssuedToUName = "";
1751 String currentWebsiteIssuedByCName = "";
1752 String currentWebsiteIssuedByOName = "";
1753 String currentWebsiteIssuedByUName = "";
1754 Date currentWebsiteSslStartDate = null;
1755 Date currentWebsiteSslEndDate = null;
1758 // Extract the individual pieces of information from the current website SSL certificate if it is not null.
1759 if (sslCertificate != null) {
1760 currentWebsiteIssuedToCName = sslCertificate.getIssuedTo().getCName();
1761 currentWebsiteIssuedToOName = sslCertificate.getIssuedTo().getOName();
1762 currentWebsiteIssuedToUName = sslCertificate.getIssuedTo().getUName();
1763 currentWebsiteIssuedByCName = sslCertificate.getIssuedBy().getCName();
1764 currentWebsiteIssuedByOName = sslCertificate.getIssuedBy().getOName();
1765 currentWebsiteIssuedByUName = sslCertificate.getIssuedBy().getUName();
1766 currentWebsiteSslStartDate = sslCertificate.getValidNotBeforeDate();
1767 currentWebsiteSslEndDate = sslCertificate.getValidNotAfterDate();
1770 // 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`.
1771 String currentWebsiteSslStartDateString = "";
1772 String currentWebsiteSslEndDateString = "";
1773 String pinnedDomainSslStartDateString = "";
1774 String pinnedDomainSslEndDateString = "";
1776 // Convert the `Dates` to `Strings` if they are not `null`.
1777 if (currentWebsiteSslStartDate != null) {
1778 currentWebsiteSslStartDateString = currentWebsiteSslStartDate.toString();
1781 if (currentWebsiteSslEndDate != null) {
1782 currentWebsiteSslEndDateString = currentWebsiteSslEndDate.toString();
1785 if (pinnedDomainSslStartDate != null) {
1786 pinnedDomainSslStartDateString = pinnedDomainSslStartDate.toString();
1789 if (pinnedDomainSslEndDate != null) {
1790 pinnedDomainSslEndDateString = pinnedDomainSslEndDate.toString();
1793 // Check to see if the pinned SSL certificate matches the current website certificate.
1794 if (!currentWebsiteIssuedToCName.equals(pinnedDomainSslIssuedToCNameString) || !currentWebsiteIssuedToOName.equals(pinnedDomainSslIssuedToONameString) ||
1795 !currentWebsiteIssuedToUName.equals(pinnedDomainSslIssuedToUNameString) || !currentWebsiteIssuedByCName.equals(pinnedDomainSslIssuedByCNameString) ||
1796 !currentWebsiteIssuedByOName.equals(pinnedDomainSslIssuedByONameString) || !currentWebsiteIssuedByUName.equals(pinnedDomainSslIssuedByUNameString) ||
1797 !currentWebsiteSslStartDateString.equals(pinnedDomainSslStartDateString) || !currentWebsiteSslEndDateString.equals(pinnedDomainSslEndDateString)) {
1798 // The pinned SSL certificate doesn't match the current domain certificate.
1799 //Display the pinned SSL certificate mismatch `AlertDialog`.
1800 AppCompatDialogFragment pinnedSslCertificateMismatchDialogFragment = new PinnedSslCertificateMismatchDialog();
1801 pinnedSslCertificateMismatchDialogFragment.show(getSupportFragmentManager(), getString(R.string.ssl_certificate_mismatch));
1807 // Handle SSL Certificate errors.
1809 public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
1810 // Get the current website SSL certificate.
1811 SslCertificate currentWebsiteSslCertificate = error.getCertificate();
1813 // Extract the individual pieces of information from the current website SSL certificate.
1814 String currentWebsiteIssuedToCName = currentWebsiteSslCertificate.getIssuedTo().getCName();
1815 String currentWebsiteIssuedToOName = currentWebsiteSslCertificate.getIssuedTo().getOName();
1816 String currentWebsiteIssuedToUName = currentWebsiteSslCertificate.getIssuedTo().getUName();
1817 String currentWebsiteIssuedByCName = currentWebsiteSslCertificate.getIssuedBy().getCName();
1818 String currentWebsiteIssuedByOName = currentWebsiteSslCertificate.getIssuedBy().getOName();
1819 String currentWebsiteIssuedByUName = currentWebsiteSslCertificate.getIssuedBy().getUName();
1820 Date currentWebsiteSslStartDate = currentWebsiteSslCertificate.getValidNotBeforeDate();
1821 Date currentWebsiteSslEndDate = currentWebsiteSslCertificate.getValidNotAfterDate();
1823 // Proceed to the website if the current SSL website certificate matches the pinned domain certificate.
1824 if (pinnedDomainSslCertificate &&
1825 currentWebsiteIssuedToCName.equals(pinnedDomainSslIssuedToCNameString) && currentWebsiteIssuedToOName.equals(pinnedDomainSslIssuedToONameString) &&
1826 currentWebsiteIssuedToUName.equals(pinnedDomainSslIssuedToUNameString) && currentWebsiteIssuedByCName.equals(pinnedDomainSslIssuedByCNameString) &&
1827 currentWebsiteIssuedByOName.equals(pinnedDomainSslIssuedByONameString) && currentWebsiteIssuedByUName.equals(pinnedDomainSslIssuedByUNameString) &&
1828 currentWebsiteSslStartDate.equals(pinnedDomainSslStartDate) && currentWebsiteSslEndDate.equals(pinnedDomainSslEndDate)) {
1829 // An SSL certificate is pinned and matches the current domain certificate.
1830 // Proceed to the website without displaying an error.
1832 } else { // Either there isn't a pinned SSL certificate or it doesn't match the current website certificate.
1833 // Store `handler` so it can be accesses from `onSslErrorCancel()` and `onSslErrorProceed()`.
1834 sslErrorHandler = handler;
1836 // Display the SSL error `AlertDialog`.
1837 AppCompatDialogFragment sslCertificateErrorDialogFragment = SslCertificateErrorDialog.displayDialog(error);
1838 sslCertificateErrorDialogFragment.show(getSupportFragmentManager(), getString(R.string.ssl_certificate_error));
1843 // Get the intent that started the app.
1844 Intent launchingIntent = getIntent();
1846 // Get the information from the intent.
1847 String launchingIntentAction = launchingIntent.getAction();
1848 Uri launchingIntentUriData = launchingIntent.getData();
1850 // If the intent action is a web search, perform the search.
1851 if ((launchingIntentAction != null) && launchingIntentAction.equals(Intent.ACTION_WEB_SEARCH)) {
1852 // Create an encoded URL string.
1853 String encodedUrlString;
1855 // Sanitize the search input and convert it to a search.
1857 encodedUrlString = URLEncoder.encode(launchingIntent.getStringExtra(SearchManager.QUERY), "UTF-8");
1858 } catch (UnsupportedEncodingException exception) {
1859 encodedUrlString = "";
1862 // Add the base search URL.
1863 formattedUrlString = searchURL + encodedUrlString;
1864 } else if (launchingIntentUriData != null){ // Check to see if the intent contains a new URL.
1865 // Set the formatted URL string.
1866 formattedUrlString = launchingIntentUriData.toString();
1869 // Load the website if not waiting for Orbot to connect.
1870 if (!waitingForOrbot) {
1871 loadUrl(formattedUrlString);
1876 protected void onNewIntent(Intent intent) {
1877 // Sets the new intent as the activity intent, so that any future `getIntent()`s pick up this one instead of creating a new activity.
1880 // Get the information from the intent.
1881 String intentAction = intent.getAction();
1882 Uri intentUriData = intent.getData();
1884 // If the intent action is a web search, perform the search.
1885 if ((intentAction != null) && intentAction.equals(Intent.ACTION_WEB_SEARCH)) {
1886 // Create an encoded URL string.
1887 String encodedUrlString;
1889 // Sanitize the search input and convert it to a search.
1891 encodedUrlString = URLEncoder.encode(intent.getStringExtra(SearchManager.QUERY), "UTF-8");
1892 } catch (UnsupportedEncodingException exception) {
1893 encodedUrlString = "";
1896 // Add the base search URL.
1897 formattedUrlString = searchURL + encodedUrlString;
1898 } else if (intentUriData != null){ // Check to see if the intent contains a new URL.
1899 // Set the formatted URL string.
1900 formattedUrlString = intentUriData.toString();
1904 loadUrl(formattedUrlString);
1906 // Close the navigation drawer if it is open.
1907 if (drawerLayout.isDrawerVisible(GravityCompat.START)) {
1908 drawerLayout.closeDrawer(GravityCompat.START);
1911 // Close the bookmarks drawer if it is open.
1912 if (drawerLayout.isDrawerVisible(GravityCompat.END)) {
1913 drawerLayout.closeDrawer(GravityCompat.END);
1916 // Clear the keyboard if displayed and remove the focus on the urlTextBar if it has it.
1917 mainWebView.requestFocus();
1921 public void onRestart() {
1922 // Run the default commands.
1925 // Make sure Orbot is running if Privacy Browser is proxying through Orbot.
1926 if (proxyThroughOrbot) {
1927 // Request Orbot to start. If Orbot is already running no hard will be caused by this request.
1928 Intent orbotIntent = new Intent("org.torproject.android.intent.action.START");
1930 // Send the intent to the Orbot package.
1931 orbotIntent.setPackage("org.torproject.android");
1934 sendBroadcast(orbotIntent);
1937 // Apply the app settings if returning from the Settings activity..
1938 if (reapplyAppSettingsOnRestart) {
1939 // Apply the app settings.
1942 // Reload the webpage if displaying of images has been disabled in the Settings activity.
1943 if (reloadOnRestart) {
1944 // Reload `mainWebView`.
1945 mainWebView.reload();
1947 // Reset `reloadOnRestartBoolean`.
1948 reloadOnRestart = false;
1951 // Reset the return from settings flag.
1952 reapplyAppSettingsOnRestart = false;
1955 // Apply the domain settings if returning from the Domains activity.
1956 if (reapplyDomainSettingsOnRestart) {
1957 // Reapply the domain settings.
1958 applyDomainSettings(formattedUrlString, false, true);
1960 // Reset `reapplyDomainSettingsOnRestart`.
1961 reapplyDomainSettingsOnRestart = false;
1964 // Load the URL on restart to apply changes to night mode.
1965 if (loadUrlOnRestart) {
1966 // Load the current `formattedUrlString`.
1967 loadUrl(formattedUrlString);
1969 // Reset `loadUrlOnRestart.
1970 loadUrlOnRestart = false;
1973 // Update the bookmarks drawer if returning from the Bookmarks activity.
1974 if (restartFromBookmarksActivity) {
1975 // Close the bookmarks drawer.
1976 drawerLayout.closeDrawer(GravityCompat.END);
1978 // Reload the bookmarks drawer.
1979 loadBookmarksFolder();
1981 // Reset `restartFromBookmarksActivity`.
1982 restartFromBookmarksActivity = false;
1985 // Update the privacy icon. `true` runs `invalidateOptionsMenu` as the last step. This can be important if the screen was rotated.
1986 updatePrivacyIcons(true);
1989 // `onResume()` runs after `onStart()`, which runs after `onCreate()` and `onRestart()`.
1991 public void onResume() {
1992 // Run the default commands.
1995 // Resume JavaScript (if enabled).
1996 mainWebView.resumeTimers();
1998 // Resume `mainWebView`.
1999 mainWebView.onResume();
2001 // Resume the adView for the free flavor.
2002 if (BuildConfig.FLAVOR.contentEquals("free")) {
2004 AdHelper.resumeAd(findViewById(R.id.adview));
2007 // Display a message to the user if waiting for Orbot.
2008 if (waitingForOrbot && !orbotStatus.equals("ON")) {
2009 // Disable the wide view port so that the waiting for Orbot text is displayed correctly.
2010 mainWebView.getSettings().setUseWideViewPort(false);
2012 // Load a waiting page. `null` specifies no encoding, which defaults to ASCII.
2013 mainWebView.loadData(waitingForOrbotHtmlString, "text/html", null);
2016 if (displayingFullScreenVideo) {
2017 // Remove the translucent overlays.
2018 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
2020 // Remove the translucent status bar overlay on the `Drawer Layout`, which is special and needs its own command.
2021 drawerLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
2023 /* SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
2024 * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
2025 * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
2027 rootCoordinatorLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
2032 public void onPause() {
2033 // Run the default commands.
2036 // Pause `mainWebView`.
2037 mainWebView.onPause();
2039 // Stop all JavaScript.
2040 mainWebView.pauseTimers();
2042 // Pause the ad or it will continue to consume resources in the background on the free flavor.
2043 if (BuildConfig.FLAVOR.contentEquals("free")) {
2045 AdHelper.pauseAd(findViewById(R.id.adview));
2050 public void onDestroy() {
2051 // Unregister the Orbot status broadcast receiver.
2052 this.unregisterReceiver(orbotStatusBroadcastReceiver);
2054 // Close the bookmarks cursor and database.
2055 bookmarksCursor.close();
2056 bookmarksDatabaseHelper.close();
2058 // Run the default commands.
2063 public boolean onCreateOptionsMenu(Menu menu) {
2064 // Inflate the menu; this adds items to the action bar if it is present.
2065 getMenuInflater().inflate(R.menu.webview_options_menu, menu);
2067 // Set mainMenu so it can be used by `onOptionsItemSelected()` and `updatePrivacyIcons`.
2070 // Set the initial status of the privacy icons. `false` does not call `invalidateOptionsMenu` as the last step.
2071 updatePrivacyIcons(false);
2073 // Get handles for the menu items.
2074 MenuItem toggleFirstPartyCookiesMenuItem = menu.findItem(R.id.toggle_first_party_cookies);
2075 MenuItem toggleThirdPartyCookiesMenuItem = menu.findItem(R.id.toggle_third_party_cookies);
2076 MenuItem toggleDomStorageMenuItem = menu.findItem(R.id.toggle_dom_storage);
2077 MenuItem toggleSaveFormDataMenuItem = menu.findItem(R.id.toggle_save_form_data); // Form data can be removed once the minimum API >= 26.
2078 MenuItem clearFormDataMenuItem = menu.findItem(R.id.clear_form_data); // Form data can be removed once the minimum API >= 26.
2079 refreshMenuItem = menu.findItem(R.id.refresh);
2080 blocklistsMenuItem = menu.findItem(R.id.blocklists);
2081 easyListMenuItem = menu.findItem(R.id.easylist);
2082 easyPrivacyMenuItem = menu.findItem(R.id.easyprivacy);
2083 fanboysAnnoyanceListMenuItem = menu.findItem(R.id.fanboys_annoyance_list);
2084 fanboysSocialBlockingListMenuItem = menu.findItem(R.id.fanboys_social_blocking_list);
2085 ultraPrivacyMenuItem = menu.findItem(R.id.ultraprivacy);
2086 blockAllThirdPartyRequestsMenuItem = menu.findItem(R.id.block_all_third_party_requests);
2087 MenuItem adConsentMenuItem = menu.findItem(R.id.ad_consent);
2089 // Only display third-party cookies if API >= 21
2090 toggleThirdPartyCookiesMenuItem.setVisible(Build.VERSION.SDK_INT >= 21);
2092 // Only display the form data menu items if the API < 26.
2093 toggleSaveFormDataMenuItem.setVisible(Build.VERSION.SDK_INT < 26);
2094 clearFormDataMenuItem.setVisible(Build.VERSION.SDK_INT < 26);
2096 // Only show Ad Consent if this is the free flavor.
2097 adConsentMenuItem.setVisible(BuildConfig.FLAVOR.contentEquals("free"));
2099 // Get the shared preference values.
2100 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
2102 // Get the status of the additional AppBar icons.
2103 displayAdditionalAppBarIcons = sharedPreferences.getBoolean("display_additional_app_bar_icons", false);
2105 // 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.
2106 if (displayAdditionalAppBarIcons) {
2107 toggleFirstPartyCookiesMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
2108 toggleDomStorageMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
2109 refreshMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
2110 } else { //Do not display the additional icons.
2111 toggleFirstPartyCookiesMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
2112 toggleDomStorageMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
2113 refreshMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
2116 // Replace Refresh with Stop if a URL is already loading.
2119 refreshMenuItem.setTitle(R.string.stop);
2121 // If the icon is displayed in the AppBar, set it according to the theme.
2122 if (displayAdditionalAppBarIcons) {
2124 refreshMenuItem.setIcon(R.drawable.close_dark);
2126 refreshMenuItem.setIcon(R.drawable.close_light);
2135 public boolean onPrepareOptionsMenu(Menu menu) {
2136 // Get handles for the menu items.
2137 MenuItem addOrEditDomain = menu.findItem(R.id.add_or_edit_domain);
2138 MenuItem toggleFirstPartyCookiesMenuItem = menu.findItem(R.id.toggle_first_party_cookies);
2139 MenuItem toggleThirdPartyCookiesMenuItem = menu.findItem(R.id.toggle_third_party_cookies);
2140 MenuItem toggleDomStorageMenuItem = menu.findItem(R.id.toggle_dom_storage);
2141 MenuItem toggleSaveFormDataMenuItem = menu.findItem(R.id.toggle_save_form_data); // Form data can be removed once the minimum API >= 26.
2142 MenuItem clearDataMenuItem = menu.findItem(R.id.clear_data);
2143 MenuItem clearCookiesMenuItem = menu.findItem(R.id.clear_cookies);
2144 MenuItem clearDOMStorageMenuItem = menu.findItem(R.id.clear_dom_storage);
2145 MenuItem clearFormDataMenuItem = menu.findItem(R.id.clear_form_data); // Form data can be removed once the minimum API >= 26.
2146 MenuItem fontSizeMenuItem = menu.findItem(R.id.font_size);
2147 MenuItem swipeToRefreshMenuItem = menu.findItem(R.id.swipe_to_refresh);
2148 MenuItem displayImagesMenuItem = menu.findItem(R.id.display_images);
2149 MenuItem nightModeMenuItem = menu.findItem(R.id.night_mode);
2150 MenuItem proxyThroughOrbotMenuItem = menu.findItem(R.id.proxy_through_orbot);
2152 // Set the text for the domain menu item.
2153 if (domainSettingsApplied) {
2154 addOrEditDomain.setTitle(R.string.edit_domain_settings);
2156 addOrEditDomain.setTitle(R.string.add_domain_settings);
2159 // Set the status of the menu item checkboxes.
2160 toggleFirstPartyCookiesMenuItem.setChecked(firstPartyCookiesEnabled);
2161 toggleThirdPartyCookiesMenuItem.setChecked(thirdPartyCookiesEnabled);
2162 toggleDomStorageMenuItem.setChecked(domStorageEnabled);
2163 toggleSaveFormDataMenuItem.setChecked(saveFormDataEnabled); // Form data can be removed once the minimum API >= 26.
2164 easyListMenuItem.setChecked(easyListEnabled);
2165 easyPrivacyMenuItem.setChecked(easyPrivacyEnabled);
2166 fanboysAnnoyanceListMenuItem.setChecked(fanboysAnnoyanceListEnabled);
2167 fanboysSocialBlockingListMenuItem.setChecked(fanboysSocialBlockingListEnabled);
2168 ultraPrivacyMenuItem.setChecked(ultraPrivacyEnabled);
2169 blockAllThirdPartyRequestsMenuItem.setChecked(blockAllThirdPartyRequests);
2170 swipeToRefreshMenuItem.setChecked(swipeRefreshLayout.isEnabled());
2171 displayImagesMenuItem.setChecked(mainWebView.getSettings().getLoadsImagesAutomatically());
2172 nightModeMenuItem.setChecked(nightMode);
2173 proxyThroughOrbotMenuItem.setChecked(proxyThroughOrbot);
2175 // Enable third-party cookies if first-party cookies are enabled.
2176 toggleThirdPartyCookiesMenuItem.setEnabled(firstPartyCookiesEnabled);
2178 // Enable DOM Storage if JavaScript is enabled.
2179 toggleDomStorageMenuItem.setEnabled(javaScriptEnabled);
2181 // Enable Clear Cookies if there are any.
2182 clearCookiesMenuItem.setEnabled(cookieManager.hasCookies());
2184 // Get a count of the number of files in the Local Storage directory.
2185 File localStorageDirectory = new File (privateDataDirectoryString + "/app_webview/Local Storage/");
2186 int localStorageDirectoryNumberOfFiles = 0;
2187 if (localStorageDirectory.exists()) {
2188 localStorageDirectoryNumberOfFiles = localStorageDirectory.list().length;
2191 // Get a count of the number of files in the IndexedDB directory.
2192 File indexedDBDirectory = new File (privateDataDirectoryString + "/app_webview/IndexedDB");
2193 int indexedDBDirectoryNumberOfFiles = 0;
2194 if (indexedDBDirectory.exists()) {
2195 indexedDBDirectoryNumberOfFiles = indexedDBDirectory.list().length;
2198 // Enable Clear DOM Storage if there is any.
2199 clearDOMStorageMenuItem.setEnabled(localStorageDirectoryNumberOfFiles > 0 || indexedDBDirectoryNumberOfFiles > 0);
2201 // Enable Clear Form Data is there is any. This can be removed once the minimum API >= 26.
2202 if (Build.VERSION.SDK_INT < 26) {
2203 WebViewDatabase mainWebViewDatabase = WebViewDatabase.getInstance(this);
2204 clearFormDataMenuItem.setEnabled(mainWebViewDatabase.hasFormData());
2206 // Disable clear form data because it is not supported on current version of Android.
2207 clearFormDataMenuItem.setEnabled(false);
2210 // Enable Clear Data if any of the submenu items are enabled.
2211 clearDataMenuItem.setEnabled(clearCookiesMenuItem.isEnabled() || clearDOMStorageMenuItem.isEnabled() || clearFormDataMenuItem.isEnabled());
2213 // Disable Fanboy's Social Blocking List if Fanboy's Annoyance List is checked.
2214 fanboysSocialBlockingListMenuItem.setEnabled(!fanboysAnnoyanceListEnabled);
2216 // Initialize the display names for the blocklists with the number of blocked requests.
2217 blocklistsMenuItem.setTitle(getString(R.string.blocklists) + " - " + blockedRequests);
2218 easyListMenuItem.setTitle(easyListBlockedRequests + " - " + getString(R.string.easylist));
2219 easyPrivacyMenuItem.setTitle(easyPrivacyBlockedRequests + " - " + getString(R.string.easyprivacy));
2220 fanboysAnnoyanceListMenuItem.setTitle(fanboysAnnoyanceListBlockedRequests + " - " + getString(R.string.fanboys_annoyance_list));
2221 fanboysSocialBlockingListMenuItem.setTitle(fanboysSocialBlockingListBlockedRequests + " - " + getString(R.string.fanboys_social_blocking_list));
2222 ultraPrivacyMenuItem.setTitle(ultraPrivacyBlockedRequests + " - " + getString(R.string.ultraprivacy));
2223 blockAllThirdPartyRequestsMenuItem.setTitle(thirdPartyBlockedRequests + " - " + getString(R.string.block_all_third_party_requests));
2225 // Get the current user agent.
2226 String currentUserAgent = mainWebView.getSettings().getUserAgentString();
2228 // Select the current user agent menu item. A switch statement cannot be used because the user agents are not compile time constants.
2229 if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[0])) { // Privacy Browser.
2230 menu.findItem(R.id.user_agent_privacy_browser).setChecked(true);
2231 } else if (currentUserAgent.equals(webViewDefaultUserAgent)) { // WebView Default.
2232 menu.findItem(R.id.user_agent_webview_default).setChecked(true);
2233 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[2])) { // Firefox on Android.
2234 menu.findItem(R.id.user_agent_firefox_on_android).setChecked(true);
2235 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[3])) { // Chrome on Android.
2236 menu.findItem(R.id.user_agent_chrome_on_android).setChecked(true);
2237 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[4])) { // Safari on iOS.
2238 menu.findItem(R.id.user_agent_safari_on_ios).setChecked(true);
2239 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[5])) { // Firefox on Linux.
2240 menu.findItem(R.id.user_agent_firefox_on_linux).setChecked(true);
2241 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[6])) { // Chromium on Linux.
2242 menu.findItem(R.id.user_agent_chromium_on_linux).setChecked(true);
2243 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[7])) { // Firefox on Windows.
2244 menu.findItem(R.id.user_agent_firefox_on_windows).setChecked(true);
2245 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[8])) { // Chrome on Windows.
2246 menu.findItem(R.id.user_agent_chrome_on_windows).setChecked(true);
2247 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[9])) { // Edge on Windows.
2248 menu.findItem(R.id.user_agent_edge_on_windows).setChecked(true);
2249 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[10])) { // Internet Explorer on Windows.
2250 menu.findItem(R.id.user_agent_internet_explorer_on_windows).setChecked(true);
2251 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[11])) { // Safari on macOS.
2252 menu.findItem(R.id.user_agent_safari_on_macos).setChecked(true);
2253 } else { // Custom user agent.
2254 menu.findItem(R.id.user_agent_custom).setChecked(true);
2257 // Initialize font size variables.
2258 int fontSize = mainWebView.getSettings().getTextZoom();
2259 String fontSizeTitle;
2260 MenuItem selectedFontSizeMenuItem;
2262 // Prepare the font size title and current size menu item.
2265 fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.twenty_five_percent);
2266 selectedFontSizeMenuItem = menu.findItem(R.id.font_size_twenty_five_percent);
2270 fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.fifty_percent);
2271 selectedFontSizeMenuItem = menu.findItem(R.id.font_size_fifty_percent);
2275 fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.seventy_five_percent);
2276 selectedFontSizeMenuItem = menu.findItem(R.id.font_size_seventy_five_percent);
2280 fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.one_hundred_percent);
2281 selectedFontSizeMenuItem = menu.findItem(R.id.font_size_one_hundred_percent);
2285 fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.one_hundred_twenty_five_percent);
2286 selectedFontSizeMenuItem = menu.findItem(R.id.font_size_one_hundred_twenty_five_percent);
2290 fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.one_hundred_fifty_percent);
2291 selectedFontSizeMenuItem = menu.findItem(R.id.font_size_one_hundred_fifty_percent);
2295 fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.one_hundred_seventy_five_percent);
2296 selectedFontSizeMenuItem = menu.findItem(R.id.font_size_one_hundred_seventy_five_percent);
2300 fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.two_hundred_percent);
2301 selectedFontSizeMenuItem = menu.findItem(R.id.font_size_two_hundred_percent);
2305 fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.one_hundred_percent);
2306 selectedFontSizeMenuItem = menu.findItem(R.id.font_size_one_hundred_percent);
2310 // Set the font size title and select the current size menu item.
2311 fontSizeMenuItem.setTitle(fontSizeTitle);
2312 selectedFontSizeMenuItem.setChecked(true);
2314 // Run all the other default commands.
2315 super.onPrepareOptionsMenu(menu);
2317 // Display the menu.
2322 // Remove Android Studio's warning about the dangers of using SetJavaScriptEnabled.
2323 @SuppressLint("SetJavaScriptEnabled")
2324 // removeAllCookies is deprecated, but it is required for API < 21.
2325 @SuppressWarnings("deprecation")
2326 public boolean onOptionsItemSelected(MenuItem menuItem) {
2327 // Get the selected menu item ID.
2328 int menuItemId = menuItem.getItemId();
2330 // Set the commands that relate to the menu entries.
2331 switch (menuItemId) {
2332 case R.id.toggle_javascript:
2333 // Switch the status of javaScriptEnabled.
2334 javaScriptEnabled = !javaScriptEnabled;
2336 // Apply the new JavaScript status.
2337 mainWebView.getSettings().setJavaScriptEnabled(javaScriptEnabled);
2339 // Update the privacy icon. `true` runs `invalidateOptionsMenu` as the last step.
2340 updatePrivacyIcons(true);
2342 // Display a `Snackbar`.
2343 if (javaScriptEnabled) { // JavaScrip is enabled.
2344 Snackbar.make(findViewById(R.id.main_webview), R.string.javascript_enabled, Snackbar.LENGTH_SHORT).show();
2345 } else if (firstPartyCookiesEnabled) { // JavaScript is disabled, but first-party cookies are enabled.
2346 Snackbar.make(findViewById(R.id.main_webview), R.string.javascript_disabled, Snackbar.LENGTH_SHORT).show();
2347 } else { // Privacy mode.
2348 Snackbar.make(findViewById(R.id.main_webview), R.string.privacy_mode, Snackbar.LENGTH_SHORT).show();
2351 // Reload the WebView.
2352 mainWebView.reload();
2355 case R.id.add_or_edit_domain:
2356 if (domainSettingsApplied) { // Edit the current domain settings.
2357 // Reapply the domain settings on returning to `MainWebViewActivity`.
2358 reapplyDomainSettingsOnRestart = true;
2359 currentDomainName = "";
2361 // Create an intent to launch the domains activity.
2362 Intent domainsIntent = new Intent(this, DomainsActivity.class);
2364 // Put extra information instructing the domains activity to directly load the current domain and close on back instead of returning to the domains list.
2365 domainsIntent.putExtra("loadDomain", domainSettingsDatabaseId);
2366 domainsIntent.putExtra("closeOnBack", true);
2369 startActivity(domainsIntent);
2370 } else { // Add a new domain.
2371 // Apply the new domain settings on returning to `MainWebViewActivity`.
2372 reapplyDomainSettingsOnRestart = true;
2373 currentDomainName = "";
2375 // Get the current domain
2376 Uri currentUri = Uri.parse(formattedUrlString);
2377 String currentDomain = currentUri.getHost();
2379 // Initialize the database handler. The `0` specifies the database version, but that is ignored and set instead using a constant in `DomainsDatabaseHelper`.
2380 DomainsDatabaseHelper domainsDatabaseHelper = new DomainsDatabaseHelper(this, null, null, 0);
2382 // Create the domain and store the database ID.
2383 int newDomainDatabaseId = domainsDatabaseHelper.addDomain(currentDomain);
2385 // Create an intent to launch the domains activity.
2386 Intent domainsIntent = new Intent(this, DomainsActivity.class);
2388 // Put extra information instructing the domains activity to directly load the new domain and close on back instead of returning to the domains list.
2389 domainsIntent.putExtra("loadDomain", newDomainDatabaseId);
2390 domainsIntent.putExtra("closeOnBack", true);
2393 startActivity(domainsIntent);
2397 case R.id.toggle_first_party_cookies:
2398 // Switch the status of firstPartyCookiesEnabled.
2399 firstPartyCookiesEnabled = !firstPartyCookiesEnabled;
2401 // Update the menu checkbox.
2402 menuItem.setChecked(firstPartyCookiesEnabled);
2404 // Apply the new cookie status.
2405 cookieManager.setAcceptCookie(firstPartyCookiesEnabled);
2407 // Update the privacy icon. `true` runs `invalidateOptionsMenu` as the last step.
2408 updatePrivacyIcons(true);
2410 // Display a `Snackbar`.
2411 if (firstPartyCookiesEnabled) { // First-party cookies are enabled.
2412 Snackbar.make(findViewById(R.id.main_webview), R.string.first_party_cookies_enabled, Snackbar.LENGTH_SHORT).show();
2413 } else if (javaScriptEnabled) { // JavaScript is still enabled.
2414 Snackbar.make(findViewById(R.id.main_webview), R.string.first_party_cookies_disabled, Snackbar.LENGTH_SHORT).show();
2415 } else { // Privacy mode.
2416 Snackbar.make(findViewById(R.id.main_webview), R.string.privacy_mode, Snackbar.LENGTH_SHORT).show();
2419 // Reload the WebView.
2420 mainWebView.reload();
2423 case R.id.toggle_third_party_cookies:
2424 if (Build.VERSION.SDK_INT >= 21) {
2425 // Switch the status of thirdPartyCookiesEnabled.
2426 thirdPartyCookiesEnabled = !thirdPartyCookiesEnabled;
2428 // Update the menu checkbox.
2429 menuItem.setChecked(thirdPartyCookiesEnabled);
2431 // Apply the new cookie status.
2432 cookieManager.setAcceptThirdPartyCookies(mainWebView, thirdPartyCookiesEnabled);
2434 // Display a `Snackbar`.
2435 if (thirdPartyCookiesEnabled) {
2436 Snackbar.make(findViewById(R.id.main_webview), R.string.third_party_cookies_enabled, Snackbar.LENGTH_SHORT).show();
2438 Snackbar.make(findViewById(R.id.main_webview), R.string.third_party_cookies_disabled, Snackbar.LENGTH_SHORT).show();
2441 // Reload the WebView.
2442 mainWebView.reload();
2443 } // Else do nothing because SDK < 21.
2446 case R.id.toggle_dom_storage:
2447 // Switch the status of domStorageEnabled.
2448 domStorageEnabled = !domStorageEnabled;
2450 // Update the menu checkbox.
2451 menuItem.setChecked(domStorageEnabled);
2453 // Apply the new DOM Storage status.
2454 mainWebView.getSettings().setDomStorageEnabled(domStorageEnabled);
2456 // Update the privacy icon. `true` runs `invalidateOptionsMenu` as the last step.
2457 updatePrivacyIcons(true);
2459 // Display a `Snackbar`.
2460 if (domStorageEnabled) {
2461 Snackbar.make(findViewById(R.id.main_webview), R.string.dom_storage_enabled, Snackbar.LENGTH_SHORT).show();
2463 Snackbar.make(findViewById(R.id.main_webview), R.string.dom_storage_disabled, Snackbar.LENGTH_SHORT).show();
2466 // Reload the WebView.
2467 mainWebView.reload();
2470 // Form data can be removed once the minimum API >= 26.
2471 case R.id.toggle_save_form_data:
2472 // Switch the status of saveFormDataEnabled.
2473 saveFormDataEnabled = !saveFormDataEnabled;
2475 // Update the menu checkbox.
2476 menuItem.setChecked(saveFormDataEnabled);
2478 // Apply the new form data status.
2479 mainWebView.getSettings().setSaveFormData(saveFormDataEnabled);
2481 // Display a `Snackbar`.
2482 if (saveFormDataEnabled) {
2483 Snackbar.make(findViewById(R.id.main_webview), R.string.form_data_enabled, Snackbar.LENGTH_SHORT).show();
2485 Snackbar.make(findViewById(R.id.main_webview), R.string.form_data_disabled, Snackbar.LENGTH_SHORT).show();
2488 // Update the privacy icon. `true` runs `invalidateOptionsMenu` as the last step.
2489 updatePrivacyIcons(true);
2491 // Reload the WebView.
2492 mainWebView.reload();
2495 case R.id.clear_cookies:
2496 Snackbar.make(findViewById(R.id.main_webview), R.string.cookies_deleted, Snackbar.LENGTH_LONG)
2497 .setAction(R.string.undo, v -> {
2498 // Do nothing because everything will be handled by `onDismissed()` below.
2500 .addCallback(new Snackbar.Callback() {
2502 public void onDismissed(Snackbar snackbar, int event) {
2504 // The user pushed the `Undo` button.
2505 case Snackbar.Callback.DISMISS_EVENT_ACTION:
2509 // The `Snackbar` was dismissed without the `Undo` button being pushed.
2511 // `cookieManager.removeAllCookie()` varies by SDK.
2512 if (Build.VERSION.SDK_INT < 21) {
2513 cookieManager.removeAllCookie();
2515 // `null` indicates no callback.
2516 cookieManager.removeAllCookies(null);
2524 case R.id.clear_dom_storage:
2525 Snackbar.make(findViewById(R.id.main_webview), R.string.dom_storage_deleted, Snackbar.LENGTH_LONG)
2526 .setAction(R.string.undo, v -> {
2527 // Do nothing because everything will be handled by `onDismissed()` below.
2529 .addCallback(new Snackbar.Callback() {
2531 public void onDismissed(Snackbar snackbar, int event) {
2533 // The user pushed the `Undo` button.
2534 case Snackbar.Callback.DISMISS_EVENT_ACTION:
2538 // The `Snackbar` was dismissed without the `Undo` button being pushed.
2540 // Delete the DOM Storage.
2541 WebStorage webStorage = WebStorage.getInstance();
2542 webStorage.deleteAllData();
2544 // Initialize a handler to manually delete the DOM storage files and directories.
2545 Handler deleteDomStorageHandler = new Handler();
2547 // Setup a runnable to manually delete the DOM storage files and directories.
2548 Runnable deleteDomStorageRunnable = () -> {
2550 // A `String[]` must be used because the directory contains a space and `Runtime.exec` will otherwise not escape the string correctly.
2551 privacyBrowserRuntime.exec(new String[]{"rm", "-rf", privateDataDirectoryString + "/app_webview/Local Storage/"});
2553 // Multiple commands must be used because `Runtime.exec()` does not like `*`.
2554 privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/IndexedDB");
2555 privacyBrowserRuntime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager");
2556 privacyBrowserRuntime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager-journal");
2557 privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/databases");
2558 } catch (IOException e) {
2559 // Do nothing if an error is thrown.
2563 // Manually delete the DOM storage files after 200 milliseconds.
2564 deleteDomStorageHandler.postDelayed(deleteDomStorageRunnable, 200);
2571 // Form data can be remove once the minimum API >= 26.
2572 case R.id.clear_form_data:
2573 Snackbar.make(findViewById(R.id.main_webview), R.string.form_data_deleted, Snackbar.LENGTH_LONG)
2574 .setAction(R.string.undo, v -> {
2575 // Do nothing because everything will be handled by `onDismissed()` below.
2577 .addCallback(new Snackbar.Callback() {
2579 public void onDismissed(Snackbar snackbar, int event) {
2581 // The user pushed the `Undo` button.
2582 case Snackbar.Callback.DISMISS_EVENT_ACTION:
2586 // The `Snackbar` was dismissed without the `Undo` button being pushed.
2588 // Delete the form data.
2589 WebViewDatabase mainWebViewDatabase = WebViewDatabase.getInstance(getApplicationContext());
2590 mainWebViewDatabase.clearFormData();
2598 // Toggle the EasyList status.
2599 easyListEnabled = !easyListEnabled;
2601 // Update the menu checkbox.
2602 menuItem.setChecked(easyListEnabled);
2604 // Reload the main WebView.
2605 mainWebView.reload();
2608 case R.id.easyprivacy:
2609 // Toggle the EasyPrivacy status.
2610 easyPrivacyEnabled = !easyPrivacyEnabled;
2612 // Update the menu checkbox.
2613 menuItem.setChecked(easyPrivacyEnabled);
2615 // Reload the main WebView.
2616 mainWebView.reload();
2619 case R.id.fanboys_annoyance_list:
2620 // Toggle Fanboy's Annoyance List status.
2621 fanboysAnnoyanceListEnabled = !fanboysAnnoyanceListEnabled;
2623 // Update the menu checkbox.
2624 menuItem.setChecked(fanboysAnnoyanceListEnabled);
2626 // Update the staus of Fanboy's Social Blocking List.
2627 MenuItem fanboysSocialBlockingListMenuItem = mainMenu.findItem(R.id.fanboys_social_blocking_list);
2628 fanboysSocialBlockingListMenuItem.setEnabled(!fanboysAnnoyanceListEnabled);
2630 // Reload the main WebView.
2631 mainWebView.reload();
2634 case R.id.fanboys_social_blocking_list:
2635 // Toggle Fanboy's Social Blocking List status.
2636 fanboysSocialBlockingListEnabled = !fanboysSocialBlockingListEnabled;
2638 // Update the menu checkbox.
2639 menuItem.setChecked(fanboysSocialBlockingListEnabled);
2641 // Reload the main WebView.
2642 mainWebView.reload();
2645 case R.id.ultraprivacy:
2646 // Toggle the UltraPrivacy status.
2647 ultraPrivacyEnabled = !ultraPrivacyEnabled;
2649 // Update the menu checkbox.
2650 menuItem.setChecked(ultraPrivacyEnabled);
2652 // Reload the main WebView.
2653 mainWebView.reload();
2656 case R.id.block_all_third_party_requests:
2657 //Toggle the third-party requests blocker status.
2658 blockAllThirdPartyRequests = !blockAllThirdPartyRequests;
2660 // Update the menu checkbox.
2661 menuItem.setChecked(blockAllThirdPartyRequests);
2663 // Reload the main WebView.
2664 mainWebView.reload();
2667 case R.id.user_agent_privacy_browser:
2668 // Update the user agent.
2669 mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[0]);
2671 // Reload the WebView.
2672 mainWebView.reload();
2675 case R.id.user_agent_webview_default:
2676 // Update the user agent.
2677 mainWebView.getSettings().setUserAgentString("");
2679 // Reload the WebView.
2680 mainWebView.reload();
2683 case R.id.user_agent_firefox_on_android:
2684 // Update the user agent.
2685 mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[2]);
2687 // Reload the WebView.
2688 mainWebView.reload();
2691 case R.id.user_agent_chrome_on_android:
2692 // Update the user agent.
2693 mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[3]);
2695 // Reload the WebView.
2696 mainWebView.reload();
2699 case R.id.user_agent_safari_on_ios:
2700 // Update the user agent.
2701 mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[4]);
2703 // Reload the WebView.
2704 mainWebView.reload();
2707 case R.id.user_agent_firefox_on_linux:
2708 // Update the user agent.
2709 mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[5]);
2711 // Reload the WebView.
2712 mainWebView.reload();
2715 case R.id.user_agent_chromium_on_linux:
2716 // Update the user agent.
2717 mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[6]);
2719 // Reload the WebView.
2720 mainWebView.reload();
2723 case R.id.user_agent_firefox_on_windows:
2724 // Update the user agent.
2725 mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[7]);
2727 // Reload the WebView.
2728 mainWebView.reload();
2731 case R.id.user_agent_chrome_on_windows:
2732 // Update the user agent.
2733 mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[8]);
2735 // Reload the WebView.
2736 mainWebView.reload();
2739 case R.id.user_agent_edge_on_windows:
2740 // Update the user agent.
2741 mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[9]);
2743 // Reload the WebView.
2744 mainWebView.reload();
2747 case R.id.user_agent_internet_explorer_on_windows:
2748 // Update the user agent.
2749 mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[10]);
2751 // Reload the WebView.
2752 mainWebView.reload();
2755 case R.id.user_agent_safari_on_macos:
2756 // Update the user agent.
2757 mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[11]);
2759 // Reload the WebView.
2760 mainWebView.reload();
2763 case R.id.user_agent_custom:
2764 // Update the user agent.
2765 mainWebView.getSettings().setUserAgentString(defaultCustomUserAgentString);
2767 // Reload the WebView.
2768 mainWebView.reload();
2771 case R.id.font_size_twenty_five_percent:
2772 mainWebView.getSettings().setTextZoom(25);
2775 case R.id.font_size_fifty_percent:
2776 mainWebView.getSettings().setTextZoom(50);
2779 case R.id.font_size_seventy_five_percent:
2780 mainWebView.getSettings().setTextZoom(75);
2783 case R.id.font_size_one_hundred_percent:
2784 mainWebView.getSettings().setTextZoom(100);
2787 case R.id.font_size_one_hundred_twenty_five_percent:
2788 mainWebView.getSettings().setTextZoom(125);
2791 case R.id.font_size_one_hundred_fifty_percent:
2792 mainWebView.getSettings().setTextZoom(150);
2795 case R.id.font_size_one_hundred_seventy_five_percent:
2796 mainWebView.getSettings().setTextZoom(175);
2799 case R.id.font_size_two_hundred_percent:
2800 mainWebView.getSettings().setTextZoom(200);
2803 case R.id.swipe_to_refresh:
2804 // Toggle swipe to refresh.
2805 swipeRefreshLayout.setEnabled(!swipeRefreshLayout.isEnabled());
2808 case R.id.display_images:
2809 if (mainWebView.getSettings().getLoadsImagesAutomatically()) { // Images are currently loaded automatically.
2810 mainWebView.getSettings().setLoadsImagesAutomatically(false);
2811 mainWebView.reload();
2812 } else { // Images are not currently loaded automatically.
2813 mainWebView.getSettings().setLoadsImagesAutomatically(true);
2817 case R.id.night_mode:
2818 // Toggle night mode.
2819 nightMode = !nightMode;
2821 // Enable or disable JavaScript according to night mode, the global preference, and any domain settings.
2822 if (nightMode) { // Night mode is enabled. Enable JavaScript.
2823 // Update the global variable.
2824 javaScriptEnabled = true;
2825 } else if (domainSettingsApplied) { // Night mode is disabled and domain settings are applied. Set JavaScript according to the domain settings.
2826 // Get the JavaScript preference that was stored the last time domain settings were loaded.
2827 javaScriptEnabled = domainSettingsJavaScriptEnabled;
2828 } else { // Night mode is disabled and domain settings are not applied. Set JavaScript according to the global preference.
2829 // Get a handle for the shared preference.
2830 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
2832 // Get the JavaScript preference.
2833 javaScriptEnabled = sharedPreferences.getBoolean("javascript", false);
2836 // Apply the JavaScript setting to the WebView.
2837 mainWebView.getSettings().setJavaScriptEnabled(javaScriptEnabled);
2839 // Update the privacy icons.
2840 updatePrivacyIcons(false);
2842 // Reload the website.
2843 mainWebView.reload();
2847 // Get a `PrintManager` instance.
2848 PrintManager printManager = (PrintManager) getSystemService(Context.PRINT_SERVICE);
2850 // Convert `mainWebView` to `printDocumentAdapter`.
2851 PrintDocumentAdapter printDocumentAdapter = mainWebView.createPrintDocumentAdapter();
2853 // Remove the lint error below that `printManager` might be `null`.
2854 assert printManager != null;
2856 // Print the document. The print attributes are `null`.
2857 printManager.print(getString(R.string.privacy_browser_web_page), printDocumentAdapter, null);
2860 case R.id.view_source:
2861 // Launch the View Source activity.
2862 Intent viewSourceIntent = new Intent(this, ViewSourceActivity.class);
2863 startActivity(viewSourceIntent);
2866 case R.id.proxy_through_orbot:
2867 // Toggle the proxy through Orbot variable.
2868 proxyThroughOrbot = !proxyThroughOrbot;
2870 // Apply the proxy through Orbot settings.
2871 applyProxyThroughOrbot(true);
2875 // Setup the share string.
2876 String shareString = webViewTitle + " – " + urlTextBox.getText().toString();
2878 // Create the share intent.
2879 Intent shareIntent = new Intent(Intent.ACTION_SEND);
2880 shareIntent.putExtra(Intent.EXTRA_TEXT, shareString);
2881 shareIntent.setType("text/plain");
2884 startActivity(Intent.createChooser(shareIntent, getString(R.string.share_url)));
2887 case R.id.open_with:
2888 // Convert the URL to an URI.
2889 Uri shareUri = Uri.parse(formattedUrlString);
2892 String shareHost = shareUri.getHost();
2894 // Create the open with intent with `ACTION_VIEW`.
2895 Intent openWithIntent = new Intent(Intent.ACTION_VIEW);
2897 // Set the data based on the host.
2898 if ((shareHost != null) && (shareHost.endsWith("youtube.com") || shareHost.equals("play.google.com") || shareHost.equals("f-droid.org"))) { // Handle App URLs.
2899 // Set the URI but not the MIME type. This should open all available apps.
2900 openWithIntent.setData(shareUri);
2901 } else { // Handle a generic URL.
2902 // Set the URI and the MIME type. `"text/html"` should load browser options.
2903 openWithIntent.setDataAndType(shareUri, "text/html");
2906 // Flag the intent to open in a new task.
2907 openWithIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
2909 // Show the chooser.
2910 startActivity(Intent.createChooser(openWithIntent, getString(R.string.open_with)));
2913 case R.id.find_on_page:
2914 // Hide the URL app bar.
2915 supportAppBar.setVisibility(View.GONE);
2917 // Show the Find on Page `RelativeLayout`.
2918 findOnPageLinearLayout.setVisibility(View.VISIBLE);
2920 // Display the keyboard. We have to wait 200 ms before running the command to work around a bug in Android.
2921 // http://stackoverflow.com/questions/5520085/android-show-softkeyboard-with-showsoftinput-is-not-working
2922 findOnPageEditText.postDelayed(() -> {
2923 // Set the focus on `findOnPageEditText`.
2924 findOnPageEditText.requestFocus();
2926 // Display the keyboard. `0` sets no input flags.
2927 inputMethodManager.showSoftInput(findOnPageEditText, 0);
2931 case R.id.add_to_homescreen:
2932 // Show the `CreateHomeScreenShortcutDialog` `AlertDialog` and name this instance `R.string.create_shortcut`.
2933 AppCompatDialogFragment createHomeScreenShortcutDialogFragment = new CreateHomeScreenShortcutDialog();
2934 createHomeScreenShortcutDialogFragment.show(getSupportFragmentManager(), getString(R.string.create_shortcut));
2936 //Everything else will be handled by `CreateHomeScreenShortcutDialog` and the associated listener below.
2940 if (menuItem.getTitle().equals(getString(R.string.refresh))) { // The refresh button was pushed.
2941 // Reload the WebView.
2942 mainWebView.reload();
2943 } else { // The stop button was pushed.
2944 // Stop the loading of the WebView.
2945 mainWebView.stopLoading();
2949 case R.id.ad_consent:
2950 // Display the ad consent dialog.
2951 DialogFragment adConsentDialogFragment = new AdConsentDialog();
2952 adConsentDialogFragment.show(getFragmentManager(), getString(R.string.ad_consent));
2956 // Don't consume the event.
2957 return super.onOptionsItemSelected(menuItem);
2961 // removeAllCookies is deprecated, but it is required for API < 21.
2962 @SuppressWarnings("deprecation")
2964 public boolean onNavigationItemSelected(@NonNull MenuItem menuItem) {
2965 int menuItemId = menuItem.getItemId();
2967 switch (menuItemId) {
2973 if (mainWebView.canGoBack()) {
2974 // Reset the formatted URL string so the page will load correctly if blocking of third-party requests is enabled.
2975 formattedUrlString = "";
2977 // Set `navigatingHistory` so that the domain settings are applied when the new URL is loaded.
2978 navigatingHistory = true;
2980 // Load the previous website in the history.
2981 mainWebView.goBack();
2986 if (mainWebView.canGoForward()) {
2987 // Reset the formatted URL string so the page will load correctly if blocking of third-party requests is enabled.
2988 formattedUrlString = "";
2990 // Set `navigatingHistory` so that the domain settings are applied when the new URL is loaded.
2991 navigatingHistory = true;
2993 // Load the next website in the history.
2994 mainWebView.goForward();
2999 // Get the `WebBackForwardList`.
3000 WebBackForwardList webBackForwardList = mainWebView.copyBackForwardList();
3002 // Show the `UrlHistoryDialog` `AlertDialog` and name this instance `R.string.history`. `this` is the `Context`.
3003 AppCompatDialogFragment urlHistoryDialogFragment = UrlHistoryDialog.loadBackForwardList(this, webBackForwardList);
3004 urlHistoryDialogFragment.show(getSupportFragmentManager(), getString(R.string.history));
3008 // Launch the requests activity.
3009 Intent requestsIntent = new Intent(this, RequestsActivity.class);
3010 startActivity(requestsIntent);
3013 case R.id.downloads:
3014 // Launch the system Download Manager.
3015 Intent downloadManagerIntent = new Intent(DownloadManager.ACTION_VIEW_DOWNLOADS);
3017 // Launch as a new task so that Download Manager and Privacy Browser show as separate windows in the recent tasks list.
3018 downloadManagerIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
3020 startActivity(downloadManagerIntent);
3024 // Set the flag to reapply the domain settings on restart when returning from Domain Settings.
3025 reapplyDomainSettingsOnRestart = true;
3026 currentDomainName = "";
3028 // Launch the domains activity.
3029 Intent domainsIntent = new Intent(this, DomainsActivity.class);
3030 startActivity(domainsIntent);
3034 // Set the flag to reapply app settings on restart when returning from Settings.
3035 reapplyAppSettingsOnRestart = true;
3037 // Set the flag to reapply the domain settings on restart when returning from Settings.
3038 reapplyDomainSettingsOnRestart = true;
3039 currentDomainName = "";
3041 // Launch the settings activity.
3042 Intent settingsIntent = new Intent(this, SettingsActivity.class);
3043 startActivity(settingsIntent);
3046 case R.id.import_export:
3047 // Launch the import/export activity.
3048 Intent importExportIntent = new Intent (this, ImportExportActivity.class);
3049 startActivity(importExportIntent);
3053 // Launch `GuideActivity`.
3054 Intent guideIntent = new Intent(this, GuideActivity.class);
3055 startActivity(guideIntent);
3059 // Launch `AboutActivity`.
3060 Intent aboutIntent = new Intent(this, AboutActivity.class);
3061 startActivity(aboutIntent);
3064 case R.id.clearAndExit:
3065 // Close the bookmarks cursor and database.
3066 bookmarksCursor.close();
3067 bookmarksDatabaseHelper.close();
3069 // Get a handle for `sharedPreferences`. `this` references the current context.
3070 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
3072 boolean clearEverything = sharedPreferences.getBoolean("clear_everything", true);
3075 if (clearEverything || sharedPreferences.getBoolean("clear_cookies", true)) {
3076 // The command to remove cookies changed slightly in API 21.
3077 if (Build.VERSION.SDK_INT >= 21) {
3078 cookieManager.removeAllCookies(null);
3080 cookieManager.removeAllCookie();
3083 // Manually delete the cookies database, as `CookieManager` sometimes will not flush its changes to disk before `System.exit(0)` is run.
3085 // We have to use two commands because `Runtime.exec()` does not like `*`.
3086 privacyBrowserRuntime.exec("rm -f " + privateDataDirectoryString + "/app_webview/Cookies");
3087 privacyBrowserRuntime.exec("rm -f " + privateDataDirectoryString + "/app_webview/Cookies-journal");
3088 } catch (IOException e) {
3089 // Do nothing if an error is thrown.
3093 // Clear DOM storage.
3094 if (clearEverything || sharedPreferences.getBoolean("clear_dom_storage", true)) {
3095 // Ask `WebStorage` to clear the DOM storage.
3096 WebStorage webStorage = WebStorage.getInstance();
3097 webStorage.deleteAllData();
3099 // Manually delete the DOM storage files and directories, as `WebStorage` sometimes will not flush its changes to disk before `System.exit(0)` is run.
3101 // A `String[]` must be used because the directory contains a space and `Runtime.exec` will otherwise not escape the string correctly.
3102 privacyBrowserRuntime.exec(new String[] {"rm", "-rf", privateDataDirectoryString + "/app_webview/Local Storage/"});
3104 // Multiple commands must be used because `Runtime.exec()` does not like `*`.
3105 privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/IndexedDB");
3106 privacyBrowserRuntime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager");
3107 privacyBrowserRuntime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager-journal");
3108 privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/databases");
3109 } catch (IOException e) {
3110 // Do nothing if an error is thrown.
3114 // Clear form data if the API < 26.
3115 if ((Build.VERSION.SDK_INT < 26) && (clearEverything || sharedPreferences.getBoolean("clear_form_data", true))) {
3116 WebViewDatabase webViewDatabase = WebViewDatabase.getInstance(this);
3117 webViewDatabase.clearFormData();
3119 // Manually delete the form data database, as `WebViewDatabase` sometimes will not flush its changes to disk before `System.exit(0)` is run.
3121 // We have to use a `String[]` because the database contains a space and `Runtime.exec` will not escape the string correctly otherwise.
3122 privacyBrowserRuntime.exec(new String[] {"rm", "-f", privateDataDirectoryString + "/app_webview/Web Data"});
3123 privacyBrowserRuntime.exec(new String[] {"rm", "-f", privateDataDirectoryString + "/app_webview/Web Data-journal"});
3124 } catch (IOException e) {
3125 // Do nothing if an error is thrown.
3130 if (clearEverything || sharedPreferences.getBoolean("clear_cache", true)) {
3131 // `true` includes disk files.
3132 mainWebView.clearCache(true);
3134 // Manually delete the cache directories.
3136 // Delete the main cache directory.
3137 privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/cache");
3139 // Delete the secondary `Service Worker` cache directory.
3140 // A `String[]` must be used because the directory contains a space and `Runtime.exec` will not escape the string correctly otherwise.
3141 privacyBrowserRuntime.exec(new String[] {"rm", "-rf", privateDataDirectoryString + "/app_webview/Service Worker/"});
3142 } catch (IOException e) {
3143 // Do nothing if an error is thrown.
3147 // Clear SSL certificate preferences.
3148 mainWebView.clearSslPreferences();
3150 // Clear the back/forward history.
3151 mainWebView.clearHistory();
3153 // Clear `formattedUrlString`.
3154 formattedUrlString = null;
3156 // Clear `customHeaders`.
3157 customHeaders.clear();
3159 // Detach all views from `mainWebViewRelativeLayout`.
3160 mainWebViewRelativeLayout.removeAllViews();
3162 // Destroy the internal state of `mainWebView`.
3163 mainWebView.destroy();
3165 // Manually delete the `app_webview` folder, which contains the cookies, DOM storage, form data, and `Service Worker` cache.
3166 // See `https://code.google.com/p/android/issues/detail?id=233826&thanks=233826&ts=1486670530`.
3167 if (clearEverything) {
3169 privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/app_webview");
3170 } catch (IOException e) {
3171 // Do nothing if an error is thrown.
3175 // Close Privacy Browser. `finishAndRemoveTask` also removes Privacy Browser from the recent app list.
3176 if (Build.VERSION.SDK_INT >= 21) {
3177 finishAndRemoveTask();
3182 // Remove the terminated program from RAM. The status code is `0`.
3187 // Close the navigation drawer.
3188 drawerLayout.closeDrawer(GravityCompat.START);
3193 public void onPostCreate(Bundle savedInstanceState) {
3194 super.onPostCreate(savedInstanceState);
3196 // Sync the state of the DrawerToggle after onRestoreInstanceState has finished.
3197 drawerToggle.syncState();
3201 public void onConfigurationChanged(Configuration newConfig) {
3202 super.onConfigurationChanged(newConfig);
3204 // Get the status bar pixel size.
3205 int statusBarResourceId = getResources().getIdentifier("status_bar_height", "dimen", "android");
3206 int statusBarPixelSize = getResources().getDimensionPixelSize(statusBarResourceId);
3208 // Get the resource density.
3209 float screenDensity = getResources().getDisplayMetrics().density;
3211 // Recalculate the drawer header padding.
3212 drawerHeaderPaddingLeftAndRight = (int) (15 * screenDensity);
3213 drawerHeaderPaddingTop = statusBarPixelSize + (int) (4 * screenDensity);
3214 drawerHeaderPaddingBottom = (int) (8 * screenDensity);
3216 // Reload the ad for the free flavor if not in full screen mode.
3217 if (BuildConfig.FLAVOR.contentEquals("free") && !inFullScreenBrowsingMode) {
3218 // Reload the ad. The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations.
3219 AdHelper.loadAd(findViewById(R.id.adview), getApplicationContext(), getString(R.string.ad_unit_id));
3222 // `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:
3223 // https://code.google.com/p/android/issues/detail?id=20493#c8
3224 // ActivityCompat.invalidateOptionsMenu(this);
3228 public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.ContextMenuInfo menuInfo) {
3229 // Store the `HitTestResult`.
3230 final WebView.HitTestResult hitTestResult = mainWebView.getHitTestResult();
3233 final String imageUrl;
3234 final String linkUrl;
3236 // Get a handle for the `ClipboardManager`.
3237 final ClipboardManager clipboardManager = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
3239 // Remove the lint errors below that `clipboardManager` might be `null`.
3240 assert clipboardManager != null;
3242 switch (hitTestResult.getType()) {
3243 // `SRC_ANCHOR_TYPE` is a link.
3244 case WebView.HitTestResult.SRC_ANCHOR_TYPE:
3245 // Get the target URL.
3246 linkUrl = hitTestResult.getExtra();
3248 // Set the target URL as the title of the `ContextMenu`.
3249 menu.setHeaderTitle(linkUrl);
3251 // Add a Load URL entry.
3252 menu.add(R.string.load_url).setOnMenuItemClickListener((MenuItem item) -> {
3257 // Add a Copy URL entry.
3258 menu.add(R.string.copy_url).setOnMenuItemClickListener((MenuItem item) -> {
3259 // Save the link URL in a `ClipData`.
3260 ClipData srcAnchorTypeClipData = ClipData.newPlainText(getString(R.string.url), linkUrl);
3262 // Set the `ClipData` as the clipboard's primary clip.
3263 clipboardManager.setPrimaryClip(srcAnchorTypeClipData);
3267 // Add a Download URL entry.
3268 menu.add(R.string.download_url).setOnMenuItemClickListener((MenuItem item) -> {
3269 // Check if the download should be processed by an external app.
3270 if (downloadWithExternalApp) { // Download with an external app.
3271 openUrlWithExternalApp(linkUrl);
3272 } else { // Download with Android's download manager.
3273 // Check to see if the storage permission has already been granted.
3274 if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) { // The storage permission needs to be requested.
3275 // Store the variables for future use by `onRequestPermissionsResult()`.
3276 downloadUrl = linkUrl;
3277 downloadContentDisposition = "none";
3278 downloadContentLength = -1;
3280 // Show a dialog if the user has previously denied the permission.
3281 if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) { // Show a dialog explaining the request first.
3282 // Instantiate the download location permission alert dialog and set the download type to DOWNLOAD_FILE.
3283 DialogFragment downloadLocationPermissionDialogFragment = DownloadLocationPermissionDialog.downloadType(DownloadLocationPermissionDialog.DOWNLOAD_FILE);
3285 // Show the download location permission alert dialog. The permission will be requested when the the dialog is closed.
3286 downloadLocationPermissionDialogFragment.show(getFragmentManager(), getString(R.string.download_location));
3287 } else { // Show the permission request directly.
3288 // Request the permission. The download dialog will be launched by `onRequestPermissionResult()`.
3289 ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_FILE_REQUEST_CODE);
3291 } else { // The storage permission has already been granted.
3292 // Get a handle for the download file alert dialog.
3293 AppCompatDialogFragment downloadFileDialogFragment = DownloadFileDialog.fromUrl(linkUrl, "none", -1);
3295 // Show the download file alert dialog.
3296 downloadFileDialogFragment.show(getSupportFragmentManager(), getString(R.string.download));
3302 // Add a `Cancel` entry, which by default closes the `ContextMenu`.
3303 menu.add(R.string.cancel);
3306 case WebView.HitTestResult.EMAIL_TYPE:
3307 // Get the target URL.
3308 linkUrl = hitTestResult.getExtra();
3310 // Set the target URL as the title of the `ContextMenu`.
3311 menu.setHeaderTitle(linkUrl);
3313 // Add a `Write Email` entry.
3314 menu.add(R.string.write_email).setOnMenuItemClickListener(item -> {
3315 // Use `ACTION_SENDTO` instead of `ACTION_SEND` so that only email programs are launched.
3316 Intent emailIntent = new Intent(Intent.ACTION_SENDTO);
3318 // Parse the url and set it as the data for the `Intent`.
3319 emailIntent.setData(Uri.parse("mailto:" + linkUrl));
3321 // `FLAG_ACTIVITY_NEW_TASK` opens the email program in a new task instead as part of Privacy Browser.
3322 emailIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
3325 startActivity(emailIntent);
3329 // Add a `Copy Email Address` entry.
3330 menu.add(R.string.copy_email_address).setOnMenuItemClickListener(item -> {
3331 // Save the email address in a `ClipData`.
3332 ClipData srcEmailTypeClipData = ClipData.newPlainText(getString(R.string.email_address), linkUrl);
3334 // Set the `ClipData` as the clipboard's primary clip.
3335 clipboardManager.setPrimaryClip(srcEmailTypeClipData);
3339 // Add a `Cancel` entry, which by default closes the `ContextMenu`.
3340 menu.add(R.string.cancel);
3343 // `IMAGE_TYPE` is an image.
3344 case WebView.HitTestResult.IMAGE_TYPE:
3345 // Get the image URL.
3346 imageUrl = hitTestResult.getExtra();
3348 // Set the image URL as the title of the `ContextMenu`.
3349 menu.setHeaderTitle(imageUrl);
3351 // Add a `View Image` entry.
3352 menu.add(R.string.view_image).setOnMenuItemClickListener(item -> {
3357 // Add a `Download Image` entry.
3358 menu.add(R.string.download_image).setOnMenuItemClickListener((MenuItem item) -> {
3359 // Check if the download should be processed by an external app.
3360 if (downloadWithExternalApp) { // Download with an external app.
3361 openUrlWithExternalApp(imageUrl);
3362 } else { // Download with Android's download manager.
3363 // Check to see if the storage permission has already been granted.
3364 if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) { // The storage permission needs to be requested.
3365 // Store the image URL for use by `onRequestPermissionResult()`.
3366 downloadImageUrl = imageUrl;
3368 // Show a dialog if the user has previously denied the permission.
3369 if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) { // Show a dialog explaining the request first.
3370 // Instantiate the download location permission alert dialog and set the download type to DOWNLOAD_IMAGE.
3371 DialogFragment downloadLocationPermissionDialogFragment = DownloadLocationPermissionDialog.downloadType(DownloadLocationPermissionDialog.DOWNLOAD_IMAGE);
3373 // Show the download location permission alert dialog. The permission will be requested when the dialog is closed.
3374 downloadLocationPermissionDialogFragment.show(getFragmentManager(), getString(R.string.download_location));
3375 } else { // Show the permission request directly.
3376 // Request the permission. The download dialog will be launched by `onRequestPermissionResult().
3377 ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_IMAGE_REQUEST_CODE);
3379 } else { // The storage permission has already been granted.
3380 // Get a handle for the download image alert dialog.
3381 AppCompatDialogFragment downloadImageDialogFragment = DownloadImageDialog.imageUrl(imageUrl);
3383 // Show the download image alert dialog.
3384 downloadImageDialogFragment.show(getSupportFragmentManager(), getString(R.string.download));
3390 // Add a `Copy URL` entry.
3391 menu.add(R.string.copy_url).setOnMenuItemClickListener(item -> {
3392 // Save the image URL in a `ClipData`.
3393 ClipData srcImageTypeClipData = ClipData.newPlainText(getString(R.string.url), imageUrl);
3395 // Set the `ClipData` as the clipboard's primary clip.
3396 clipboardManager.setPrimaryClip(srcImageTypeClipData);
3400 // Add a `Cancel` entry, which by default closes the `ContextMenu`.
3401 menu.add(R.string.cancel);
3405 // `SRC_IMAGE_ANCHOR_TYPE` is an image that is also a link.
3406 case WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE:
3407 // Get the image URL.
3408 imageUrl = hitTestResult.getExtra();
3410 // Set the image URL as the title of the `ContextMenu`.
3411 menu.setHeaderTitle(imageUrl);
3413 // Add a `View Image` entry.
3414 menu.add(R.string.view_image).setOnMenuItemClickListener(item -> {
3419 // Add a `Download Image` entry.
3420 menu.add(R.string.download_image).setOnMenuItemClickListener((MenuItem item) -> {
3421 // Check if the download should be processed by an external app.
3422 if (downloadWithExternalApp) { // Download with an external app.
3423 openUrlWithExternalApp(imageUrl);
3424 } else { // Download with Android's download manager.
3425 // Check to see if the storage permission has already been granted.
3426 if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) { // The storage permission needs to be requested.
3427 // Store the image URL for use by `onRequestPermissionResult()`.
3428 downloadImageUrl = imageUrl;
3430 // Show a dialog if the user has previously denied the permission.
3431 if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) { // Show a dialog explaining the request first.
3432 // Instantiate the download location permission alert dialog and set the download type to DOWNLOAD_IMAGE.
3433 DialogFragment downloadLocationPermissionDialogFragment = DownloadLocationPermissionDialog.downloadType(DownloadLocationPermissionDialog.DOWNLOAD_IMAGE);
3435 // Show the download location permission alert dialog. The permission will be requested when the dialog is closed.
3436 downloadLocationPermissionDialogFragment.show(getFragmentManager(), getString(R.string.download_location));
3437 } else { // Show the permission request directly.
3438 // Request the permission. The download dialog will be launched by `onRequestPermissionResult().
3439 ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_IMAGE_REQUEST_CODE);
3441 } else { // The storage permission has already been granted.
3442 // Get a handle for the download image alert dialog.
3443 AppCompatDialogFragment downloadImageDialogFragment = DownloadImageDialog.imageUrl(imageUrl);
3445 // Show the download image alert dialog.
3446 downloadImageDialogFragment.show(getSupportFragmentManager(), getString(R.string.download));
3452 // Add a `Copy URL` entry.
3453 menu.add(R.string.copy_url).setOnMenuItemClickListener(item -> {
3454 // Save the image URL in a `ClipData`.
3455 ClipData srcImageAnchorTypeClipData = ClipData.newPlainText(getString(R.string.url), imageUrl);
3457 // Set the `ClipData` as the clipboard's primary clip.
3458 clipboardManager.setPrimaryClip(srcImageAnchorTypeClipData);
3462 // Add a `Cancel` entry, which by default closes the `ContextMenu`.
3463 menu.add(R.string.cancel);
3469 public void onCreateBookmark(AppCompatDialogFragment dialogFragment) {
3470 // Get the `EditTexts` from the `dialogFragment`.
3471 EditText createBookmarkNameEditText = dialogFragment.getDialog().findViewById(R.id.create_bookmark_name_edittext);
3472 EditText createBookmarkUrlEditText = dialogFragment.getDialog().findViewById(R.id.create_bookmark_url_edittext);
3474 // Extract the strings from the `EditTexts`.
3475 String bookmarkNameString = createBookmarkNameEditText.getText().toString();
3476 String bookmarkUrlString = createBookmarkUrlEditText.getText().toString();
3478 // Convert the favoriteIcon Bitmap to a byte array. `0` is for lossless compression (the only option for a PNG).
3479 ByteArrayOutputStream favoriteIconByteArrayOutputStream = new ByteArrayOutputStream();
3480 favoriteIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, favoriteIconByteArrayOutputStream);
3481 byte[] favoriteIconByteArray = favoriteIconByteArrayOutputStream.toByteArray();
3483 // Display the new bookmark below the current items in the (0 indexed) list.
3484 int newBookmarkDisplayOrder = bookmarksListView.getCount();
3486 // Create the bookmark.
3487 bookmarksDatabaseHelper.createBookmark(bookmarkNameString, bookmarkUrlString, currentBookmarksFolder, newBookmarkDisplayOrder, favoriteIconByteArray);
3489 // Update `bookmarksCursor` with the current contents of this folder.
3490 bookmarksCursor = bookmarksDatabaseHelper.getAllBookmarksCursorByDisplayOrder(currentBookmarksFolder);
3492 // Update the `ListView`.
3493 bookmarksCursorAdapter.changeCursor(bookmarksCursor);
3495 // Scroll to the new bookmark.
3496 bookmarksListView.setSelection(newBookmarkDisplayOrder);
3500 public void onCreateBookmarkFolder(AppCompatDialogFragment dialogFragment) {
3501 // Get handles for the views in `dialogFragment`.
3502 EditText createFolderNameEditText = dialogFragment.getDialog().findViewById(R.id.create_folder_name_edittext);
3503 RadioButton defaultFolderIconRadioButton = dialogFragment.getDialog().findViewById(R.id.create_folder_default_icon_radiobutton);
3504 ImageView folderIconImageView = dialogFragment.getDialog().findViewById(R.id.create_folder_default_icon);
3506 // Get new folder name string.
3507 String folderNameString = createFolderNameEditText.getText().toString();
3509 // Get the new folder icon `Bitmap`.
3510 Bitmap folderIconBitmap;
3511 if (defaultFolderIconRadioButton.isChecked()) { // Use the default folder icon.
3512 // Get the default folder icon and convert it to a `Bitmap`.
3513 Drawable folderIconDrawable = folderIconImageView.getDrawable();
3514 BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable;
3515 folderIconBitmap = folderIconBitmapDrawable.getBitmap();
3516 } else { // Use the `WebView` favorite icon.
3517 folderIconBitmap = favoriteIconBitmap;
3520 // Convert `folderIconBitmap` to a byte array. `0` is for lossless compression (the only option for a PNG).
3521 ByteArrayOutputStream folderIconByteArrayOutputStream = new ByteArrayOutputStream();
3522 folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, folderIconByteArrayOutputStream);
3523 byte[] folderIconByteArray = folderIconByteArrayOutputStream.toByteArray();
3525 // Move all the bookmarks down one in the display order.
3526 for (int i = 0; i < bookmarksListView.getCount(); i++) {
3527 int databaseId = (int) bookmarksListView.getItemIdAtPosition(i);
3528 bookmarksDatabaseHelper.updateDisplayOrder(databaseId, i + 1);
3531 // Create the folder, which will be placed at the top of the `ListView`.
3532 bookmarksDatabaseHelper.createFolder(folderNameString, currentBookmarksFolder, folderIconByteArray);
3534 // Update `bookmarksCursor` with the current contents of this folder.
3535 bookmarksCursor = bookmarksDatabaseHelper.getAllBookmarksCursorByDisplayOrder(currentBookmarksFolder);
3537 // Update the `ListView`.
3538 bookmarksCursorAdapter.changeCursor(bookmarksCursor);
3540 // Scroll to the new folder.
3541 bookmarksListView.setSelection(0);
3545 public void onCreateHomeScreenShortcut(AppCompatDialogFragment dialogFragment) {
3546 // Get the shortcut name.
3547 EditText shortcutNameEditText = dialogFragment.getDialog().findViewById(R.id.shortcut_name_edittext);
3548 String shortcutNameString = shortcutNameEditText.getText().toString();
3550 // Convert the favorite icon bitmap to an `Icon`. `IconCompat` is required until API >= 26.
3551 IconCompat favoriteIcon = IconCompat.createWithBitmap(favoriteIconBitmap);
3553 // Setup the shortcut intent.
3554 Intent shortcutIntent = new Intent(Intent.ACTION_VIEW);
3555 shortcutIntent.setData(Uri.parse(formattedUrlString));
3557 // Create a shortcut info builder. The shortcut name becomes the shortcut ID.
3558 ShortcutInfoCompat.Builder shortcutInfoBuilder = new ShortcutInfoCompat.Builder(this, shortcutNameString);
3560 // Add the required fields to the shortcut info builder.
3561 shortcutInfoBuilder.setIcon(favoriteIcon);
3562 shortcutInfoBuilder.setIntent(shortcutIntent);
3563 shortcutInfoBuilder.setShortLabel(shortcutNameString);
3565 // Request the pin. `ShortcutManagerCompat` can be switched to `ShortcutManager` once API >= 26.
3566 ShortcutManagerCompat.requestPinShortcut(this, shortcutInfoBuilder.build(), null);
3570 public void onCloseDownloadLocationPermissionDialog(int downloadType) {
3571 switch (downloadType) {
3572 case DownloadLocationPermissionDialog.DOWNLOAD_FILE:
3573 // Request the WRITE_EXTERNAL_STORAGE permission with a file request code.
3574 ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_FILE_REQUEST_CODE);
3577 case DownloadLocationPermissionDialog.DOWNLOAD_IMAGE:
3578 // Request the WRITE_EXTERNAL_STORAGE permission with an image request code.
3579 ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_IMAGE_REQUEST_CODE);
3585 public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
3586 switch (requestCode) {
3587 case DOWNLOAD_FILE_REQUEST_CODE:
3588 // Show the download file alert dialog. When the dialog closes, the correct command will be used based on the permission status.
3589 AppCompatDialogFragment downloadFileDialogFragment = DownloadFileDialog.fromUrl(downloadUrl, downloadContentDisposition, downloadContentLength);
3591 // On API 23, displaying the fragment must be delayed or the app will crash.
3592 if (Build.VERSION.SDK_INT == 23) {
3593 new Handler().postDelayed(() -> downloadFileDialogFragment.show(getSupportFragmentManager(), getString(R.string.download)), 500);
3595 downloadFileDialogFragment.show(getSupportFragmentManager(), getString(R.string.download));
3598 // Reset the download variables.
3600 downloadContentDisposition = "";
3601 downloadContentLength = 0;
3604 case DOWNLOAD_IMAGE_REQUEST_CODE:
3605 // Show the download image alert dialog. When the dialog closes, the correct command will be used based on the permission status.
3606 AppCompatDialogFragment downloadImageDialogFragment = DownloadImageDialog.imageUrl(downloadImageUrl);
3608 // On API 23, displaying the fragment must be delayed or the app will crash.
3609 if (Build.VERSION.SDK_INT == 23) {
3610 new Handler().postDelayed(() -> downloadImageDialogFragment.show(getSupportFragmentManager(), getString(R.string.download)), 500);
3612 downloadImageDialogFragment.show(getSupportFragmentManager(), getString(R.string.download));
3615 // Reset the image URL variable.
3616 downloadImageUrl = "";
3622 public void onDownloadImage(AppCompatDialogFragment dialogFragment, String imageUrl) {
3623 // Download the image if it has an HTTP or HTTPS URI.
3624 if (imageUrl.startsWith("http")) {
3625 // Get a handle for the system `DOWNLOAD_SERVICE`.
3626 DownloadManager downloadManager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
3628 // Parse `imageUrl`.
3629 DownloadManager.Request downloadRequest = new DownloadManager.Request(Uri.parse(imageUrl));
3631 // Pass cookies to download manager if cookies are enabled. This is required to download images from websites that require a login.
3632 // Code contributed 2017 Hendrik Knackstedt. Copyright assigned to Soren Stoutner <soren@stoutner.com>.
3633 if (firstPartyCookiesEnabled) {
3634 // Get the cookies for `imageUrl`.
3635 String cookies = cookieManager.getCookie(imageUrl);
3637 // Add the cookies to `downloadRequest`. In the HTTP request header, cookies are named `Cookie`.
3638 downloadRequest.addRequestHeader("Cookie", cookies);
3641 // Get the file name from the dialog fragment.
3642 EditText downloadImageNameEditText = dialogFragment.getDialog().findViewById(R.id.download_image_name);
3643 String imageName = downloadImageNameEditText.getText().toString();
3645 // Specify the download location.
3646 if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { // External write permission granted.
3647 // Download to the public download directory.
3648 downloadRequest.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, imageName);
3649 } else { // External write permission denied.
3650 // Download to the app's external download directory.
3651 downloadRequest.setDestinationInExternalFilesDir(this, Environment.DIRECTORY_DOWNLOADS, imageName);
3654 // Allow `MediaScanner` to index the download if it is a media file.
3655 downloadRequest.allowScanningByMediaScanner();
3657 // Add the URL as the description for the download.
3658 downloadRequest.setDescription(imageUrl);
3660 // Show the download notification after the download is completed.
3661 downloadRequest.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
3663 // Remove the lint warning below that `downloadManager` might be `null`.
3664 assert downloadManager != null;
3666 // Initiate the download.
3667 downloadManager.enqueue(downloadRequest);
3668 } else { // The image is not an HTTP or HTTPS URI.
3669 Snackbar.make(mainWebView, R.string.cannot_download_image, Snackbar.LENGTH_INDEFINITE).show();
3674 public void onDownloadFile(AppCompatDialogFragment dialogFragment, String downloadUrl) {
3675 // Download the file if it has an HTTP or HTTPS URI.
3676 if (downloadUrl.startsWith("http")) {
3677 // Get a handle for the system `DOWNLOAD_SERVICE`.
3678 DownloadManager downloadManager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
3680 // Parse `downloadUrl`.
3681 DownloadManager.Request downloadRequest = new DownloadManager.Request(Uri.parse(downloadUrl));
3683 // Pass cookies to download manager if cookies are enabled. This is required to download files from websites that require a login.
3684 // Code contributed 2017 Hendrik Knackstedt. Copyright assigned to Soren Stoutner <soren@stoutner.com>.
3685 if (firstPartyCookiesEnabled) {
3686 // Get the cookies for `downloadUrl`.
3687 String cookies = cookieManager.getCookie(downloadUrl);
3689 // Add the cookies to `downloadRequest`. In the HTTP request header, cookies are named `Cookie`.
3690 downloadRequest.addRequestHeader("Cookie", cookies);
3693 // Get the file name from the dialog fragment.
3694 EditText downloadFileNameEditText = dialogFragment.getDialog().findViewById(R.id.download_file_name);
3695 String fileName = downloadFileNameEditText.getText().toString();
3697 // Specify the download location.
3698 if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { // External write permission granted.
3699 // Download to the public download directory.
3700 downloadRequest.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, fileName);
3701 } else { // External write permission denied.
3702 // Download to the app's external download directory.
3703 downloadRequest.setDestinationInExternalFilesDir(this, Environment.DIRECTORY_DOWNLOADS, fileName);
3706 // Allow `MediaScanner` to index the download if it is a media file.
3707 downloadRequest.allowScanningByMediaScanner();
3709 // Add the URL as the description for the download.
3710 downloadRequest.setDescription(downloadUrl);
3712 // Show the download notification after the download is completed.
3713 downloadRequest.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
3715 // Remove the lint warning below that `downloadManager` might be `null`.
3716 assert downloadManager != null;
3718 // Initiate the download.
3719 downloadManager.enqueue(downloadRequest);
3720 } else { // The download is not an HTTP or HTTPS URI.
3721 Snackbar.make(mainWebView, R.string.cannot_download_file, Snackbar.LENGTH_INDEFINITE).show();
3726 public void onSaveBookmark(AppCompatDialogFragment dialogFragment, int selectedBookmarkDatabaseId) {
3727 // Get handles for the views from `dialogFragment`.
3728 EditText editBookmarkNameEditText = dialogFragment.getDialog().findViewById(R.id.edit_bookmark_name_edittext);
3729 EditText editBookmarkUrlEditText = dialogFragment.getDialog().findViewById(R.id.edit_bookmark_url_edittext);
3730 RadioButton currentBookmarkIconRadioButton = dialogFragment.getDialog().findViewById(R.id.edit_bookmark_current_icon_radiobutton);
3732 // Store the bookmark strings.
3733 String bookmarkNameString = editBookmarkNameEditText.getText().toString();
3734 String bookmarkUrlString = editBookmarkUrlEditText.getText().toString();
3736 // Update the bookmark.
3737 if (currentBookmarkIconRadioButton.isChecked()) { // Update the bookmark without changing the favorite icon.
3738 bookmarksDatabaseHelper.updateBookmark(selectedBookmarkDatabaseId, bookmarkNameString, bookmarkUrlString);
3739 } else { // Update the bookmark using the `WebView` favorite icon.
3740 // Convert the favorite icon to a byte array. `0` is for lossless compression (the only option for a PNG).
3741 ByteArrayOutputStream newFavoriteIconByteArrayOutputStream = new ByteArrayOutputStream();
3742 favoriteIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, newFavoriteIconByteArrayOutputStream);
3743 byte[] newFavoriteIconByteArray = newFavoriteIconByteArrayOutputStream.toByteArray();
3745 // Update the bookmark and the favorite icon.
3746 bookmarksDatabaseHelper.updateBookmark(selectedBookmarkDatabaseId, bookmarkNameString, bookmarkUrlString, newFavoriteIconByteArray);
3749 // Update `bookmarksCursor` with the current contents of this folder.
3750 bookmarksCursor = bookmarksDatabaseHelper.getAllBookmarksCursorByDisplayOrder(currentBookmarksFolder);
3752 // Update the `ListView`.
3753 bookmarksCursorAdapter.changeCursor(bookmarksCursor);
3757 public void onSaveBookmarkFolder(AppCompatDialogFragment dialogFragment, int selectedFolderDatabaseId) {
3758 // Get handles for the views from `dialogFragment`.
3759 EditText editFolderNameEditText = dialogFragment.getDialog().findViewById(R.id.edit_folder_name_edittext);
3760 RadioButton currentFolderIconRadioButton = dialogFragment.getDialog().findViewById(R.id.edit_folder_current_icon_radiobutton);
3761 RadioButton defaultFolderIconRadioButton = dialogFragment.getDialog().findViewById(R.id.edit_folder_default_icon_radiobutton);
3762 ImageView folderIconImageView = dialogFragment.getDialog().findViewById(R.id.edit_folder_default_icon_imageview);
3764 // Get the new folder name.
3765 String newFolderNameString = editFolderNameEditText.getText().toString();
3767 // Check if the favorite icon has changed.
3768 if (currentFolderIconRadioButton.isChecked()) { // Only the name has changed.
3769 // Update the name in the database.
3770 bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, oldFolderNameString, newFolderNameString);
3771 } else if (!currentFolderIconRadioButton.isChecked() && newFolderNameString.equals(oldFolderNameString)) { // Only the icon has changed.
3772 // Get the new folder icon `Bitmap`.
3773 Bitmap folderIconBitmap;
3774 if (defaultFolderIconRadioButton.isChecked()) {
3775 // Get the default folder icon and convert it to a `Bitmap`.
3776 Drawable folderIconDrawable = folderIconImageView.getDrawable();
3777 BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable;
3778 folderIconBitmap = folderIconBitmapDrawable.getBitmap();
3779 } else { // Use the `WebView` favorite icon.
3780 folderIconBitmap = favoriteIconBitmap;
3783 // Convert the folder `Bitmap` to a byte array. `0` is for lossless compression (the only option for a PNG).
3784 ByteArrayOutputStream folderIconByteArrayOutputStream = new ByteArrayOutputStream();
3785 folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, folderIconByteArrayOutputStream);
3786 byte[] folderIconByteArray = folderIconByteArrayOutputStream.toByteArray();
3788 // Update the folder icon in the database.
3789 bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, folderIconByteArray);
3790 } else { // The folder icon and the name have changed.
3791 // Get the new folder icon `Bitmap`.
3792 Bitmap folderIconBitmap;
3793 if (defaultFolderIconRadioButton.isChecked()) {
3794 // Get the default folder icon and convert it to a `Bitmap`.
3795 Drawable folderIconDrawable = folderIconImageView.getDrawable();
3796 BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable;
3797 folderIconBitmap = folderIconBitmapDrawable.getBitmap();
3798 } else { // Use the `WebView` favorite icon.
3799 folderIconBitmap = MainWebViewActivity.favoriteIconBitmap;
3802 // Convert the folder `Bitmap` to a byte array. `0` is for lossless compression (the only option for a PNG).
3803 ByteArrayOutputStream folderIconByteArrayOutputStream = new ByteArrayOutputStream();
3804 folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, folderIconByteArrayOutputStream);
3805 byte[] folderIconByteArray = folderIconByteArrayOutputStream.toByteArray();
3807 // Update the folder name and icon in the database.
3808 bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, oldFolderNameString, newFolderNameString, folderIconByteArray);
3811 // Update `bookmarksCursor` with the current contents of this folder.
3812 bookmarksCursor = bookmarksDatabaseHelper.getAllBookmarksCursorByDisplayOrder(currentBookmarksFolder);
3814 // Update the `ListView`.
3815 bookmarksCursorAdapter.changeCursor(bookmarksCursor);
3819 public void onHttpAuthenticationCancel() {
3820 // Cancel the `HttpAuthHandler`.
3821 httpAuthHandler.cancel();
3825 public void onHttpAuthenticationProceed(AppCompatDialogFragment dialogFragment) {
3826 // Get handles for the `EditTexts`.
3827 EditText usernameEditText = dialogFragment.getDialog().findViewById(R.id.http_authentication_username);
3828 EditText passwordEditText = dialogFragment.getDialog().findViewById(R.id.http_authentication_password);
3830 // Proceed with the HTTP authentication.
3831 httpAuthHandler.proceed(usernameEditText.getText().toString(), passwordEditText.getText().toString());
3834 public void viewSslCertificate(View view) {
3835 // Show the `ViewSslCertificateDialog` `AlertDialog` and name this instance `@string/view_ssl_certificate`.
3836 DialogFragment viewSslCertificateDialogFragment = new ViewSslCertificateDialog();
3837 viewSslCertificateDialogFragment.show(getFragmentManager(), getString(R.string.view_ssl_certificate));
3841 public void onSslErrorCancel() {
3842 sslErrorHandler.cancel();
3846 public void onSslErrorProceed() {
3847 sslErrorHandler.proceed();
3851 public void onSslMismatchBack() {
3852 if (mainWebView.canGoBack()) { // There is a back page in the history.
3853 // Reset the formatted URL string so the page will load correctly if blocking of third-party requests is enabled.
3854 formattedUrlString = "";
3856 // Set `navigatingHistory` so that the domain settings are applied when the new URL is loaded.
3857 navigatingHistory = true;
3860 mainWebView.goBack();
3861 } else { // There are no pages to go back to.
3862 // Load a blank page
3868 public void onSslMismatchProceed() {
3869 // Do not check the pinned SSL certificate for this domain again until the domain changes.
3870 ignorePinnedSslCertificate = true;
3874 public void onUrlHistoryEntrySelected(int moveBackOrForwardSteps) {
3875 // Reset the formatted URL string so the page will load correctly if blocking of third-party requests is enabled.
3876 formattedUrlString = "";
3878 // Set `navigatingHistory` so that the domain settings are applied when the new URL is loaded.
3879 navigatingHistory = true;
3881 // Load the history entry.
3882 mainWebView.goBackOrForward(moveBackOrForwardSteps);
3886 public void onClearHistory() {
3887 // Clear the history.
3888 mainWebView.clearHistory();
3891 // Override `onBackPressed` to handle the navigation drawer and `mainWebView`.
3893 public void onBackPressed() {
3894 if (drawerLayout.isDrawerVisible(GravityCompat.START)) { // The navigation drawer is open.
3895 // Close the navigation drawer.
3896 drawerLayout.closeDrawer(GravityCompat.START);
3897 } else if (drawerLayout.isDrawerVisible(GravityCompat.END)){ // The bookmarks drawer is open.
3898 if (currentBookmarksFolder.isEmpty()) { // The home folder is displayed.
3899 // close the bookmarks drawer.
3900 drawerLayout.closeDrawer(GravityCompat.END);
3901 } else { // A subfolder is displayed.
3902 // Place the former parent folder in `currentFolder`.
3903 currentBookmarksFolder = bookmarksDatabaseHelper.getParentFolder(currentBookmarksFolder);
3905 // Load the new folder.
3906 loadBookmarksFolder();
3909 } else if (mainWebView.canGoBack()) { // There is at least one item in the `WebView` history.
3910 // Reset the formatted URL string so the page will load correctly if blocking of third-party requests is enabled.
3911 formattedUrlString = "";
3913 // Set `navigatingHistory` so that the domain settings are applied when the new URL is loaded.
3914 navigatingHistory = true;
3917 mainWebView.goBack();
3918 } else { // There isn't anything to do in Privacy Browser.
3919 // Pass `onBackPressed()` to the system.
3920 super.onBackPressed();
3924 // 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.
3926 public void onActivityResult(int requestCode, int resultCode, Intent data) {
3927 // File uploads only work on API >= 21.
3928 if (Build.VERSION.SDK_INT >= 21) {
3929 // Pass the file to the WebView.
3930 fileChooserCallback.onReceiveValue(WebChromeClient.FileChooserParams.parseResult(resultCode, data));
3934 private void loadUrlFromTextBox() {
3935 // Get the text from urlTextBox and convert it to a string. trim() removes white spaces from the beginning and end of the string.
3936 String unformattedUrlString = urlTextBox.getText().toString().trim();
3938 // Check to see if `unformattedUrlString` is a valid URL. Otherwise, convert it into a search.
3939 if (unformattedUrlString.startsWith("content://")) {
3940 // Load the entire content URL.
3941 formattedUrlString = unformattedUrlString;
3942 } else if (Patterns.WEB_URL.matcher(unformattedUrlString).matches() || unformattedUrlString.startsWith("http://") || unformattedUrlString.startsWith("https://")
3943 || unformattedUrlString.startsWith("file://")) {
3944 // Add `https://` at the beginning if there is no protocol. Otherwise the app will segfault.
3945 if (!unformattedUrlString.startsWith("http") && !unformattedUrlString.startsWith("file://") && !unformattedUrlString.startsWith("content://")) {
3946 unformattedUrlString = "https://" + unformattedUrlString;
3949 // Initialize `unformattedUrl`.
3950 URL unformattedUrl = null;
3952 // 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.
3954 unformattedUrl = new URL(unformattedUrlString);
3955 } catch (MalformedURLException e) {
3956 e.printStackTrace();
3959 // The ternary operator (? :) makes sure that a null pointer exception is not thrown, which would happen if `.get` was called on a `null` value.
3960 String scheme = unformattedUrl != null ? unformattedUrl.getProtocol() : null;
3961 String authority = unformattedUrl != null ? unformattedUrl.getAuthority() : null;
3962 String path = unformattedUrl != null ? unformattedUrl.getPath() : null;
3963 String query = unformattedUrl != null ? unformattedUrl.getQuery() : null;
3964 String fragment = unformattedUrl != null ? unformattedUrl.getRef() : null;
3967 Uri.Builder formattedUri = new Uri.Builder();
3968 formattedUri.scheme(scheme).authority(authority).path(path).query(query).fragment(fragment);
3970 // Decode `formattedUri` as a `String` in `UTF-8`.
3972 formattedUrlString = URLDecoder.decode(formattedUri.build().toString(), "UTF-8");
3973 } catch (UnsupportedEncodingException exception) {
3974 // Load a blank string.
3975 formattedUrlString = "";
3977 } else if (unformattedUrlString.isEmpty()){ // Load a blank web site.
3978 // Load a blank string.
3979 formattedUrlString = "";
3980 } else { // Search for the contents of the URL box.
3981 // Create an encoded URL String.
3982 String encodedUrlString;
3984 // Sanitize the search input.
3986 encodedUrlString = URLEncoder.encode(unformattedUrlString, "UTF-8");
3987 } catch (UnsupportedEncodingException exception) {
3988 encodedUrlString = "";
3991 // Add the base search URL.
3992 formattedUrlString = searchURL + encodedUrlString;
3995 // 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.
3996 urlTextBox.clearFocus();
3999 loadUrl(formattedUrlString);
4002 private void loadUrl(String url) {// Apply any custom domain settings.
4003 // Set the URL as the formatted URL string so that checking third-party requests works correctly.
4004 formattedUrlString = url;
4006 // Apply the domain settings.
4007 applyDomainSettings(url, true, false);
4009 // If loading a website, set `urlIsLoading` to prevent changes in the user agent on websites with redirects from reloading the current website.
4010 urlIsLoading = !url.equals("");
4013 mainWebView.loadUrl(url, customHeaders);
4016 public void findPreviousOnPage(View view) {
4017 // Go to the previous highlighted phrase on the page. `false` goes backwards instead of forwards.
4018 mainWebView.findNext(false);
4021 public void findNextOnPage(View view) {
4022 // Go to the next highlighted phrase on the page. `true` goes forwards instead of backwards.
4023 mainWebView.findNext(true);
4026 public void closeFindOnPage(View view) {
4027 // Delete the contents of `find_on_page_edittext`.
4028 findOnPageEditText.setText(null);
4030 // Clear the highlighted phrases.
4031 mainWebView.clearMatches();
4033 // Hide the Find on Page `RelativeLayout`.
4034 findOnPageLinearLayout.setVisibility(View.GONE);
4036 // Show the URL app bar.
4037 supportAppBar.setVisibility(View.VISIBLE);
4039 // Hide the keyboard so we can see the webpage. `0` indicates no additional flags.
4040 inputMethodManager.hideSoftInputFromWindow(mainWebView.getWindowToken(), 0);
4043 private void applyAppSettings() {
4044 // Get a handle for the shared preferences.
4045 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
4047 // Store the values from the shared preferences in variables.
4048 incognitoModeEnabled = sharedPreferences.getBoolean("incognito_mode", false);
4049 boolean doNotTrackEnabled = sharedPreferences.getBoolean("do_not_track", false);
4050 proxyThroughOrbot = sharedPreferences.getBoolean("proxy_through_orbot", false);
4051 fullScreenBrowsingModeEnabled = sharedPreferences.getBoolean("full_screen_browsing_mode", false);
4052 hideSystemBarsOnFullscreen = sharedPreferences.getBoolean("hide_system_bars", false);
4053 translucentNavigationBarOnFullscreen = sharedPreferences.getBoolean("translucent_navigation_bar", true);
4054 downloadWithExternalApp = sharedPreferences.getBoolean("download_with_external_app", false);
4056 // Apply the proxy through Orbot settings.
4057 applyProxyThroughOrbot(false);
4059 // Set Do Not Track status.
4060 if (doNotTrackEnabled) {
4061 customHeaders.put("DNT", "1");
4063 customHeaders.remove("DNT");
4066 // Apply the appropriate full screen mode the `SYSTEM_UI` flags.
4067 if (fullScreenBrowsingModeEnabled && inFullScreenBrowsingMode) { // Privacy Browser is currently in full screen browsing mode.
4068 if (hideSystemBarsOnFullscreen) { // Hide everything.
4069 // Remove the translucent navigation setting if it is currently flagged.
4070 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
4072 // Remove the translucent status bar overlay.
4073 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
4075 // Remove the translucent status bar overlay on the `Drawer Layout`, which is special and needs its own command.
4076 drawerLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
4078 /* SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
4079 * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
4080 * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
4082 rootCoordinatorLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
4083 } else { // Hide everything except the status and navigation bars.
4084 // Remove any `SYSTEM_UI` flags from `rootCoordinatorLayout`.
4085 rootCoordinatorLayout.setSystemUiVisibility(0);
4087 // Add the translucent status flag if it is unset.
4088 getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
4090 if (translucentNavigationBarOnFullscreen) {
4091 // Set the navigation bar to be translucent.
4092 getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
4094 // Set the navigation bar to be black.
4095 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
4098 } else { // Privacy Browser is not in full screen browsing mode.
4099 // 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.
4100 inFullScreenBrowsingMode = false;
4102 // Show the `appBar` if `findOnPageLinearLayout` is not visible.
4103 if (findOnPageLinearLayout.getVisibility() == View.GONE) {
4107 // Show the `BannerAd` in the free flavor.
4108 if (BuildConfig.FLAVOR.contentEquals("free")) {
4109 // Initialize the ad. The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations.
4110 AdHelper.initializeAds(findViewById(R.id.adview), getApplicationContext(), getFragmentManager(), getString(R.string.google_app_id), getString(R.string.ad_unit_id));
4113 // Remove any `SYSTEM_UI` flags from `rootCoordinatorLayout`.
4114 rootCoordinatorLayout.setSystemUiVisibility(0);
4116 // Remove the translucent navigation bar flag if it is set.
4117 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
4119 // Add the translucent status flag if it is unset. This also resets `drawerLayout's` `View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN`.
4120 getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
4122 // Constrain `rootCoordinatorLayout` inside the status and navigation bars.
4123 rootCoordinatorLayout.setFitsSystemWindows(true);
4127 // `reloadWebsite` is used if returning from the Domains activity. Otherwise JavaScript might not function correctly if it is newly enabled.
4128 // The deprecated `.getDrawable()` must be used until the minimum API >= 21.
4129 @SuppressWarnings("deprecation")
4130 private boolean applyDomainSettings(String url, boolean resetFavoriteIcon, boolean reloadWebsite) {
4131 // Get the current user agent.
4132 String initialUserAgent = mainWebView.getSettings().getUserAgentString();
4134 // Initialize a variable to track if the user agent changes.
4135 boolean userAgentChanged = false;
4137 // Parse the URL into a URI.
4138 Uri uri = Uri.parse(url);
4140 // Extract the domain from `uri`.
4141 String hostName = uri.getHost();
4143 // Initialize `loadingNewDomainName`.
4144 boolean loadingNewDomainName;
4146 // If either `hostName` or `currentDomainName` are `null`, run the options for loading a new domain name.
4147 // The lint suggestion to simplify the `if` statement is incorrect, because `hostName.equals(currentDomainName)` can produce a `null object reference.`
4148 //noinspection SimplifiableIfStatement
4149 if ((hostName == null) || (currentDomainName == null)) {
4150 loadingNewDomainName = true;
4151 } else { // Determine if `hostName` equals `currentDomainName`.
4152 loadingNewDomainName = !hostName.equals(currentDomainName);
4155 // Strings don't like to be null.
4156 if (hostName == null) {
4160 // 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.
4161 if (loadingNewDomainName) {
4162 // Set the new `hostname` as the `currentDomainName`.
4163 currentDomainName = hostName;
4165 // Reset `ignorePinnedSslCertificate`.
4166 ignorePinnedSslCertificate = false;
4168 // Reset the favorite icon if specified.
4169 if (resetFavoriteIcon) {
4170 favoriteIconBitmap = favoriteIconDefaultBitmap;
4171 favoriteIconImageView.setImageBitmap(Bitmap.createScaledBitmap(favoriteIconBitmap, 64, 64, true));
4174 // Initialize the database handler. The `0` specifies the database version, but that is ignored and set instead using a constant in `DomainsDatabaseHelper`.
4175 DomainsDatabaseHelper domainsDatabaseHelper = new DomainsDatabaseHelper(this, null, null, 0);
4177 // Get a full cursor from `domainsDatabaseHelper`.
4178 Cursor domainNameCursor = domainsDatabaseHelper.getDomainNameCursorOrderedByDomain();
4180 // Initialize `domainSettingsSet`.
4181 Set<String> domainSettingsSet = new HashSet<>();
4183 // Get the domain name column index.
4184 int domainNameColumnIndex = domainNameCursor.getColumnIndex(DomainsDatabaseHelper.DOMAIN_NAME);
4186 // Populate `domainSettingsSet`.
4187 for (int i = 0; i < domainNameCursor.getCount(); i++) {
4188 // Move `domainsCursor` to the current row.
4189 domainNameCursor.moveToPosition(i);
4191 // Store the domain name in `domainSettingsSet`.
4192 domainSettingsSet.add(domainNameCursor.getString(domainNameColumnIndex));
4195 // Close `domainNameCursor.
4196 domainNameCursor.close();
4198 // Initialize variables to track if domain settings will be applied and, if so, under which name.
4199 domainSettingsApplied = false;
4200 String domainNameInDatabase = null;
4202 // Check the hostname.
4203 if (domainSettingsSet.contains(hostName)) {
4204 domainSettingsApplied = true;
4205 domainNameInDatabase = hostName;
4208 // Check all the subdomains of the host name against wildcard domains in the domain cursor.
4209 while (!domainSettingsApplied && hostName.contains(".")) { // Stop checking if domain settings are already applied or there are no more `.` in the host name.
4210 if (domainSettingsSet.contains("*." + hostName)) { // Check the host name prepended by `*.`.
4211 // Apply the domain settings.
4212 domainSettingsApplied = true;
4214 // Store the applied domain names as it appears in the database.
4215 domainNameInDatabase = "*." + hostName;
4218 // Strip out the lowest subdomain of of the host name.
4219 hostName = hostName.substring(hostName.indexOf(".") + 1);
4223 // Get a handle for the shared preference.
4224 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
4226 // Store the general preference information.
4227 String defaultFontSizeString = sharedPreferences.getString("font_size", getString(R.string.font_size_default_value));
4228 String defaultUserAgentName = sharedPreferences.getString("user_agent", getString(R.string.user_agent_default_value));
4229 defaultCustomUserAgentString = sharedPreferences.getString("custom_user_agent", getString(R.string.custom_user_agent_default_value));
4230 boolean defaultSwipeToRefresh = sharedPreferences.getBoolean("swipe_to_refresh", true);
4231 nightMode = sharedPreferences.getBoolean("night_mode", false);
4232 boolean displayWebpageImages = sharedPreferences.getBoolean("display_webpage_images", true);
4234 if (domainSettingsApplied) { // The url has custom domain settings.
4235 // Get a cursor for the current host and move it to the first position.
4236 Cursor currentHostDomainSettingsCursor = domainsDatabaseHelper.getCursorForDomainName(domainNameInDatabase);
4237 currentHostDomainSettingsCursor.moveToFirst();
4239 // Get the settings from the cursor.
4240 domainSettingsDatabaseId = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper._ID)));
4241 javaScriptEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_JAVASCRIPT)) == 1);
4242 firstPartyCookiesEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FIRST_PARTY_COOKIES)) == 1);
4243 thirdPartyCookiesEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_THIRD_PARTY_COOKIES)) == 1);
4244 domStorageEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_DOM_STORAGE)) == 1);
4245 // Form data can be removed once the minimum API >= 26.
4246 saveFormDataEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FORM_DATA)) == 1);
4247 easyListEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_EASYLIST)) == 1);
4248 easyPrivacyEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_EASYPRIVACY)) == 1);
4249 fanboysAnnoyanceListEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FANBOYS_ANNOYANCE_LIST)) == 1);
4250 fanboysSocialBlockingListEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FANBOYS_SOCIAL_BLOCKING_LIST)) == 1);
4251 ultraPrivacyEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_ULTRAPRIVACY)) == 1);
4252 blockAllThirdPartyRequests = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.BLOCK_ALL_THIRD_PARTY_REQUESTS)) == 1);
4253 String userAgentName = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.USER_AGENT));
4254 int fontSize = currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.FONT_SIZE));
4255 int swipeToRefreshInt = currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SWIPE_TO_REFRESH));
4256 int nightModeInt = currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.NIGHT_MODE));
4257 int displayWebpageImagesInt = currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.DISPLAY_IMAGES));
4258 pinnedDomainSslCertificate = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.PINNED_SSL_CERTIFICATE)) == 1);
4259 pinnedDomainSslIssuedToCNameString = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_TO_COMMON_NAME));
4260 pinnedDomainSslIssuedToONameString = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_TO_ORGANIZATION));
4261 pinnedDomainSslIssuedToUNameString = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_TO_ORGANIZATIONAL_UNIT));
4262 pinnedDomainSslIssuedByCNameString = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_BY_COMMON_NAME));
4263 pinnedDomainSslIssuedByONameString = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_BY_ORGANIZATION));
4264 pinnedDomainSslIssuedByUNameString = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_BY_ORGANIZATIONAL_UNIT));
4266 // Set `nightMode` according to `nightModeInt`. If `nightModeInt` is `DomainsDatabaseHelper.NIGHT_MODE_SYSTEM_DEFAULT` the current setting from `sharedPreferences` will be used.
4267 switch (nightModeInt) {
4268 case DomainsDatabaseHelper.NIGHT_MODE_ENABLED:
4272 case DomainsDatabaseHelper.NIGHT_MODE_DISABLED:
4277 // Store the domain JavaScript status. This is used by the options menu night mode toggle.
4278 domainSettingsJavaScriptEnabled = javaScriptEnabled;
4280 // Enable JavaScript if night mode is enabled.
4282 javaScriptEnabled = true;
4285 // Set the pinned SSL certificate start date to `null` if the saved date `long` is 0.
4286 if (currentHostDomainSettingsCursor.getLong(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_START_DATE)) == 0) {
4287 pinnedDomainSslStartDate = null;
4289 pinnedDomainSslStartDate = new Date(currentHostDomainSettingsCursor.getLong(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_START_DATE)));
4292 // Set the pinned SSL certificate end date to `null` if the saved date `long` is 0.
4293 if (currentHostDomainSettingsCursor.getLong(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_END_DATE)) == 0) {
4294 pinnedDomainSslEndDate = null;
4296 pinnedDomainSslEndDate = new Date(currentHostDomainSettingsCursor.getLong(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_END_DATE)));
4299 // Close `currentHostDomainSettingsCursor`.
4300 currentHostDomainSettingsCursor.close();
4302 // Apply the domain settings.
4303 mainWebView.getSettings().setJavaScriptEnabled(javaScriptEnabled);
4304 cookieManager.setAcceptCookie(firstPartyCookiesEnabled);
4305 mainWebView.getSettings().setDomStorageEnabled(domStorageEnabled);
4307 // Apply the form data setting if the API < 26.
4308 if (Build.VERSION.SDK_INT < 26) {
4309 mainWebView.getSettings().setSaveFormData(saveFormDataEnabled);
4312 // Apply the font size.
4313 if (fontSize == 0) { // Apply the default font size.
4314 mainWebView.getSettings().setTextZoom(Integer.valueOf(defaultFontSizeString));
4315 } else { // Apply the specified font size.
4316 mainWebView.getSettings().setTextZoom(fontSize);
4319 // Set third-party cookies status if API >= 21.
4320 if (Build.VERSION.SDK_INT >= 21) {
4321 cookieManager.setAcceptThirdPartyCookies(mainWebView, thirdPartyCookiesEnabled);
4324 // 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.
4325 // <https://redmine.stoutner.com/issues/160>
4326 if (!urlIsLoading) {
4327 // Set the user agent.
4328 if (userAgentName.equals(getString(R.string.system_default_user_agent))) { // Use the system default user agent.
4329 // Get the array position of the default user agent name.
4330 int defaultUserAgentArrayPosition = userAgentNamesArray.getPosition(defaultUserAgentName);
4332 // Set the user agent according to the system default.
4333 switch (defaultUserAgentArrayPosition) {
4334 case UNRECOGNIZED_USER_AGENT: // The default user agent name is not on the canonical list.
4335 // This is probably because it was set in an older version of Privacy Browser before the switch to persistent user agent names.
4336 mainWebView.getSettings().setUserAgentString(defaultUserAgentName);
4339 case SETTINGS_WEBVIEW_DEFAULT_USER_AGENT:
4340 // Set the user agent to `""`, which uses the default value.
4341 mainWebView.getSettings().setUserAgentString("");
4344 case SETTINGS_CUSTOM_USER_AGENT:
4345 // Set the custom user agent.
4346 mainWebView.getSettings().setUserAgentString(defaultCustomUserAgentString);
4350 // Get the user agent string from the user agent data array
4351 mainWebView.getSettings().setUserAgentString(userAgentDataArray[defaultUserAgentArrayPosition]);
4353 } else { // Set the user agent according to the stored name.
4354 // Get the array position of the user agent name.
4355 int userAgentArrayPosition = userAgentNamesArray.getPosition(userAgentName);
4357 switch (userAgentArrayPosition) {
4358 case UNRECOGNIZED_USER_AGENT: // The user agent name contains a custom user agent.
4359 mainWebView.getSettings().setUserAgentString(userAgentName);
4362 case SETTINGS_WEBVIEW_DEFAULT_USER_AGENT:
4363 // Set the user agent to `""`, which uses the default value.
4364 mainWebView.getSettings().setUserAgentString("");
4368 // Get the user agent string from the user agent data array.
4369 mainWebView.getSettings().setUserAgentString(userAgentDataArray[userAgentArrayPosition]);
4373 // Store the applied user agent string, which is used in the View Source activity.
4374 appliedUserAgentString = mainWebView.getSettings().getUserAgentString();
4376 // Update the user agent change tracker.
4377 userAgentChanged = !appliedUserAgentString.equals(initialUserAgent);
4380 // Set swipe to refresh.
4381 switch (swipeToRefreshInt) {
4382 case DomainsDatabaseHelper.SWIPE_TO_REFRESH_SYSTEM_DEFAULT:
4383 // Set swipe to refresh according to the default.
4384 swipeRefreshLayout.setEnabled(defaultSwipeToRefresh);
4387 case DomainsDatabaseHelper.SWIPE_TO_REFRESH_ENABLED:
4388 // Enable swipe to refresh.
4389 swipeRefreshLayout.setEnabled(true);
4392 case DomainsDatabaseHelper.SWIPE_TO_REFRESH_DISABLED:
4393 // Disable swipe to refresh.
4394 swipeRefreshLayout.setEnabled(false);
4397 // Set the loading of webpage images.
4398 switch (displayWebpageImagesInt) {
4399 case DomainsDatabaseHelper.DISPLAY_WEBPAGE_IMAGES_SYSTEM_DEFAULT:
4400 mainWebView.getSettings().setLoadsImagesAutomatically(displayWebpageImages);
4403 case DomainsDatabaseHelper.DISPLAY_WEBPAGE_IMAGES_ENABLED:
4404 mainWebView.getSettings().setLoadsImagesAutomatically(true);
4407 case DomainsDatabaseHelper.DISPLAY_WEBPAGE_IMAGES_DISABLED:
4408 mainWebView.getSettings().setLoadsImagesAutomatically(false);
4412 // 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.
4414 urlAppBarRelativeLayout.setBackground(getResources().getDrawable(R.drawable.url_bar_background_dark_blue));
4416 urlAppBarRelativeLayout.setBackground(getResources().getDrawable(R.drawable.url_bar_background_light_green));
4418 } else { // The new URL does not have custom domain settings. Load the defaults.
4419 // Store the values from `sharedPreferences` in variables.
4420 javaScriptEnabled = sharedPreferences.getBoolean("javascript", false);
4421 firstPartyCookiesEnabled = sharedPreferences.getBoolean("first_party_cookies", false);
4422 thirdPartyCookiesEnabled = sharedPreferences.getBoolean("third_party_cookies", false);
4423 domStorageEnabled = sharedPreferences.getBoolean("dom_storage", false);
4424 saveFormDataEnabled = sharedPreferences.getBoolean("save_form_data", false); // Form data can be removed once the minimum API >= 26.
4425 easyListEnabled = sharedPreferences.getBoolean("easylist", true);
4426 easyPrivacyEnabled = sharedPreferences.getBoolean("easyprivacy", true);
4427 fanboysAnnoyanceListEnabled = sharedPreferences.getBoolean("fanboys_annoyance_list", true);
4428 fanboysSocialBlockingListEnabled = sharedPreferences.getBoolean("fanboys_social_blocking_list", true);
4429 ultraPrivacyEnabled = sharedPreferences.getBoolean("ultraprivacy", true);
4430 blockAllThirdPartyRequests = sharedPreferences.getBoolean("block_all_third_party_requests", false);
4432 // Set `javaScriptEnabled` to be `true` if `night_mode` is `true`.
4434 javaScriptEnabled = true;
4437 // Apply the default settings.
4438 mainWebView.getSettings().setJavaScriptEnabled(javaScriptEnabled);
4439 cookieManager.setAcceptCookie(firstPartyCookiesEnabled);
4440 mainWebView.getSettings().setDomStorageEnabled(domStorageEnabled);
4441 mainWebView.getSettings().setTextZoom(Integer.valueOf(defaultFontSizeString));
4442 swipeRefreshLayout.setEnabled(defaultSwipeToRefresh);
4444 // Apply the form data setting if the API < 26.
4445 if (Build.VERSION.SDK_INT < 26) {
4446 mainWebView.getSettings().setSaveFormData(saveFormDataEnabled);
4449 // Reset the pinned SSL certificate information.
4450 domainSettingsDatabaseId = -1;
4451 pinnedDomainSslCertificate = false;
4452 pinnedDomainSslIssuedToCNameString = "";
4453 pinnedDomainSslIssuedToONameString = "";
4454 pinnedDomainSslIssuedToUNameString = "";
4455 pinnedDomainSslIssuedByCNameString = "";
4456 pinnedDomainSslIssuedByONameString = "";
4457 pinnedDomainSslIssuedByUNameString = "";
4458 pinnedDomainSslStartDate = null;
4459 pinnedDomainSslEndDate = null;
4461 // Set third-party cookies status if API >= 21.
4462 if (Build.VERSION.SDK_INT >= 21) {
4463 cookieManager.setAcceptThirdPartyCookies(mainWebView, thirdPartyCookiesEnabled);
4466 // 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.
4467 // <https://redmine.stoutner.com/issues/160>
4468 if (!urlIsLoading) {
4469 // Get the array position of the user agent name.
4470 int userAgentArrayPosition = userAgentNamesArray.getPosition(defaultUserAgentName);
4472 // Set the user agent.
4473 switch (userAgentArrayPosition) {
4474 case UNRECOGNIZED_USER_AGENT: // The default user agent name is not on the canonical list.
4475 // This is probably because it was set in an older version of Privacy Browser before the switch to persistent user agent names.
4476 mainWebView.getSettings().setUserAgentString(defaultUserAgentName);
4479 case SETTINGS_WEBVIEW_DEFAULT_USER_AGENT:
4480 // Set the user agent to `""`, which uses the default value.
4481 mainWebView.getSettings().setUserAgentString("");
4484 case SETTINGS_CUSTOM_USER_AGENT:
4485 // Set the custom user agent.
4486 mainWebView.getSettings().setUserAgentString(defaultCustomUserAgentString);
4490 // Get the user agent string from the user agent data array
4491 mainWebView.getSettings().setUserAgentString(userAgentDataArray[userAgentArrayPosition]);
4494 // Store the applied user agent string, which is used in the View Source activity.
4495 appliedUserAgentString = mainWebView.getSettings().getUserAgentString();
4497 // Update the user agent change tracker.
4498 userAgentChanged = !appliedUserAgentString.equals(initialUserAgent);
4501 // Set the loading of webpage images.
4502 mainWebView.getSettings().setLoadsImagesAutomatically(displayWebpageImages);
4504 // Set a transparent background on `urlTextBox`. We have to use the deprecated `.getDrawable()` until the minimum API >= 21.
4505 urlAppBarRelativeLayout.setBackgroundDrawable(getResources().getDrawable(R.color.transparent));
4508 // Close the domains database helper.
4509 domainsDatabaseHelper.close();
4511 // Update the privacy icons, but only if `mainMenu` has already been populated.
4512 if (mainMenu != null) {
4513 updatePrivacyIcons(true);
4517 // Reload the website if returning from the Domains activity.
4518 if (reloadWebsite) {
4519 mainWebView.reload();
4522 // Return the user agent changed status.
4523 return userAgentChanged;
4526 private void applyProxyThroughOrbot(boolean reloadWebsite) {
4527 // Get a handle for the shared preferences.
4528 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
4530 // Get the search preferences.
4531 String homepageString = sharedPreferences.getString("homepage", getString(R.string.homepage_default_value));
4532 String torHomepageString = sharedPreferences.getString("tor_homepage", getString(R.string.tor_homepage_default_value));
4533 String torSearchString = sharedPreferences.getString("tor_search", getString(R.string.tor_search_default_value));
4534 String torSearchCustomUrlString = sharedPreferences.getString("tor_search_custom_url", getString(R.string.tor_search_custom_url_default_value));
4535 String searchString = sharedPreferences.getString("search", getString(R.string.search_default_value));
4536 String searchCustomUrlString = sharedPreferences.getString("search_custom_url", getString(R.string.search_custom_url_default_value));
4538 // Set the homepage, search, and proxy options.
4539 if (proxyThroughOrbot) { // Set the Tor options.
4540 // Set `torHomepageString` as `homepage`.
4541 homepage = torHomepageString;
4543 // If formattedUrlString is null assign the homepage to it.
4544 if (formattedUrlString == null) {
4545 formattedUrlString = homepage;
4548 // Set the search URL.
4549 if (torSearchString.equals("Custom URL")) { // Get the custom URL string.
4550 searchURL = torSearchCustomUrlString;
4551 } else { // Use the string from the pre-built list.
4552 searchURL = torSearchString;
4555 // Set the proxy. `this` refers to the current activity where an `AlertDialog` might be displayed.
4556 OrbotProxyHelper.setProxy(getApplicationContext(), this, "localhost", "8118");
4558 // Set the `appBar` background to indicate proxying through Orbot is enabled. `this` refers to the context.
4560 appBar.setBackgroundDrawable(ContextCompat.getDrawable(this, R.color.dark_blue_30));
4562 appBar.setBackgroundDrawable(ContextCompat.getDrawable(this, R.color.blue_50));
4565 // Check to see if Orbot is ready.
4566 if (!orbotStatus.equals("ON")) { // Orbot is not ready.
4567 // Set `waitingForOrbot`.
4568 waitingForOrbot = true;
4570 // Disable the wide view port so that the waiting for Orbot text is displayed correctly.
4571 mainWebView.getSettings().setUseWideViewPort(false);
4573 // Load a waiting page. `null` specifies no encoding, which defaults to ASCII.
4574 mainWebView.loadData(waitingForOrbotHtmlString, "text/html", null);
4575 } else if (reloadWebsite) { // Orbot is ready and the website should be reloaded.
4576 // Reload the website.
4577 mainWebView.reload();
4579 } else { // Set the non-Tor options.
4580 // Set `homepageString` as `homepage`.
4581 homepage = homepageString;
4583 // If formattedUrlString is null assign the homepage to it.
4584 if (formattedUrlString == null) {
4585 formattedUrlString = homepage;
4588 // Set the search URL.
4589 if (searchString.equals("Custom URL")) { // Get the custom URL string.
4590 searchURL = searchCustomUrlString;
4591 } else { // Use the string from the pre-built list.
4592 searchURL = searchString;
4595 // Reset the proxy to default. The host is `""` and the port is `"0"`.
4596 OrbotProxyHelper.setProxy(getApplicationContext(), this, "", "0");
4598 // Set the default `appBar` background. `this` refers to the context.
4600 appBar.setBackgroundDrawable(ContextCompat.getDrawable(this, R.color.gray_900));
4602 appBar.setBackgroundDrawable(ContextCompat.getDrawable(this, R.color.gray_100));
4605 // Reset `waitingForOrbot.
4606 waitingForOrbot = false;
4608 // Reload the website if requested.
4609 if (reloadWebsite) {
4610 mainWebView.reload();
4615 private void updatePrivacyIcons(boolean runInvalidateOptionsMenu) {
4616 // Get handles for the menu items.
4617 MenuItem privacyMenuItem = mainMenu.findItem(R.id.toggle_javascript);
4618 MenuItem firstPartyCookiesMenuItem = mainMenu.findItem(R.id.toggle_first_party_cookies);
4619 MenuItem domStorageMenuItem = mainMenu.findItem(R.id.toggle_dom_storage);
4620 MenuItem refreshMenuItem = mainMenu.findItem(R.id.refresh);
4622 // Update the privacy icon.
4623 if (javaScriptEnabled) { // JavaScript is enabled.
4624 privacyMenuItem.setIcon(R.drawable.javascript_enabled);
4625 } else if (firstPartyCookiesEnabled) { // JavaScript is disabled but cookies are enabled.
4626 privacyMenuItem.setIcon(R.drawable.warning);
4627 } else { // All the dangerous features are disabled.
4628 privacyMenuItem.setIcon(R.drawable.privacy_mode);
4631 // Update the first-party cookies icon.
4632 if (firstPartyCookiesEnabled) { // First-party cookies are enabled.
4633 firstPartyCookiesMenuItem.setIcon(R.drawable.cookies_enabled);
4634 } else { // First-party cookies are disabled.
4636 firstPartyCookiesMenuItem.setIcon(R.drawable.cookies_disabled_dark);
4638 firstPartyCookiesMenuItem.setIcon(R.drawable.cookies_disabled_light);
4642 // Update the DOM storage icon.
4643 if (javaScriptEnabled && domStorageEnabled) { // Both JavaScript and DOM storage are enabled.
4644 domStorageMenuItem.setIcon(R.drawable.dom_storage_enabled);
4645 } else if (javaScriptEnabled) { // JavaScript is enabled but DOM storage is disabled.
4647 domStorageMenuItem.setIcon(R.drawable.dom_storage_disabled_dark);
4649 domStorageMenuItem.setIcon(R.drawable.dom_storage_disabled_light);
4651 } else { // JavaScript is disabled, so DOM storage is ghosted.
4653 domStorageMenuItem.setIcon(R.drawable.dom_storage_ghosted_dark);
4655 domStorageMenuItem.setIcon(R.drawable.dom_storage_ghosted_light);
4659 // Update the refresh icon.
4661 refreshMenuItem.setIcon(R.drawable.refresh_enabled_dark);
4663 refreshMenuItem.setIcon(R.drawable.refresh_enabled_light);
4666 // `invalidateOptionsMenu` calls `onPrepareOptionsMenu()` and redraws the icons in the `AppBar`.
4667 if (runInvalidateOptionsMenu) {
4668 invalidateOptionsMenu();
4672 private void openUrlWithExternalApp(String url) {
4673 // Create a download intent. Not specifying the action type will display the maximum number of options.
4674 Intent downloadIntent = new Intent();
4676 // Set the URI and the MIME type. Specifying `text/html` displays a good number of options.
4677 downloadIntent.setDataAndType(Uri.parse(url), "text/html");
4679 // Flag the intent to open in a new task.
4680 downloadIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
4682 // Show the chooser.
4683 startActivity(Intent.createChooser(downloadIntent, getString(R.string.open_with)));
4686 private void highlightUrlText() {
4687 // Get the URL string.
4688 String urlString = urlTextBox.getText().toString();
4690 // Highlight the URL according to the protocol.
4691 if (urlString.startsWith("file://")) { // This is a file URL.
4692 // De-emphasize only the protocol.
4693 urlTextBox.getText().setSpan(initialGrayColorSpan, 0, 7, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
4694 } else if (urlString.startsWith("content://")) {
4695 // De-emphasize only the protocol.
4696 urlTextBox.getText().setSpan(initialGrayColorSpan, 0, 10, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
4697 } else { // This is a web URL.
4698 // Get the index of the `/` immediately after the domain name.
4699 int endOfDomainName = urlString.indexOf("/", (urlString.indexOf("//") + 2));
4701 // Create a base URL string.
4704 // Get the base URL.
4705 if (endOfDomainName > 0) { // There is at least one character after the base URL.
4706 // Get the base URL.
4707 baseUrl = urlString.substring(0, endOfDomainName);
4708 } else { // There are no characters after the base URL.
4709 // Set the base URL to be the entire URL string.
4710 baseUrl = urlString;
4713 // Get the index of the last `.` in the domain.
4714 int lastDotIndex = baseUrl.lastIndexOf(".");
4716 // Get the index of the penultimate `.` in the domain.
4717 int penultimateDotIndex = baseUrl.lastIndexOf(".", lastDotIndex - 1);
4719 // Markup the beginning of the URL.
4720 if (urlString.startsWith("http://")) { // Highlight the protocol of connections that are not encrypted.
4721 urlTextBox.getText().setSpan(redColorSpan, 0, 7, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
4723 // De-emphasize subdomains.
4724 if (penultimateDotIndex > 0) { // There is more than one subdomain in the domain name.
4725 urlTextBox.getText().setSpan(initialGrayColorSpan, 7, penultimateDotIndex + 1, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
4727 } else if (urlString.startsWith("https://")) { // De-emphasize the protocol of connections that are encrypted.
4728 if (penultimateDotIndex > 0) { // There is more than one subdomain in the domain name.
4729 // De-emphasize the protocol and the additional subdomains.
4730 urlTextBox.getText().setSpan(initialGrayColorSpan, 0, penultimateDotIndex + 1, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
4731 } else { // There is only one subdomain in the domain name.
4732 // De-emphasize only the protocol.
4733 urlTextBox.getText().setSpan(initialGrayColorSpan, 0, 8, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
4737 // De-emphasize the text after the domain name.
4738 if (endOfDomainName > 0) {
4739 urlTextBox.getText().setSpan(finalGrayColorSpan, endOfDomainName, urlString.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
4744 private void loadBookmarksFolder() {
4745 // Update the bookmarks cursor with the contents of the bookmarks database for the current folder.
4746 bookmarksCursor = bookmarksDatabaseHelper.getAllBookmarksCursorByDisplayOrder(currentBookmarksFolder);
4748 // Populate the bookmarks cursor adapter. `this` specifies the `Context`. `false` disables `autoRequery`.
4749 bookmarksCursorAdapter = new CursorAdapter(this, bookmarksCursor, false) {
4751 public View newView(Context context, Cursor cursor, ViewGroup parent) {
4752 // Inflate the individual item layout. `false` does not attach it to the root.
4753 return getLayoutInflater().inflate(R.layout.bookmarks_drawer_item_linearlayout, parent, false);
4757 public void bindView(View view, Context context, Cursor cursor) {
4758 // Get handles for the views.
4759 ImageView bookmarkFavoriteIcon = view.findViewById(R.id.bookmark_favorite_icon);
4760 TextView bookmarkNameTextView = view.findViewById(R.id.bookmark_name);
4762 // Get the favorite icon byte array from the cursor.
4763 byte[] favoriteIconByteArray = cursor.getBlob(cursor.getColumnIndex(BookmarksDatabaseHelper.FAVORITE_ICON));
4765 // Convert the byte array to a `Bitmap` beginning at the first byte and ending at the last.
4766 Bitmap favoriteIconBitmap = BitmapFactory.decodeByteArray(favoriteIconByteArray, 0, favoriteIconByteArray.length);
4768 // Display the bitmap in `bookmarkFavoriteIcon`.
4769 bookmarkFavoriteIcon.setImageBitmap(favoriteIconBitmap);
4771 // Get the bookmark name from the cursor and display it in `bookmarkNameTextView`.
4772 String bookmarkNameString = cursor.getString(cursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME));
4773 bookmarkNameTextView.setText(bookmarkNameString);
4775 // Make the font bold for folders.
4776 if (cursor.getInt(cursor.getColumnIndex(BookmarksDatabaseHelper.IS_FOLDER)) == 1) {
4777 bookmarkNameTextView.setTypeface(Typeface.DEFAULT_BOLD);
4778 } else { // Reset the font to default for normal bookmarks.
4779 bookmarkNameTextView.setTypeface(Typeface.DEFAULT);
4784 // Populate the `ListView` with the adapter.
4785 bookmarksListView.setAdapter(bookmarksCursorAdapter);
4787 // Set the bookmarks drawer title.
4788 if (currentBookmarksFolder.isEmpty()) {
4789 bookmarksTitleTextView.setText(R.string.bookmarks);
4791 bookmarksTitleTextView.setText(currentBookmarksFolder);