2 * Copyright © 2015-2019 Soren Stoutner <soren@stoutner.com>.
4 * Download cookie code contributed 2017 Hendrik Knackstedt. Copyright assigned to Soren Stoutner <soren@stoutner.com>.
6 * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
8 * Privacy Browser is free software: you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation, either version 3 of the License, or
11 * (at your option) any later version.
13 * Privacy Browser is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with Privacy Browser. If not, see <http://www.gnu.org/licenses/>.
22 package com.stoutner.privacybrowser.activities;
24 import android.Manifest;
25 import android.annotation.SuppressLint;
26 import android.app.Activity;
27 import android.app.DialogFragment;
28 import android.app.DownloadManager;
29 import android.app.SearchManager;
30 import android.content.ActivityNotFoundException;
31 import android.content.BroadcastReceiver;
32 import android.content.ClipData;
33 import android.content.ClipboardManager;
34 import android.content.Context;
35 import android.content.Intent;
36 import android.content.IntentFilter;
37 import android.content.SharedPreferences;
38 import android.content.pm.PackageManager;
39 import android.content.res.Configuration;
40 import android.content.res.Resources;
41 import android.database.Cursor;
42 import android.graphics.Bitmap;
43 import android.graphics.BitmapFactory;
44 import android.graphics.Typeface;
45 import android.graphics.drawable.BitmapDrawable;
46 import android.graphics.drawable.Drawable;
47 import android.net.Uri;
48 import android.net.http.SslCertificate;
49 import android.net.http.SslError;
50 import android.os.AsyncTask;
51 import android.os.Build;
52 import android.os.Bundle;
53 import android.os.Environment;
54 import android.os.Handler;
55 import android.preference.PreferenceManager;
56 import android.print.PrintDocumentAdapter;
57 import android.print.PrintManager;
58 import android.support.annotation.NonNull;
59 import android.support.design.widget.CoordinatorLayout;
60 import android.support.design.widget.FloatingActionButton;
61 import android.support.design.widget.NavigationView;
62 import android.support.design.widget.Snackbar;
63 import android.support.v4.app.ActivityCompat;
64 import android.support.v4.app.FragmentManager;
65 import android.support.v4.content.ContextCompat;
66 // `ShortcutInfoCompat`, `ShortcutManagerCompat`, and `IconCompat` can be switched to the non-compat version once API >= 26.
67 import android.support.v4.content.pm.ShortcutInfoCompat;
68 import android.support.v4.content.pm.ShortcutManagerCompat;
69 import android.support.v4.graphics.drawable.IconCompat;
70 import android.support.v4.view.GravityCompat;
71 import android.support.v4.widget.DrawerLayout;
72 import android.support.v4.widget.SwipeRefreshLayout;
73 import android.support.v7.app.ActionBar;
74 import android.support.v7.app.ActionBarDrawerToggle;
75 import android.support.v7.app.AppCompatActivity;
76 import android.support.v7.app.AppCompatDialogFragment;
77 import android.support.v7.widget.Toolbar;
78 import android.text.Editable;
79 import android.text.Spanned;
80 import android.text.TextWatcher;
81 import android.text.style.ForegroundColorSpan;
82 import android.util.Patterns;
83 import android.view.ContextMenu;
84 import android.view.GestureDetector;
85 import android.view.KeyEvent;
86 import android.view.Menu;
87 import android.view.MenuItem;
88 import android.view.MotionEvent;
89 import android.view.View;
90 import android.view.ViewGroup;
91 import android.view.WindowManager;
92 import android.view.inputmethod.InputMethodManager;
93 import android.webkit.CookieManager;
94 import android.webkit.HttpAuthHandler;
95 import android.webkit.SslErrorHandler;
96 import android.webkit.ValueCallback;
97 import android.webkit.WebBackForwardList;
98 import android.webkit.WebChromeClient;
99 import android.webkit.WebResourceResponse;
100 import android.webkit.WebSettings;
101 import android.webkit.WebStorage;
102 import android.webkit.WebView;
103 import android.webkit.WebViewClient;
104 import android.webkit.WebViewDatabase;
105 import android.widget.ArrayAdapter;
106 import android.widget.CursorAdapter;
107 import android.widget.EditText;
108 import android.widget.FrameLayout;
109 import android.widget.ImageView;
110 import android.widget.LinearLayout;
111 import android.widget.ListView;
112 import android.widget.ProgressBar;
113 import android.widget.RadioButton;
114 import android.widget.RelativeLayout;
115 import android.widget.TextView;
117 import com.stoutner.privacybrowser.BuildConfig;
118 import com.stoutner.privacybrowser.R;
119 import com.stoutner.privacybrowser.dialogs.AdConsentDialog;
120 import com.stoutner.privacybrowser.dialogs.CreateBookmarkDialog;
121 import com.stoutner.privacybrowser.dialogs.CreateBookmarkFolderDialog;
122 import com.stoutner.privacybrowser.dialogs.CreateHomeScreenShortcutDialog;
123 import com.stoutner.privacybrowser.dialogs.DownloadImageDialog;
124 import com.stoutner.privacybrowser.dialogs.DownloadLocationPermissionDialog;
125 import com.stoutner.privacybrowser.dialogs.EditBookmarkDialog;
126 import com.stoutner.privacybrowser.dialogs.EditBookmarkFolderDialog;
127 import com.stoutner.privacybrowser.dialogs.HttpAuthenticationDialog;
128 import com.stoutner.privacybrowser.dialogs.PinnedMismatchDialog;
129 import com.stoutner.privacybrowser.dialogs.UrlHistoryDialog;
130 import com.stoutner.privacybrowser.dialogs.ViewSslCertificateDialog;
131 import com.stoutner.privacybrowser.helpers.AdHelper;
132 import com.stoutner.privacybrowser.helpers.BlockListHelper;
133 import com.stoutner.privacybrowser.helpers.BookmarksDatabaseHelper;
134 import com.stoutner.privacybrowser.helpers.DomainsDatabaseHelper;
135 import com.stoutner.privacybrowser.helpers.OrbotProxyHelper;
136 import com.stoutner.privacybrowser.dialogs.DownloadFileDialog;
137 import com.stoutner.privacybrowser.dialogs.SslCertificateErrorDialog;
139 import java.io.ByteArrayInputStream;
140 import java.io.ByteArrayOutputStream;
142 import java.io.IOException;
143 import java.io.UnsupportedEncodingException;
144 import java.lang.ref.WeakReference;
145 import java.net.InetAddress;
146 import java.net.MalformedURLException;
148 import java.net.URLDecoder;
149 import java.net.URLEncoder;
150 import java.net.UnknownHostException;
151 import java.util.ArrayList;
152 import java.util.Date;
153 import java.util.HashMap;
154 import java.util.HashSet;
155 import java.util.List;
156 import java.util.Map;
157 import java.util.Set;
159 // AppCompatActivity from android.support.v7.app.AppCompatActivity must be used to have access to the SupportActionBar until the minimum API is >= 21.
160 public class MainWebViewActivity extends AppCompatActivity implements CreateBookmarkDialog.CreateBookmarkListener, CreateBookmarkFolderDialog.CreateBookmarkFolderListener,
161 CreateHomeScreenShortcutDialog.CreateHomeScreenShortcutListener, DownloadFileDialog.DownloadFileListener, DownloadImageDialog.DownloadImageListener,
162 DownloadLocationPermissionDialog.DownloadLocationPermissionDialogListener, EditBookmarkDialog.EditBookmarkListener, EditBookmarkFolderDialog.EditBookmarkFolderListener,
163 HttpAuthenticationDialog.HttpAuthenticationListener, NavigationView.OnNavigationItemSelectedListener, PinnedMismatchDialog.PinnedMismatchListener,
164 SslCertificateErrorDialog.SslCertificateErrorListener, UrlHistoryDialog.UrlHistoryListener {
166 // `darkTheme` is public static so it can be accessed from everywhere.
167 public static boolean darkTheme;
169 // `allowScreenshots` is public static so it can be accessed from everywhere. It is also used in `onCreate()`.
170 public static boolean allowScreenshots;
172 // `favoriteIconBitmap` is public static so it can be accessed from `CreateHomeScreenShortcutDialog`, `BookmarksActivity`, `BookmarksDatabaseViewActivity`, `CreateBookmarkDialog`,
173 // `CreateBookmarkFolderDialog`, `EditBookmarkDialog`, `EditBookmarkFolderDialog`, `EditBookmarkDatabaseViewDialog`, and `ViewSslCertificateDialog`. It is also used in `onCreate()`,
174 // `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onCreateHomeScreenShortcutCreate()`, `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and `applyDomainSettings()`.
175 public static Bitmap favoriteIconBitmap;
177 // `favoriteIconDefaultBitmap` public static so it can be accessed from `PinnedMismatchDialog`. It is also used in `onCreate()` and `applyDomainSettings`.
178 public static Bitmap favoriteIconDefaultBitmap;
180 // `formattedUrlString` is public static so it can be accessed from `AddDomainDialog`, `BookmarksActivity`, `DomainSettingsFragment`, `CreateBookmarkDialog`, and `PinnedMismatchDialog`.
181 // It is also used in `onCreate()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onCreateHomeScreenShortcutCreate()`, `loadUrlFromTextBox()`, and `applyProxyThroughOrbot()`.
182 public static String formattedUrlString;
184 // `sslCertificate` is public static so it can be accessed from `DomainsActivity`, `DomainsListFragment`, `DomainSettingsFragment`, `PinnedMismatchDialog`, and `ViewSslCertificateDialog`.
185 // It is also used in `onCreate()` and `checkPinnedMismatch()`.
186 public static SslCertificate sslCertificate;
188 // `currentHostIpAddresses` is public static so it can be accessed from `DomainSettingsFragment` and `ViewSslCertificateDialog`.
189 // It is also used in `onCreate()` and `GetHostIpAddresses()`.
190 public static String currentHostIpAddresses;
192 // `orbotStatus` is public static so it can be accessed from `OrbotProxyHelper`. It is also used in `onCreate()`, `onResume()`, and `applyProxyThroughOrbot()`.
193 public static String orbotStatus;
195 // `webViewTitle` is public static so it can be accessed from `CreateBookmarkDialog` and `CreateHomeScreenShortcutDialog`. It is also used in `onCreate()`.
196 public static String webViewTitle;
198 // `appliedUserAgentString` is public static so it can be accessed from `ViewSourceActivity`. It is also used in `applyDomainSettings()`.
199 public static String appliedUserAgentString;
201 // `reloadOnRestart` is public static so it can be accessed from `SettingsFragment`. It is also used in `onRestart()`
202 public static boolean reloadOnRestart;
204 // `reloadUrlOnRestart` is public static so it can be accessed from `SettingsFragment` and `BookmarksActivity`. It is also used in `onRestart()`.
205 public static boolean loadUrlOnRestart;
207 // `restartFromBookmarksActivity` is public static so it can be accessed from `BookmarksActivity`. It is also used in `onRestart()`.
208 public static boolean restartFromBookmarksActivity;
210 // The block list versions are public static so they can be accessed from `AboutTabFragment`. They are also used in `onCreate()`.
211 public static String easyListVersion;
212 public static String easyPrivacyVersion;
213 public static String fanboysAnnoyanceVersion;
214 public static String fanboysSocialVersion;
215 public static String ultraPrivacyVersion;
217 // The request items are public static so they can be accessed by `BlockListHelper`, `RequestsArrayAdapter`, and `ViewRequestsDialog`. They are also used in `onCreate()` and `onPrepareOptionsMenu()`.
218 public static List<String[]> resourceRequests;
219 public static String[] whiteListResultStringArray;
220 private int blockedRequests;
221 private int easyListBlockedRequests;
222 private int easyPrivacyBlockedRequests;
223 private int fanboysAnnoyanceListBlockedRequests;
224 private int fanboysSocialBlockingListBlockedRequests;
225 private int ultraPrivacyBlockedRequests;
226 private int thirdPartyBlockedRequests;
228 public final static int REQUEST_DISPOSITION = 0;
229 public final static int REQUEST_URL = 1;
230 public final static int REQUEST_BLOCKLIST = 2;
231 public final static int REQUEST_SUBLIST = 3;
232 public final static int REQUEST_BLOCKLIST_ENTRIES = 4;
233 public final static int REQUEST_BLOCKLIST_ORIGINAL_ENTRY = 5;
235 public final static int REQUEST_DEFAULT = 0;
236 public final static int REQUEST_ALLOWED = 1;
237 public final static int REQUEST_THIRD_PARTY = 2;
238 public final static int REQUEST_BLOCKED = 3;
240 public final static int MAIN_WHITELIST = 1;
241 public final static int FINAL_WHITELIST = 2;
242 public final static int DOMAIN_WHITELIST = 3;
243 public final static int DOMAIN_INITIAL_WHITELIST = 4;
244 public final static int DOMAIN_FINAL_WHITELIST = 5;
245 public final static int THIRD_PARTY_WHITELIST = 6;
246 public final static int THIRD_PARTY_DOMAIN_WHITELIST = 7;
247 public final static int THIRD_PARTY_DOMAIN_INITIAL_WHITELIST = 8;
249 public final static int MAIN_BLACKLIST = 9;
250 public final static int INITIAL_BLACKLIST = 10;
251 public final static int FINAL_BLACKLIST = 11;
252 public final static int DOMAIN_BLACKLIST = 12;
253 public final static int DOMAIN_INITIAL_BLACKLIST = 13;
254 public final static int DOMAIN_FINAL_BLACKLIST = 14;
255 public final static int DOMAIN_REGULAR_EXPRESSION_BLACKLIST = 15;
256 public final static int THIRD_PARTY_BLACKLIST = 16;
257 public final static int THIRD_PARTY_INITIAL_BLACKLIST = 17;
258 public final static int THIRD_PARTY_DOMAIN_BLACKLIST = 18;
259 public final static int THIRD_PARTY_DOMAIN_INITIAL_BLACKLIST = 19;
260 public final static int THIRD_PARTY_REGULAR_EXPRESSION_BLACKLIST = 20;
261 public final static int THIRD_PARTY_DOMAIN_REGULAR_EXPRESSION_BLACKLIST = 21;
262 public final static int REGULAR_EXPRESSION_BLACKLIST = 22;
264 // `blockAllThirdPartyRequests` is public static so it can be accessed from `RequestsActivity`.
265 // It is also used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, and `applyAppSettings()`
266 public static boolean blockAllThirdPartyRequests;
268 // `currentBookmarksFolder` is public static so it can be accessed from `BookmarksActivity`. It is also used in `onCreate()`, `onBackPressed()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`,
269 // `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and `loadBookmarksFolder()`.
270 public static String currentBookmarksFolder;
272 // `domainSettingsDatabaseId` is public static so it can be accessed from `PinnedMismatchDialog`. It is also used in `onCreate()`, `onOptionsItemSelected()`, and `applyDomainSettings()`.
273 public static int domainSettingsDatabaseId;
275 // The pinned variables are public static so they can be accessed from `PinnedMismatchDialog`. They are also used in `onCreate()`, `applyDomainSettings()`, and `checkPinnedMismatch()`.
276 public static String pinnedSslIssuedToCName;
277 public static String pinnedSslIssuedToOName;
278 public static String pinnedSslIssuedToUName;
279 public static String pinnedSslIssuedByCName;
280 public static String pinnedSslIssuedByOName;
281 public static String pinnedSslIssuedByUName;
282 public static Date pinnedSslStartDate;
283 public static Date pinnedSslEndDate;
284 public static String pinnedHostIpAddresses;
286 // The user agent constants are public static so they can be accessed from `SettingsFragment`, `DomainsActivity`, and `DomainSettingsFragment`.
287 public final static int UNRECOGNIZED_USER_AGENT = -1;
288 public final static int SETTINGS_WEBVIEW_DEFAULT_USER_AGENT = 1;
289 public final static int SETTINGS_CUSTOM_USER_AGENT = 12;
290 public final static int DOMAINS_SYSTEM_DEFAULT_USER_AGENT = 0;
291 public final static int DOMAINS_WEBVIEW_DEFAULT_USER_AGENT = 2;
292 public final static int DOMAINS_CUSTOM_USER_AGENT = 13;
296 // `urlIsLoading` is used in `onCreate()`, `onCreateOptionsMenu()`, `loadUrl()`, `applyDomainSettings()`, and `GetHostIpAddresses`.
297 private static boolean urlIsLoading;
299 // `gettingIpAddresses` is used in `onCreate() and `GetHostIpAddresses`.
300 private static boolean gettingIpAddresses;
302 // `pinnedDomainSslCertificate` is used in `onCreate()`, `applyDomainSettings()`, and `checkPinnedMismatch()`.
303 private static boolean pinnedSslCertificate;
305 // `pinnedIpAddress` is used in `applyDomainSettings()` and `checkPinnedMismatch()`.
306 private static boolean pinnedIpAddresses;
308 // `ignorePinnedDomainInformation` is used in `onSslMismatchProceed()`, `applyDomainSettings()`, and `checkPinnedMismatch()`.
309 private static boolean ignorePinnedDomainInformation;
311 // `supportFragmentManager` is used in `onCreate()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onCreateContextMenu()`, `onRequestPermissionResult()`, `viewSslCertificate()`,
312 // `applyAppSettings()`, and `checkPinnedMismatch()`.
313 private static FragmentManager supportFragmentManager;
316 // `appBar` is used in `onCreate()`, `onOptionsItemSelected()`, `closeFindOnPage()`, `applyAppSettings()`, and `applyProxyThroughOrbot()`.
317 private ActionBar appBar;
319 // `navigatingHistory` is used in `onCreate()`, `onNavigationItemSelected()`, `onSslMismatchBack()`, and `applyDomainSettings()`.
320 private boolean navigatingHistory;
322 // `drawerLayout` is used in `onCreate()`, `onNewIntent()`, `onBackPressed()`, and `onRestart()`.
323 private DrawerLayout drawerLayout;
325 // `rootCoordinatorLayout` is used in `onCreate()` and `applyAppSettings()`.
326 private CoordinatorLayout rootCoordinatorLayout;
328 // `mainWebView` is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onRestart()`, `onCreateContextMenu()`, `findPreviousOnPage()`,
329 // `findNextOnPage()`, `closeFindOnPage()`, `loadUrlFromTextBox()`, `onSslMismatchBack()`, and `applyProxyThroughOrbot()`.
330 private WebView mainWebView;
332 // `fullScreenVideoFrameLayout` is used in `onCreate()` and `onConfigurationChanged()`.
333 private FrameLayout fullScreenVideoFrameLayout;
335 // `swipeRefreshLayout` is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, and `onRestart()`.
336 private SwipeRefreshLayout swipeRefreshLayout;
338 // `urlAppBarRelativeLayout` is used in `onCreate()` and `applyDomainSettings()`.
339 private RelativeLayout urlAppBarRelativeLayout;
341 // `favoriteIconImageView` is used in `onCreate()` and `applyDomainSettings()`
342 private ImageView favoriteIconImageView;
344 // `cookieManager` is used in `onCreate()`, `onOptionsItemSelected()`, and `onNavigationItemSelected()`, `loadUrlFromTextBox()`, `onDownloadImage()`, `onDownloadFile()`, and `onRestart()`.
345 private CookieManager cookieManager;
347 // `customHeader` is used in `onCreate()`, `onOptionsItemSelected()`, `onCreateContextMenu()`, and `loadUrl()`.
348 private final Map<String, String> customHeaders = new HashMap<>();
350 // `javaScriptEnabled` is also used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, `applyDomainSettings()`, and `updatePrivacyIcons()`.
351 private boolean javaScriptEnabled;
353 // `firstPartyCookiesEnabled` is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, `onDownloadImage()`, `onDownloadFile()`, and `applyDomainSettings()`.
354 private boolean firstPartyCookiesEnabled;
356 // `thirdPartyCookiesEnabled` used in `onCreate()`, `onPrepareOptionsMenu()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, and `applyDomainSettings()`.
357 private boolean thirdPartyCookiesEnabled;
359 // `domStorageEnabled` is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, and `applyDomainSettings()`.
360 private boolean domStorageEnabled;
362 // `saveFormDataEnabled` is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, and `applyDomainSettings()`. It can be removed once the minimum API >= 26.
363 private boolean saveFormDataEnabled;
365 // `nightMode` is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, and `applyDomainSettings()`.
366 private boolean nightMode;
368 // 'homepage' is used in `onCreate()`, `onNavigationItemSelected()`, and `applyProxyThroughOrbot()`.
369 private String homepage;
371 // `searchURL` is used in `loadURLFromTextBox()` and `applyProxyThroughOrbot()`.
372 private String searchURL;
374 // `mainMenu` is used in `onCreateOptionsMenu()` and `updatePrivacyIcons()`.
375 private Menu mainMenu;
377 // `refreshMenuItem` is used in `onCreate()` and `onCreateOptionsMenu()`.
378 private MenuItem refreshMenuItem;
380 // The blocklist menu items are used in `onCreate()`, `onCreateOptionsMenu()`, and `onPrepareOptionsMenu()`.
381 private MenuItem blocklistsMenuItem;
382 private MenuItem easyListMenuItem;
383 private MenuItem easyPrivacyMenuItem;
384 private MenuItem fanboysAnnoyanceListMenuItem;
385 private MenuItem fanboysSocialBlockingListMenuItem;
386 private MenuItem ultraPrivacyMenuItem;
387 private MenuItem blockAllThirdPartyRequestsMenuItem;
389 // The blocklist variables are used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, and `applyAppSettings()`.
390 private boolean easyListEnabled;
391 private boolean easyPrivacyEnabled;
392 private boolean fanboysAnnoyanceListEnabled;
393 private boolean fanboysSocialBlockingListEnabled;
394 private boolean ultraPrivacyEnabled;
396 // `webViewDefaultUserAgent` is used in `onCreate()` and `onPrepareOptionsMenu()`.
397 private String webViewDefaultUserAgent;
399 // `defaultCustomUserAgentString` is used in `onPrepareOptionsMenu()` and `applyDomainSettings()`.
400 private String defaultCustomUserAgentString;
402 // `privacyBrowserRuntime` is used in `onCreate()`, `onOptionsItemSelected()`, and `applyAppSettings()`.
403 private Runtime privacyBrowserRuntime;
405 // `proxyThroughOrbot` is used in `onRestart()`, `onOptionsItemSelected()`, `applyAppSettings()`, and `applyProxyThroughOrbot()`.
406 private boolean proxyThroughOrbot;
408 // `incognitoModeEnabled` is used in `onCreate()` and `applyAppSettings()`.
409 private boolean incognitoModeEnabled;
411 // `fullScreenBrowsingModeEnabled` is used in `onCreate()` and `applyAppSettings()`.
412 private boolean fullScreenBrowsingModeEnabled;
414 // `inFullScreenBrowsingMode` is used in `onCreate()`, `onConfigurationChanged()`, and `applyAppSettings()`.
415 private boolean inFullScreenBrowsingMode;
417 // `hideSystemBarsOnFullscreen` is used in `onCreate()` and `applyAppSettings()`.
418 private boolean hideSystemBarsOnFullscreen;
420 // `translucentNavigationBarOnFullscreen` is used in `onCreate()` and `applyAppSettings()`.
421 private boolean translucentNavigationBarOnFullscreen;
423 // `reapplyDomainSettingsOnRestart` is used in `onCreate()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onRestart()`, and `onAddDomain()`, .
424 private boolean reapplyDomainSettingsOnRestart;
426 // `reapplyAppSettingsOnRestart` is used in `onNavigationItemSelected()` and `onRestart()`.
427 private boolean reapplyAppSettingsOnRestart;
429 // `displayingFullScreenVideo` is used in `onCreate()` and `onResume()`.
430 private boolean displayingFullScreenVideo;
432 // `downloadWithExternalApp` is used in `onCreate()`, `onCreateContextMenu()`, and `applyDomainSettings()`.
433 private boolean downloadWithExternalApp;
435 // `currentDomainName` is used in `onCreate()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onAddDomain()`, and `applyDomainSettings()`.
436 private String currentDomainName;
438 // `orbotStatusBroadcastReceiver` is used in `onCreate()` and `onDestroy()`.
439 private BroadcastReceiver orbotStatusBroadcastReceiver;
441 // `waitingForOrbot` is used in `onCreate()`, `onResume()`, and `applyProxyThroughOrbot()`.
442 private boolean waitingForOrbot;
444 // `domainSettingsApplied` is used in `prepareOptionsMenu()` and `applyDomainSettings()`.
445 private boolean domainSettingsApplied;
447 // `domainSettingsJavaScriptEnabled` is used in `onOptionsItemSelected()` and `applyDomainSettings()`.
448 private Boolean domainSettingsJavaScriptEnabled;
450 // `waitingForOrbotHtmlString` is used in `onCreate()` and `applyProxyThroughOrbot()`.
451 private String waitingForOrbotHtmlString;
453 // `privateDataDirectoryString` is used in `onCreate()`, `onOptionsItemSelected()`, and `onNavigationItemSelected()`.
454 private String privateDataDirectoryString;
456 // `findOnPageLinearLayout` is used in `onCreate()`, `onOptionsItemSelected()`, and `closeFindOnPage()`.
457 private LinearLayout findOnPageLinearLayout;
459 // `findOnPageEditText` is used in `onCreate()`, `onOptionsItemSelected()`, and `closeFindOnPage()`.
460 private EditText findOnPageEditText;
462 // `displayAdditionalAppBarIcons` is used in `onCreate()` and `onCreateOptionsMenu()`.
463 private boolean displayAdditionalAppBarIcons;
465 // `drawerToggle` is used in `onCreate()`, `onPostCreate()`, `onConfigurationChanged()`, `onNewIntent()`, and `onNavigationItemSelected()`.
466 private ActionBarDrawerToggle drawerToggle;
468 // `supportAppBar` is used in `onCreate()`, `onOptionsItemSelected()`, and `closeFindOnPage()`.
469 private Toolbar supportAppBar;
471 // `urlTextBox` is used in `onCreate()`, `onOptionsItemSelected()`, `loadUrlFromTextBox()`, `loadUrl()`, and `highlightUrlText()`.
472 private EditText urlTextBox;
474 // The color spans are used in `onCreate()` and `highlightUrlText()`.
475 private ForegroundColorSpan redColorSpan;
476 private ForegroundColorSpan initialGrayColorSpan;
477 private ForegroundColorSpan finalGrayColorSpan;
479 // The drawer header padding variables are used in `onCreate()` and `onConfigurationChanged()`.
480 private int drawerHeaderPaddingLeftAndRight;
481 private int drawerHeaderPaddingTop;
482 private int drawerHeaderPaddingBottom;
484 // `sslErrorHandler` is used in `onCreate()`, `onSslErrorCancel()`, and `onSslErrorProceed`.
485 private SslErrorHandler sslErrorHandler;
487 // `httpAuthHandler` is used in `onCreate()`, `onHttpAuthenticationCancel()`, and `onHttpAuthenticationProceed()`.
488 private static HttpAuthHandler httpAuthHandler;
490 // `inputMethodManager` is used in `onOptionsItemSelected()`, `loadUrlFromTextBox()`, and `closeFindOnPage()`.
491 private InputMethodManager inputMethodManager;
493 // `mainWebViewRelativeLayout` is used in `onCreate()` and `onNavigationItemSelected()`.
494 private RelativeLayout mainWebViewRelativeLayout;
496 // `bookmarksDatabaseHelper` is used in `onCreate()`, `onDestroy`, `onOptionsItemSelected()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`,
497 // and `loadBookmarksFolder()`.
498 private BookmarksDatabaseHelper bookmarksDatabaseHelper;
500 // `bookmarksListView` is used in `onCreate()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, and `loadBookmarksFolder()`.
501 private ListView bookmarksListView;
503 // `bookmarksTitleTextView` is used in `onCreate()` and `loadBookmarksFolder()`.
504 private TextView bookmarksTitleTextView;
506 // `bookmarksCursor` is used in `onDestroy()`, `onOptionsItemSelected()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and `loadBookmarksFolder()`.
507 private Cursor bookmarksCursor;
509 // `bookmarksCursorAdapter` is used in `onCreateBookmark()`, `onCreateBookmarkFolder()` `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and `loadBookmarksFolder()`.
510 private CursorAdapter bookmarksCursorAdapter;
512 // `oldFolderNameString` is used in `onCreate()` and `onSaveEditBookmarkFolder()`.
513 private String oldFolderNameString;
515 // `fileChooserCallback` is used in `onCreate()` and `onActivityResult()`.
516 private ValueCallback<Uri[]> fileChooserCallback;
518 // The download strings are used in `onCreate()` and `onRequestPermissionResult()`.
519 private String downloadUrl;
520 private String downloadContentDisposition;
521 private long downloadContentLength;
523 // `downloadImageUrl` is used in `onCreateContextMenu()` and `onRequestPermissionResult()`.
524 private String downloadImageUrl;
526 // The user agent variables are used in `onCreate()` and `applyDomainSettings()`.
527 private ArrayAdapter<CharSequence> userAgentNamesArray;
528 private String[] userAgentDataArray;
530 // The request codes are used in `onCreate()`, `onCreateContextMenu()`, `onCloseDownloadLocationPermissionDialog()`, and `onRequestPermissionResult()`.
531 private final int DOWNLOAD_FILE_REQUEST_CODE = 1;
532 private final int DOWNLOAD_IMAGE_REQUEST_CODE = 2;
535 // 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.
536 // Also, remove the warning about needing to override `performClick()` when using an `OnTouchListener` with `WebView`.
537 @SuppressLint({"SetJavaScriptEnabled", "ClickableViewAccessibility"})
538 // Remove Android Studio's warning about deprecations. The deprecated `getColor()` must be used until API >= 23.
539 @SuppressWarnings("deprecation")
540 protected void onCreate(Bundle savedInstanceState) {
541 // Get a handle for the shared preferences.
542 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
544 // Get the theme and screenshot preferences.
545 darkTheme = sharedPreferences.getBoolean("dark_theme", false);
546 allowScreenshots = sharedPreferences.getBoolean("allow_screenshots", false);
548 // Disable screenshots if not allowed.
549 if (!allowScreenshots) {
550 getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE);
553 // Set the activity theme.
555 setTheme(R.style.PrivacyBrowserDark);
557 setTheme(R.style.PrivacyBrowserLight);
560 // Run the default commands.
561 super.onCreate(savedInstanceState);
563 // Set the content view.
564 setContentView(R.layout.main_drawerlayout);
566 // Get a handle for the resources and the support fragment manager.
567 Resources resources = getResources();
568 supportFragmentManager = getSupportFragmentManager();
570 // Get a handle for `inputMethodManager`.
571 inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
573 // `SupportActionBar` from `android.support.v7.app.ActionBar` must be used until the minimum API is >= 21.
574 supportAppBar = findViewById(R.id.app_bar);
575 setSupportActionBar(supportAppBar);
576 appBar = getSupportActionBar();
578 // This is needed to get rid of the Android Studio warning that `appBar` might be null.
579 assert appBar != null;
581 // Add the custom `url_app_bar` layout, which shows the favorite icon and the URL text bar.
582 appBar.setCustomView(R.layout.url_app_bar);
583 appBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM);
585 // Initialize the foreground color spans for highlighting the URLs. We have to use the deprecated `getColor()` until API >= 23.
586 redColorSpan = new ForegroundColorSpan(resources.getColor(R.color.red_a700));
587 initialGrayColorSpan = new ForegroundColorSpan(resources.getColor(R.color.gray_500));
588 finalGrayColorSpan = new ForegroundColorSpan(resources.getColor(R.color.gray_500));
590 // Get a handle for `urlTextBox`.
591 urlTextBox = findViewById(R.id.url_edittext);
593 // Remove the formatting from `urlTextBar` when the user is editing the text.
594 urlTextBox.setOnFocusChangeListener((View v, boolean hasFocus) -> {
595 if (hasFocus) { // The user is editing the URL text box.
596 // Remove the highlighting.
597 urlTextBox.getText().removeSpan(redColorSpan);
598 urlTextBox.getText().removeSpan(initialGrayColorSpan);
599 urlTextBox.getText().removeSpan(finalGrayColorSpan);
600 } else { // The user has stopped editing the URL text box.
601 // Move to the beginning of the string.
602 urlTextBox.setSelection(0);
604 // Reapply the highlighting.
609 // Set the go button on the keyboard to load the URL in `urlTextBox`.
610 urlTextBox.setOnKeyListener((View v, int keyCode, KeyEvent event) -> {
611 // If the event is a key-down event on the `enter` button, load the URL.
612 if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) {
613 // Load the URL into the mainWebView and consume the event.
614 loadUrlFromTextBox();
616 // If the enter key was pressed, consume the event.
619 // If any other key was pressed, do not consume the event.
624 // Set `waitingForOrbotHTMLString`.
625 waitingForOrbotHtmlString = "<html><body><br/><center><h1>" + getString(R.string.waiting_for_orbot) + "</h1></center></body></html>";
627 // Initialize `currentDomainName`, `orbotStatus`, and `waitingForOrbot`.
628 currentDomainName = "";
629 orbotStatus = "unknown";
630 waitingForOrbot = false;
632 // Create an Orbot status `BroadcastReceiver`.
633 orbotStatusBroadcastReceiver = new BroadcastReceiver() {
635 public void onReceive(Context context, Intent intent) {
636 // Store the content of the status message in `orbotStatus`.
637 orbotStatus = intent.getStringExtra("org.torproject.android.intent.extra.STATUS");
639 // If Privacy Browser is waiting on Orbot, load the website now that Orbot is connected.
640 if (orbotStatus.equals("ON") && waitingForOrbot) {
641 // Reset `waitingForOrbot`.
642 waitingForOrbot = false;
644 // Load `formattedUrlString
645 loadUrl(formattedUrlString);
650 // Register `orbotStatusBroadcastReceiver` on `this` context.
651 this.registerReceiver(orbotStatusBroadcastReceiver, new IntentFilter("org.torproject.android.intent.action.STATUS"));
653 // Get handles for views that need to be accessed.
654 drawerLayout = findViewById(R.id.drawerlayout);
655 rootCoordinatorLayout = findViewById(R.id.root_coordinatorlayout);
656 bookmarksListView = findViewById(R.id.bookmarks_drawer_listview);
657 bookmarksTitleTextView = findViewById(R.id.bookmarks_title_textview);
658 FloatingActionButton launchBookmarksActivityFab = findViewById(R.id.launch_bookmarks_activity_fab);
659 FloatingActionButton createBookmarkFolderFab = findViewById(R.id.create_bookmark_folder_fab);
660 FloatingActionButton createBookmarkFab = findViewById(R.id.create_bookmark_fab);
661 mainWebViewRelativeLayout = findViewById(R.id.main_webview_relativelayout);
662 mainWebView = findViewById(R.id.main_webview);
663 findOnPageLinearLayout = findViewById(R.id.find_on_page_linearlayout);
664 findOnPageEditText = findViewById(R.id.find_on_page_edittext);
665 fullScreenVideoFrameLayout = findViewById(R.id.full_screen_video_framelayout);
666 urlAppBarRelativeLayout = findViewById(R.id.url_app_bar_relativelayout);
667 favoriteIconImageView = findViewById(R.id.favorite_icon);
669 // 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.
671 launchBookmarksActivityFab.setImageDrawable(resources.getDrawable(R.drawable.bookmarks_dark));
672 createBookmarkFolderFab.setImageDrawable(resources.getDrawable(R.drawable.create_folder_dark));
673 createBookmarkFab.setImageDrawable(resources.getDrawable(R.drawable.create_bookmark_dark));
674 bookmarksListView.setBackgroundColor(resources.getColor(R.color.gray_850));
676 launchBookmarksActivityFab.setImageDrawable(resources.getDrawable(R.drawable.bookmarks_light));
677 createBookmarkFolderFab.setImageDrawable(resources.getDrawable(R.drawable.create_folder_light));
678 createBookmarkFab.setImageDrawable(resources.getDrawable(R.drawable.create_bookmark_light));
679 bookmarksListView.setBackgroundColor(resources.getColor(R.color.white));
682 // Set the launch bookmarks activity FAB to launch the bookmarks activity.
683 launchBookmarksActivityFab.setOnClickListener(v -> {
684 // Create an intent to launch the bookmarks activity.
685 Intent bookmarksIntent = new Intent(getApplicationContext(), BookmarksActivity.class);
687 // Include the current folder with the `Intent`.
688 bookmarksIntent.putExtra("Current Folder", currentBookmarksFolder);
691 startActivity(bookmarksIntent);
694 // Set the create new bookmark folder FAB to display an alert dialog.
695 createBookmarkFolderFab.setOnClickListener(v -> {
696 // Show the `CreateBookmarkFolderDialog` `AlertDialog` and name the instance `@string/create_folder`.
697 AppCompatDialogFragment createBookmarkFolderDialog = new CreateBookmarkFolderDialog();
698 createBookmarkFolderDialog.show(supportFragmentManager, resources.getString(R.string.create_folder));
701 // Set the create new bookmark FAB to display an alert dialog.
702 createBookmarkFab.setOnClickListener(view -> {
703 // Show the `CreateBookmarkDialog` `AlertDialog` and name the instance `@string/create_bookmark`.
704 AppCompatDialogFragment createBookmarkDialog = new CreateBookmarkDialog();
705 createBookmarkDialog.show(supportFragmentManager, resources.getString(R.string.create_bookmark));
708 // Create a double-tap listener to toggle full-screen mode.
709 final GestureDetector gestureDetector = new GestureDetector(this, new GestureDetector.SimpleOnGestureListener() {
710 // Override `onDoubleTap()`. All other events are handled using the default settings.
712 public boolean onDoubleTap(MotionEvent event) {
713 if (fullScreenBrowsingModeEnabled) { // Only process the double-tap if full screen browsing mode is enabled.
714 // Toggle `inFullScreenBrowsingMode`.
715 inFullScreenBrowsingMode = !inFullScreenBrowsingMode;
717 if (inFullScreenBrowsingMode) { // Switch to full screen mode.
718 // Hide the `appBar`.
721 // Hide the banner ad in the free flavor.
722 if (BuildConfig.FLAVOR.contentEquals("free")) {
723 // The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations.
724 AdHelper.hideAd(findViewById(R.id.adview));
727 // Modify the system bars.
728 if (hideSystemBarsOnFullscreen) { // Hide everything.
729 // Remove the translucent overlays.
730 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
732 // Remove the translucent status bar overlay on the `Drawer Layout`, which is special and needs its own command.
733 drawerLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
735 /* SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
736 * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
737 * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
739 rootCoordinatorLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
741 // Set `rootCoordinatorLayout` to fill the whole screen.
742 rootCoordinatorLayout.setFitsSystemWindows(false);
743 } else { // Hide everything except the status and navigation bars.
744 // Set `rootCoordinatorLayout` to fit under the status and navigation bars.
745 rootCoordinatorLayout.setFitsSystemWindows(false);
747 // 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.
748 if (translucentNavigationBarOnFullscreen) {
749 // Set the navigation bar to be translucent.
750 getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
753 } else { // Switch to normal viewing mode.
754 // Show the `appBar`.
757 // Show the `BannerAd` in the free flavor.
758 if (BuildConfig.FLAVOR.contentEquals("free")) {
759 // Reload the ad. The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations.
760 AdHelper.loadAd(findViewById(R.id.adview), getApplicationContext(), getString(R.string.ad_unit_id));
763 // Remove the translucent navigation bar flag if it is set.
764 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
766 // Add the translucent status flag if it is unset. This also resets `drawerLayout's` `View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN`.
767 getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
769 // Remove any `SYSTEM_UI` flags from `rootCoordinatorLayout`.
770 rootCoordinatorLayout.setSystemUiVisibility(0);
772 // Constrain `rootCoordinatorLayout` inside the status and navigation bars.
773 rootCoordinatorLayout.setFitsSystemWindows(true);
776 // Consume the double-tap.
778 } else { // Do not consume the double-tap because full screen browsing mode is disabled.
784 // Pass all touch events on `mainWebView` through `gestureDetector` to check for double-taps.
785 mainWebView.setOnTouchListener((View v, MotionEvent event) -> {
786 // Call `performClick()` on the view, which is required for accessibility.
789 // Send the `event` to `gestureDetector`.
790 return gestureDetector.onTouchEvent(event);
793 // Update `findOnPageCountTextView`.
794 mainWebView.setFindListener(new WebView.FindListener() {
795 // Get a handle for `findOnPageCountTextView`.
796 final TextView findOnPageCountTextView = findViewById(R.id.find_on_page_count_textview);
799 public void onFindResultReceived(int activeMatchOrdinal, int numberOfMatches, boolean isDoneCounting) {
800 if ((isDoneCounting) && (numberOfMatches == 0)) { // There are no matches.
801 // Set `findOnPageCountTextView` to `0/0`.
802 findOnPageCountTextView.setText(R.string.zero_of_zero);
803 } else if (isDoneCounting) { // There are matches.
804 // `activeMatchOrdinal` is zero-based.
805 int activeMatch = activeMatchOrdinal + 1;
807 // Build the match string.
808 String matchString = activeMatch + "/" + numberOfMatches;
810 // Set `findOnPageCountTextView`.
811 findOnPageCountTextView.setText(matchString);
816 // Search for the string on the page whenever a character changes in the `findOnPageEditText`.
817 findOnPageEditText.addTextChangedListener(new TextWatcher() {
819 public void beforeTextChanged(CharSequence s, int start, int count, int after) {
824 public void onTextChanged(CharSequence s, int start, int before, int count) {
829 public void afterTextChanged(Editable s) {
830 // Search for the text in `mainWebView`.
831 mainWebView.findAllAsync(findOnPageEditText.getText().toString());
835 // Set the `check mark` button for the `findOnPageEditText` keyboard to close the soft keyboard.
836 findOnPageEditText.setOnKeyListener((v, keyCode, event) -> {
837 if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) { // The `enter` key was pressed.
838 // Hide the soft keyboard. `0` indicates no additional flags.
839 inputMethodManager.hideSoftInputFromWindow(mainWebView.getWindowToken(), 0);
841 // Consume the event.
843 } else { // A different key was pressed.
844 // Do not consume the event.
849 // Implement swipe to refresh.
850 swipeRefreshLayout = findViewById(R.id.swiperefreshlayout);
851 swipeRefreshLayout.setOnRefreshListener(() -> mainWebView.reload());
853 // Set the swipe to refresh color according to the theme.
855 swipeRefreshLayout.setColorSchemeResources(R.color.blue_600);
856 swipeRefreshLayout.setProgressBackgroundColorSchemeResource(R.color.gray_850);
858 swipeRefreshLayout.setColorSchemeResources(R.color.blue_700);
861 // `DrawerTitle` identifies the `DrawerLayouts` in accessibility mode.
862 drawerLayout.setDrawerTitle(GravityCompat.START, getString(R.string.navigation_drawer));
863 drawerLayout.setDrawerTitle(GravityCompat.END, getString(R.string.bookmarks));
865 // Listen for touches on the navigation menu.
866 final NavigationView navigationView = findViewById(R.id.navigationview);
867 navigationView.setNavigationItemSelectedListener(this);
869 // 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.
870 final Menu navigationMenu = navigationView.getMenu();
871 final MenuItem navigationBackMenuItem = navigationMenu.getItem(1);
872 final MenuItem navigationForwardMenuItem = navigationMenu.getItem(2);
873 final MenuItem navigationHistoryMenuItem = navigationMenu.getItem(3);
874 final MenuItem navigationRequestsMenuItem = navigationMenu.getItem(4);
876 // Initialize the bookmarks database helper. The `0` specifies a database version, but that is ignored and set instead using a constant in `BookmarksDatabaseHelper`.
877 bookmarksDatabaseHelper = new BookmarksDatabaseHelper(this, null, null, 0);
879 // Initialize `currentBookmarksFolder`. `""` is the home folder in the database.
880 currentBookmarksFolder = "";
882 // Load the home folder, which is `""` in the database.
883 loadBookmarksFolder();
885 bookmarksListView.setOnItemClickListener((parent, view, position, id) -> {
886 // Convert the id from long to int to match the format of the bookmarks database.
887 int databaseID = (int) id;
889 // Get the bookmark cursor for this ID and move it to the first row.
890 Cursor bookmarkCursor = bookmarksDatabaseHelper.getBookmark(databaseID);
891 bookmarkCursor.moveToFirst();
893 // Act upon the bookmark according to the type.
894 if (bookmarkCursor.getInt(bookmarkCursor.getColumnIndex(BookmarksDatabaseHelper.IS_FOLDER)) == 1) { // The selected bookmark is a folder.
895 // Store the new folder name in `currentBookmarksFolder`.
896 currentBookmarksFolder = bookmarkCursor.getString(bookmarkCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME));
898 // Load the new folder.
899 loadBookmarksFolder();
900 } else { // The selected bookmark is not a folder.
901 // Load the bookmark URL.
902 loadUrl(bookmarkCursor.getString(bookmarkCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_URL)));
904 // Close the bookmarks drawer.
905 drawerLayout.closeDrawer(GravityCompat.END);
908 // Close the `Cursor`.
909 bookmarkCursor.close();
912 bookmarksListView.setOnItemLongClickListener((parent, view, position, id) -> {
913 // Convert the database ID from `long` to `int`.
914 int databaseId = (int) id;
916 // Find out if the selected bookmark is a folder.
917 boolean isFolder = bookmarksDatabaseHelper.isFolder(databaseId);
920 // Save the current folder name, which is used in `onSaveEditBookmarkFolder()`.
921 oldFolderNameString = bookmarksCursor.getString(bookmarksCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME));
923 // Show the edit bookmark folder `AlertDialog` and name the instance `@string/edit_folder`.
924 AppCompatDialogFragment editFolderDialog = EditBookmarkFolderDialog.folderDatabaseId(databaseId);
925 editFolderDialog.show(supportFragmentManager, resources.getString(R.string.edit_folder));
927 // Show the edit bookmark `AlertDialog` and name the instance `@string/edit_bookmark`.
928 AppCompatDialogFragment editBookmarkDialog = EditBookmarkDialog.bookmarkDatabaseId(databaseId);
929 editBookmarkDialog.show(supportFragmentManager, resources.getString(R.string.edit_bookmark));
932 // Consume the event.
936 // Get the status bar pixel size.
937 int statusBarResourceId = resources.getIdentifier("status_bar_height", "dimen", "android");
938 int statusBarPixelSize = resources.getDimensionPixelSize(statusBarResourceId);
940 // Get the resource density.
941 float screenDensity = resources.getDisplayMetrics().density;
943 // Calculate the drawer header padding. This is used to move the text in the drawer headers below any cutouts.
944 drawerHeaderPaddingLeftAndRight = (int) (15 * screenDensity);
945 drawerHeaderPaddingTop = statusBarPixelSize + (int) (4 * screenDensity);
946 drawerHeaderPaddingBottom = (int) (8 * screenDensity);
948 // The drawer listener is used to update the navigation menu.`
949 drawerLayout.addDrawerListener(new DrawerLayout.DrawerListener() {
951 public void onDrawerSlide(@NonNull View drawerView, float slideOffset) {
955 public void onDrawerOpened(@NonNull View drawerView) {
959 public void onDrawerClosed(@NonNull View drawerView) {
963 public void onDrawerStateChanged(int newState) {
964 if ((newState == DrawerLayout.STATE_SETTLING) || (newState == DrawerLayout.STATE_DRAGGING)) { // A drawer is opening or closing.
965 // Get handles for the drawer headers.
966 TextView navigationHeaderTextView = findViewById(R.id.navigationText);
967 TextView bookmarksHeaderTextView = findViewById(R.id.bookmarks_title_textview);
969 // 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.
970 if (navigationHeaderTextView != null) {
971 navigationHeaderTextView.setPadding(drawerHeaderPaddingLeftAndRight, drawerHeaderPaddingTop, drawerHeaderPaddingLeftAndRight, drawerHeaderPaddingBottom);
974 // 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.
975 if (bookmarksHeaderTextView != null) {
976 bookmarksHeaderTextView.setPadding(drawerHeaderPaddingLeftAndRight, drawerHeaderPaddingTop, drawerHeaderPaddingLeftAndRight, drawerHeaderPaddingBottom);
979 // Update the back, forward, history, and requests menu items.
980 navigationBackMenuItem.setEnabled(mainWebView.canGoBack());
981 navigationForwardMenuItem.setEnabled(mainWebView.canGoForward());
982 navigationHistoryMenuItem.setEnabled((mainWebView.canGoBack() || mainWebView.canGoForward()));
983 navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
985 // Hide the keyboard (if displayed).
986 inputMethodManager.hideSoftInputFromWindow(mainWebView.getWindowToken(), 0);
988 // 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.
989 urlTextBox.clearFocus();
990 mainWebView.clearFocus();
995 // drawerToggle creates the hamburger icon at the start of the AppBar.
996 drawerToggle = new ActionBarDrawerToggle(this, drawerLayout, supportAppBar, R.string.open_navigation_drawer, R.string.close_navigation_drawer);
998 // Get a handle for the progress bar.
999 final ProgressBar progressBar = findViewById(R.id.progress_bar);
1001 mainWebView.setWebChromeClient(new WebChromeClient() {
1002 // Update the progress bar when a page is loading.
1004 public void onProgressChanged(WebView view, int progress) {
1005 // Inject the night mode CSS if night mode is enabled.
1007 // `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
1008 // used by WordPress. `text-decoration: none` removes all text underlines. `text-shadow: none` removes text shadows, which usually have a hard coded color.
1009 // `border: none` removes all borders, which can also be used to underline text. `a {color: #1565C0}` sets links to be a dark blue.
1010 // `::selection {background: #0D47A1}' sets the text selection highlight color to be a dark blue. `!important` takes precedent over any existing sub-settings.
1011 mainWebView.evaluateJavascript("(function() {var parent = document.getElementsByTagName('head').item(0); var style = document.createElement('style'); style.type = 'text/css'; " +
1012 "style.innerHTML = '* {background-color: #212121 !important; color: #BDBDBD !important; box-shadow: none !important; text-decoration: none !important;" +
1013 "text-shadow: none !important; border: none !important;} a {color: #1565C0 !important;} ::selection {background: #0D47A1 !important;}'; parent.appendChild(style)})()", value -> {
1014 // Initialize a handler to display `mainWebView`.
1015 Handler displayWebViewHandler = new Handler();
1017 // Setup a runnable to display `mainWebView` after a delay to allow the CSS to be applied.
1018 Runnable displayWebViewRunnable = () -> {
1019 // Only display `mainWebView` if the progress bar is gone. This prevents the display of the `WebView` while it is still loading.
1020 if (progressBar.getVisibility() == View.GONE) {
1021 mainWebView.setVisibility(View.VISIBLE);
1025 // Displaying of `mainWebView` after 500 milliseconds.
1026 displayWebViewHandler.postDelayed(displayWebViewRunnable, 500);
1030 // Update the progress bar.
1031 progressBar.setProgress(progress);
1033 // Set the visibility of the progress bar.
1034 if (progress < 100) {
1035 // Show the progress bar.
1036 progressBar.setVisibility(View.VISIBLE);
1038 // Hide the progress bar.
1039 progressBar.setVisibility(View.GONE);
1041 // Display `mainWebView` if night mode is disabled.
1042 // 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
1043 // currently enabled.
1045 mainWebView.setVisibility(View.VISIBLE);
1048 //Stop the swipe to refresh indicator if it is running
1049 swipeRefreshLayout.setRefreshing(false);
1053 // Set the favorite icon when it changes.
1055 public void onReceivedIcon(WebView view, Bitmap icon) {
1056 // Only update the favorite icon if the website has finished loading.
1057 if (progressBar.getVisibility() == View.GONE) {
1058 // Save a copy of the favorite icon.
1059 favoriteIconBitmap = icon;
1061 // Place the favorite icon in the appBar.
1062 favoriteIconImageView.setImageBitmap(Bitmap.createScaledBitmap(icon, 64, 64, true));
1066 // Save a copy of the title when it changes.
1068 public void onReceivedTitle(WebView view, String title) {
1069 // Save a copy of the title.
1070 webViewTitle = title;
1073 // Enter full screen video.
1075 public void onShowCustomView(View view, CustomViewCallback callback) {
1076 // Set the full screen video flag.
1077 displayingFullScreenVideo = true;
1079 // Pause the ad if this is the free flavor.
1080 if (BuildConfig.FLAVOR.contentEquals("free")) {
1081 // The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations.
1082 AdHelper.pauseAd(findViewById(R.id.adview));
1085 // Remove the translucent overlays.
1086 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
1088 // Remove the translucent status bar overlay on the `Drawer Layout`, which is special and needs its own command.
1089 drawerLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
1091 /* SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
1092 * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
1093 * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
1095 rootCoordinatorLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
1097 // Set `rootCoordinatorLayout` to fill the entire screen.
1098 rootCoordinatorLayout.setFitsSystemWindows(false);
1100 // Disable the sliding drawers.
1101 drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED);
1103 // Add `view` to `fullScreenVideoFrameLayout` and display it on the screen.
1104 fullScreenVideoFrameLayout.addView(view);
1105 fullScreenVideoFrameLayout.setVisibility(View.VISIBLE);
1108 // Exit full screen video.
1110 public void onHideCustomView() {
1111 // Unset the full screen video flag.
1112 displayingFullScreenVideo = false;
1114 // Hide `fullScreenVideoFrameLayout`.
1115 fullScreenVideoFrameLayout.removeAllViews();
1116 fullScreenVideoFrameLayout.setVisibility(View.GONE);
1118 // Enable the sliding drawers.
1119 drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED);
1121 // Apply the appropriate full screen mode the `SYSTEM_UI` flags.
1122 if (fullScreenBrowsingModeEnabled && inFullScreenBrowsingMode) { // Privacy Browser is currently in full screen browsing mode.
1123 if (hideSystemBarsOnFullscreen) { // Hide everything.
1124 // Remove the translucent navigation setting if it is currently flagged.
1125 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
1127 // Remove the translucent status bar overlay.
1128 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
1130 // Remove the translucent status bar overlay on the `Drawer Layout`, which is special and needs its own command.
1131 drawerLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
1133 /* SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
1134 * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
1135 * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
1137 rootCoordinatorLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
1138 } else { // Hide everything except the status and navigation bars.
1139 // Remove any `SYSTEM_UI` flags from `rootCoordinatorLayout`.
1140 rootCoordinatorLayout.setSystemUiVisibility(0);
1142 // Add the translucent status flag if it is unset.
1143 getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
1145 if (translucentNavigationBarOnFullscreen) {
1146 // Set the navigation bar to be translucent. This also resets `drawerLayout's` `View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN`.
1147 getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
1149 // Set the navigation bar to be black.
1150 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
1153 } else { // Switch to normal viewing mode.
1154 // Show the `appBar` if `findOnPageLinearLayout` is not visible.
1155 if (findOnPageLinearLayout.getVisibility() == View.GONE) {
1159 // Show the `BannerAd` in the free flavor.
1160 if (BuildConfig.FLAVOR.contentEquals("free")) {
1161 // Initialize the ad. The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations.
1162 AdHelper.initializeAds(findViewById(R.id.adview), getApplicationContext(), getFragmentManager(), getString(R.string.google_app_id), getString(R.string.ad_unit_id));
1165 // Remove any `SYSTEM_UI` flags from `rootCoordinatorLayout`.
1166 rootCoordinatorLayout.setSystemUiVisibility(0);
1168 // Remove the translucent navigation bar flag if it is set.
1169 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
1171 // Add the translucent status flag if it is unset. This also resets `drawerLayout's` `View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN`.
1172 getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
1174 // Constrain `rootCoordinatorLayout` inside the status and navigation bars.
1175 rootCoordinatorLayout.setFitsSystemWindows(true);
1178 // Show the ad if this is the free flavor.
1179 if (BuildConfig.FLAVOR.contentEquals("free")) {
1180 // Reload the ad. The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations.
1181 AdHelper.loadAd(findViewById(R.id.adview), getApplicationContext(), getString(R.string.ad_unit_id));
1187 public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) {
1188 // Show the file chooser if the device is running API >= 21.
1189 if (Build.VERSION.SDK_INT >= 21) {
1190 // Store the file path callback.
1191 fileChooserCallback = filePathCallback;
1193 // Create an intent to open a chooser based ont the file chooser parameters.
1194 Intent fileChooserIntent = fileChooserParams.createIntent();
1196 // Open the file chooser. Currently only one `startActivityForResult` exists in this activity, so the request code, used to differentiate them, is simply `0`.
1197 startActivityForResult(fileChooserIntent, 0);
1203 // Register `mainWebView` for a context menu. This is used to see link targets and download images.
1204 registerForContextMenu(mainWebView);
1206 // Allow the downloading of files.
1207 mainWebView.setDownloadListener((String url, String userAgent, String contentDisposition, String mimetype, long contentLength) -> {
1208 // Check if the download should be processed by an external app.
1209 if (downloadWithExternalApp) { // Download with an external app.
1210 openUrlWithExternalApp(url);
1211 } else { // Download with Android's download manager.
1212 // Check to see if the WRITE_EXTERNAL_STORAGE permission has already been granted.
1213 if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) { // The storage permission has not been granted.
1214 // The WRITE_EXTERNAL_STORAGE permission needs to be requested.
1216 // Store the variables for future use by `onRequestPermissionsResult()`.
1218 downloadContentDisposition = contentDisposition;
1219 downloadContentLength = contentLength;
1221 // Show a dialog if the user has previously denied the permission.
1222 if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) { // Show a dialog explaining the request first.
1223 // Instantiate the download location permission alert dialog and set the download type to DOWNLOAD_FILE.
1224 DialogFragment downloadLocationPermissionDialogFragment = DownloadLocationPermissionDialog.downloadType(DownloadLocationPermissionDialog.DOWNLOAD_FILE);
1226 // Show the download location permission alert dialog. The permission will be requested when the the dialog is closed.
1227 downloadLocationPermissionDialogFragment.show(getFragmentManager(), getString(R.string.download_location));
1228 } else { // Show the permission request directly.
1229 // Request the permission. The download dialog will be launched by `onRequestPermissionResult()`.
1230 ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_FILE_REQUEST_CODE);
1232 } else { // The storage permission has already been granted.
1233 // Get a handle for the download file alert dialog.
1234 AppCompatDialogFragment downloadFileDialogFragment = DownloadFileDialog.fromUrl(url, contentDisposition, contentLength);
1236 // Show the download file alert dialog.
1237 downloadFileDialogFragment.show(supportFragmentManager, getString(R.string.download));
1242 // Allow pinch to zoom.
1243 mainWebView.getSettings().setBuiltInZoomControls(true);
1245 // Hide zoom controls.
1246 mainWebView.getSettings().setDisplayZoomControls(false);
1248 // Don't allow mixed content (HTTP and HTTPS) on the same website.
1249 if (Build.VERSION.SDK_INT >= 21) {
1250 mainWebView.getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_NEVER_ALLOW);
1253 // Set the WebView to use a wide viewport. Otherwise, some web pages will be scrunched and some content will render outside the screen.
1254 mainWebView.getSettings().setUseWideViewPort(true);
1256 // Set the WebView to load in overview mode (zoomed out to the maximum width).
1257 mainWebView.getSettings().setLoadWithOverviewMode(true);
1259 // Explicitly disable geolocation.
1260 mainWebView.getSettings().setGeolocationEnabled(false);
1262 // Initialize cookieManager.
1263 cookieManager = CookieManager.getInstance();
1265 // 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).
1266 customHeaders.put("X-Requested-With", "");
1268 // Initialize the default preference values the first time the program is run. `false` keeps this command from resetting any current preferences back to default.
1269 PreferenceManager.setDefaultValues(this, R.xml.preferences, false);
1271 // Get a handle for the `Runtime`.
1272 privacyBrowserRuntime = Runtime.getRuntime();
1274 // Store the application's private data directory.
1275 privateDataDirectoryString = getApplicationInfo().dataDir;
1276 // `dataDir` will vary, but will be something like `/data/user/0/com.stoutner.privacybrowser.standard`, which links to `/data/data/com.stoutner.privacybrowser.standard`.
1278 // Initialize `inFullScreenBrowsingMode`, which is always false at this point because Privacy Browser never starts in full screen browsing mode.
1279 inFullScreenBrowsingMode = false;
1281 // Initialize the privacy settings variables.
1282 javaScriptEnabled = false;
1283 firstPartyCookiesEnabled = false;
1284 thirdPartyCookiesEnabled = false;
1285 domStorageEnabled = false;
1286 saveFormDataEnabled = false; // Form data can be removed once the minimum API >= 26.
1289 // Store the default user agent.
1290 webViewDefaultUserAgent = mainWebView.getSettings().getUserAgentString();
1292 // Initialize the WebView title.
1293 webViewTitle = getString(R.string.no_title);
1295 // Initialize the favorite icon bitmap. `ContextCompat` must be used until API >= 21.
1296 Drawable favoriteIconDrawable = ContextCompat.getDrawable(getApplicationContext(), R.drawable.world);
1297 BitmapDrawable favoriteIconBitmapDrawable = (BitmapDrawable) favoriteIconDrawable;
1298 assert favoriteIconBitmapDrawable != null;
1299 favoriteIconDefaultBitmap = favoriteIconBitmapDrawable.getBitmap();
1301 // If the favorite icon is null, load the default.
1302 if (favoriteIconBitmap == null) {
1303 favoriteIconBitmap = favoriteIconDefaultBitmap;
1306 // Initialize the user agent array adapter and string array.
1307 userAgentNamesArray = ArrayAdapter.createFromResource(this, R.array.user_agent_names, R.layout.spinner_item);
1308 userAgentDataArray = resources.getStringArray(R.array.user_agent_data);
1310 // Apply the app settings from the shared preferences.
1313 // Instantiate the block list helper.
1314 BlockListHelper blockListHelper = new BlockListHelper();
1316 // Initialize the list of resource requests.
1317 resourceRequests = new ArrayList<>();
1319 // Parse the block lists.
1320 final ArrayList<List<String[]>> easyList = blockListHelper.parseBlockList(getAssets(), "blocklists/easylist.txt");
1321 final ArrayList<List<String[]>> easyPrivacy = blockListHelper.parseBlockList(getAssets(), "blocklists/easyprivacy.txt");
1322 final ArrayList<List<String[]>> fanboysAnnoyanceList = blockListHelper.parseBlockList(getAssets(), "blocklists/fanboy-annoyance.txt");
1323 final ArrayList<List<String[]>> fanboysSocialList = blockListHelper.parseBlockList(getAssets(), "blocklists/fanboy-social.txt");
1324 final ArrayList<List<String[]>> ultraPrivacy = blockListHelper.parseBlockList(getAssets(), "blocklists/ultraprivacy.txt");
1326 // Store the list versions.
1327 easyListVersion = easyList.get(0).get(0)[0];
1328 easyPrivacyVersion = easyPrivacy.get(0).get(0)[0];
1329 fanboysAnnoyanceVersion = fanboysAnnoyanceList.get(0).get(0)[0];
1330 fanboysSocialVersion = fanboysSocialList.get(0).get(0)[0];
1331 ultraPrivacyVersion = ultraPrivacy.get(0).get(0)[0];
1333 // Get a handle for the activity. This is used to update the requests counter while the navigation menu is open.
1334 Activity activity = this;
1336 mainWebView.setWebViewClient(new WebViewClient() {
1337 // `shouldOverrideUrlLoading` makes this `WebView` the default handler for URLs inside the app, so that links are not kicked out to other apps.
1338 // The deprecated `shouldOverrideUrlLoading` must be used until API >= 24.
1339 @SuppressWarnings("deprecation")
1341 public boolean shouldOverrideUrlLoading(WebView view, String url) {
1342 if (url.startsWith("http")) { // Load the URL in Privacy Browser.
1343 // Reset the formatted URL string so the page will load correctly if blocking of third-party requests is enabled.
1344 formattedUrlString = "";
1346 // Apply the domain settings for the new URL. `applyDomainSettings` doesn't do anything if the domain has not changed.
1347 boolean userAgentChanged = applyDomainSettings(url, true, false);
1349 // Check if the user agent has changed.
1350 if (userAgentChanged) {
1351 // Manually load the URL. The changing of the user agent will cause WebView to reload the previous URL.
1352 mainWebView.loadUrl(url, customHeaders);
1354 // Returning true indicates that Privacy Browser is manually handling the loading of the URL.
1357 // Returning false causes the current WebView to handle the URL and prevents it from adding redirects to the history list.
1360 } else if (url.startsWith("mailto:")) { // Load the email address in an external email program.
1361 // Use `ACTION_SENDTO` instead of `ACTION_SEND` so that only email programs are launched.
1362 Intent emailIntent = new Intent(Intent.ACTION_SENDTO);
1364 // Parse the url and set it as the data for the intent.
1365 emailIntent.setData(Uri.parse(url));
1367 // Open the email program in a new task instead of as part of Privacy Browser.
1368 emailIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
1371 startActivity(emailIntent);
1373 // Returning true indicates Privacy Browser is handling the URL by creating an intent.
1375 } else if (url.startsWith("tel:")) { // Load the phone number in the dialer.
1376 // Open the dialer and load the phone number, but wait for the user to place the call.
1377 Intent dialIntent = new Intent(Intent.ACTION_DIAL);
1379 // Add the phone number to the intent.
1380 dialIntent.setData(Uri.parse(url));
1382 // Open the dialer in a new task instead of as part of Privacy Browser.
1383 dialIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
1386 startActivity(dialIntent);
1388 // Returning true indicates Privacy Browser is handling the URL by creating an intent.
1390 } else { // Load a system chooser to select an app that can handle the URL.
1391 // Open an app that can handle the URL.
1392 Intent genericIntent = new Intent(Intent.ACTION_VIEW);
1394 // Add the URL to the intent.
1395 genericIntent.setData(Uri.parse(url));
1397 // List all apps that can handle the URL instead of just opening the first one.
1398 genericIntent.addCategory(Intent.CATEGORY_BROWSABLE);
1400 // Open the app in a new task instead of as part of Privacy Browser.
1401 genericIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
1403 // Start the app or display a snackbar if no app is available to handle the URL.
1405 startActivity(genericIntent);
1406 } catch (ActivityNotFoundException exception) {
1407 Snackbar.make(mainWebView, getString(R.string.unrecognized_url) + " " + url, Snackbar.LENGTH_SHORT).show();
1410 // Returning true indicates Privacy Browser is handling the URL by creating an intent.
1415 // Check requests against the block lists. The deprecated `shouldInterceptRequest()` must be used until minimum API >= 21.
1416 @SuppressWarnings("deprecation")
1418 public WebResourceResponse shouldInterceptRequest(WebView view, String url){
1419 // Create an empty web resource response to be used if the resource request is blocked.
1420 WebResourceResponse emptyWebResourceResponse = new WebResourceResponse("text/plain", "utf8", new ByteArrayInputStream("".getBytes()));
1422 // Reset the whitelist results tracker.
1423 whiteListResultStringArray = null;
1425 // Initialize the third party request tracker.
1426 boolean isThirdPartyRequest = false;
1428 // Initialize the current domain string.
1429 String currentDomain = "";
1431 // Nobody is happy when comparing null strings.
1432 if (!(formattedUrlString == null) && !(url == null)) {
1433 // Get the domain strings to URIs.
1434 Uri currentDomainUri = Uri.parse(formattedUrlString);
1435 Uri requestDomainUri = Uri.parse(url);
1437 // Get the domain host names.
1438 String currentBaseDomain = currentDomainUri.getHost();
1439 String requestBaseDomain = requestDomainUri.getHost();
1441 // Update the current domain variable.
1442 currentDomain = currentBaseDomain;
1444 // Only compare the current base domain and the request base domain if neither is null.
1445 if (!(currentBaseDomain == null) && !(requestBaseDomain == null)) {
1446 // Determine the current base domain.
1447 while (currentBaseDomain.indexOf(".", currentBaseDomain.indexOf(".") + 1) > 0) { // There is at least one subdomain.
1448 // Remove the first subdomain.
1449 currentBaseDomain = currentBaseDomain.substring(currentBaseDomain.indexOf(".") + 1);
1452 // Determine the request base domain.
1453 while (requestBaseDomain.indexOf(".", requestBaseDomain.indexOf(".") + 1) > 0) { // There is at least one subdomain.
1454 // Remove the first subdomain.
1455 requestBaseDomain = requestBaseDomain.substring(requestBaseDomain.indexOf(".") + 1);
1458 // Update the third party request tracker.
1459 isThirdPartyRequest = !currentBaseDomain.equals(requestBaseDomain);
1463 // Block third-party requests if enabled.
1464 if (isThirdPartyRequest && blockAllThirdPartyRequests) {
1465 // Increment the blocked requests counters.
1467 thirdPartyBlockedRequests++;
1469 // Update the titles of the blocklist menu items. This must be run from the UI thread.
1470 activity.runOnUiThread(() -> {
1471 navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
1472 blocklistsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
1473 blockAllThirdPartyRequestsMenuItem.setTitle(thirdPartyBlockedRequests + " - " + getString(R.string.block_all_third_party_requests));
1476 // Add the request to the log.
1477 resourceRequests.add(new String[]{String.valueOf(REQUEST_THIRD_PARTY), url});
1479 // Return an empty web resource response.
1480 return emptyWebResourceResponse;
1483 // Check UltraPrivacy if it is enabled.
1484 if (ultraPrivacyEnabled) {
1485 if (blockListHelper.isBlocked(currentDomain, url, isThirdPartyRequest, ultraPrivacy)) {
1486 // Increment the blocked requests counters.
1488 ultraPrivacyBlockedRequests++;
1490 // Update the titles of the blocklist menu items. This must be run from the UI thread.
1491 activity.runOnUiThread(() -> {
1492 navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
1493 blocklistsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
1494 ultraPrivacyMenuItem.setTitle(ultraPrivacyBlockedRequests + " - " + getString(R.string.ultraprivacy));
1497 // The resource request was blocked. Return an empty web resource response.
1498 return emptyWebResourceResponse;
1501 // If the whitelist result is not null, the request has been allowed by UltraPrivacy.
1502 if (whiteListResultStringArray != null) {
1503 // Add a whitelist entry to the resource requests array.
1504 resourceRequests.add(whiteListResultStringArray);
1506 // The resource request has been allowed by UltraPrivacy. `return null` loads the requested resource.
1511 // Check EasyList if it is enabled.
1512 if (easyListEnabled) {
1513 if (blockListHelper.isBlocked(currentDomain, url, isThirdPartyRequest, easyList)) {
1514 // Increment the blocked requests counters.
1516 easyListBlockedRequests++;
1518 // Update the titles of the blocklist menu items. This must be run from the UI thread.
1519 activity.runOnUiThread(() -> {
1520 navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
1521 blocklistsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
1522 easyListMenuItem.setTitle(easyListBlockedRequests + " - " + getString(R.string.easylist));
1525 // Reset the whitelist results tracker (because otherwise it will sometimes add results to the list due to a race condition).
1526 whiteListResultStringArray = null;
1528 // The resource request was blocked. Return an empty web resource response.
1529 return emptyWebResourceResponse;
1533 // Check EasyPrivacy if it is enabled.
1534 if (easyPrivacyEnabled) {
1535 if (blockListHelper.isBlocked(currentDomain, url, isThirdPartyRequest, easyPrivacy)) {
1536 // Increment the blocked requests counters.
1538 easyPrivacyBlockedRequests++;
1540 // Update the titles of the blocklist menu items. This must be run from the UI thread.
1541 activity.runOnUiThread(() -> {
1542 navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
1543 blocklistsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
1544 easyPrivacyMenuItem.setTitle(easyPrivacyBlockedRequests + " - " + getString(R.string.easyprivacy));
1547 // Reset the whitelist results tracker (because otherwise it will sometimes add results to the list due to a race condition).
1548 whiteListResultStringArray = null;
1550 // The resource request was blocked. Return an empty web resource response.
1551 return emptyWebResourceResponse;
1555 // Check Fanboy’s Annoyance List if it is enabled.
1556 if (fanboysAnnoyanceListEnabled) {
1557 if (blockListHelper.isBlocked(currentDomain, url, isThirdPartyRequest, fanboysAnnoyanceList)) {
1558 // Increment the blocked requests counters.
1560 fanboysAnnoyanceListBlockedRequests++;
1562 // Update the titles of the blocklist menu items. This must be run from the UI thread.
1563 activity.runOnUiThread(() -> {
1564 navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
1565 blocklistsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
1566 fanboysAnnoyanceListMenuItem.setTitle(fanboysAnnoyanceListBlockedRequests + " - " + getString(R.string.fanboys_annoyance_list));
1569 // Reset the whitelist results tracker (because otherwise it will sometimes add results to the list due to a race condition).
1570 whiteListResultStringArray = null;
1572 // The resource request was blocked. Return an empty web resource response.
1573 return emptyWebResourceResponse;
1575 } else if (fanboysSocialBlockingListEnabled){ // Only check Fanboy’s Social Blocking List if Fanboy’s Annoyance List is disabled.
1576 if (blockListHelper.isBlocked(currentDomain, url, isThirdPartyRequest, fanboysSocialList)) {
1577 // Increment the blocked requests counters.
1579 fanboysSocialBlockingListBlockedRequests++;
1581 // Update the titles of the blocklist menu items. This must be run from the UI thread.
1582 activity.runOnUiThread(() -> {
1583 navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
1584 blocklistsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
1585 fanboysSocialBlockingListMenuItem.setTitle(fanboysSocialBlockingListBlockedRequests + " - " + getString(R.string.fanboys_social_blocking_list));
1588 // Reset the whitelist results tracker (because otherwise it will sometimes add results to the list due to a race condition).
1589 whiteListResultStringArray = null;
1591 // The resource request was blocked. Return an empty web resource response.
1592 return emptyWebResourceResponse;
1596 // Add the request to the log because it hasn't been processed by any of the previous checks.
1597 if (whiteListResultStringArray != null ) { // The request was processed by a whitelist.
1598 resourceRequests.add(whiteListResultStringArray);
1599 } else { // The request didn't match any blocklist entry. Log it as a default request.
1600 resourceRequests.add(new String[]{String.valueOf(REQUEST_DEFAULT), url});
1603 // The resource request has not been blocked. `return null` loads the requested resource.
1607 // Handle HTTP authentication requests.
1609 public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, String host, String realm) {
1610 // Store `handler` so it can be accessed from `onHttpAuthenticationCancel()` and `onHttpAuthenticationProceed()`.
1611 httpAuthHandler = handler;
1613 // Display the HTTP authentication dialog.
1614 AppCompatDialogFragment httpAuthenticationDialogFragment = HttpAuthenticationDialog.displayDialog(host, realm);
1615 httpAuthenticationDialogFragment.show(supportFragmentManager, getString(R.string.http_authentication));
1618 // Update the URL in urlTextBox when the page starts to load.
1620 public void onPageStarted(WebView view, String url, Bitmap favicon) {
1621 // 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.
1622 // This is also used to determine when to check for pinned mismatches.
1623 urlIsLoading = true;
1625 // Reset the list of host IP addresses.
1626 currentHostIpAddresses = "";
1628 // Reset the list of resource requests.
1629 resourceRequests.clear();
1631 // Initialize the counters for requests blocked by each blocklist.
1632 blockedRequests = 0;
1633 easyListBlockedRequests = 0;
1634 easyPrivacyBlockedRequests = 0;
1635 fanboysAnnoyanceListBlockedRequests = 0;
1636 fanboysSocialBlockingListBlockedRequests = 0;
1637 ultraPrivacyBlockedRequests = 0;
1638 thirdPartyBlockedRequests = 0;
1640 // If night mode is enabled, hide `mainWebView` until after the night mode CSS is applied.
1642 mainWebView.setVisibility(View.INVISIBLE);
1645 // Hide the keyboard. `0` indicates no additional flags.
1646 inputMethodManager.hideSoftInputFromWindow(mainWebView.getWindowToken(), 0);
1648 // Check to see if Privacy Browser is waiting on Orbot.
1649 if (!waitingForOrbot) { // Process the URL.
1650 // 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.
1651 formattedUrlString = url;
1653 // Display the formatted URL text.
1654 urlTextBox.setText(formattedUrlString);
1656 // Apply text highlighting to `urlTextBox`.
1659 // Get a URI for the current URL.
1660 Uri currentUri = Uri.parse(formattedUrlString);
1662 // Get the IP addresses for the host.
1663 new GetHostIpAddresses(activity).execute(currentUri.getHost());
1665 // Apply any custom domain settings if the URL was loaded by navigating history.
1666 if (navigatingHistory) {
1667 // Apply the domain settings.
1668 boolean userAgentChanged = applyDomainSettings(url, true, false);
1670 // Reset `navigatingHistory`.
1671 navigatingHistory = false;
1673 // Manually load the URL if the user agent has changed, which will have caused the previous URL to be reloaded.
1674 if (userAgentChanged) {
1675 loadUrl(formattedUrlString);
1679 // Replace Refresh with Stop if the menu item has been created. (The WebView typically begins loading before the menu items are instantiated.)
1680 if (refreshMenuItem != null) {
1682 refreshMenuItem.setTitle(R.string.stop);
1684 // If the icon is displayed in the AppBar, set it according to the theme.
1685 if (displayAdditionalAppBarIcons) {
1687 refreshMenuItem.setIcon(R.drawable.close_dark);
1689 refreshMenuItem.setIcon(R.drawable.close_light);
1696 // It is necessary to update `formattedUrlString` and `urlTextBox` after the page finishes loading because the final URL can change during load.
1698 public void onPageFinished(WebView view, String url) {
1699 // Reset the wide view port if it has been turned off by the waiting for Orbot message.
1700 if (!waitingForOrbot) {
1701 // Only use a wide view port if the URL starts with `http`, not for `file://` and `content://`.
1702 mainWebView.getSettings().setUseWideViewPort(url.startsWith("http"));
1705 // Flush any cookies to persistent storage. `CookieManager` has become very lazy about flushing cookies in recent versions.
1706 if (firstPartyCookiesEnabled && Build.VERSION.SDK_INT >= 21) {
1707 cookieManager.flush();
1710 // Update the Refresh menu item if it has been created.
1711 if (refreshMenuItem != null) {
1712 // Reset the Refresh title.
1713 refreshMenuItem.setTitle(R.string.refresh);
1715 // If the icon is displayed in the AppBar, reset it according to the theme.
1716 if (displayAdditionalAppBarIcons) {
1718 refreshMenuItem.setIcon(R.drawable.refresh_enabled_dark);
1720 refreshMenuItem.setIcon(R.drawable.refresh_enabled_light);
1727 // Clear the cache and history if Incognito Mode is enabled.
1728 if (incognitoModeEnabled) {
1729 // Clear the cache. `true` includes disk files.
1730 mainWebView.clearCache(true);
1732 // Clear the back/forward history.
1733 mainWebView.clearHistory();
1735 // Manually delete cache folders.
1737 // Delete the main cache directory.
1738 privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/cache");
1740 // Delete the secondary `Service Worker` cache directory.
1741 // A `String[]` must be used because the directory contains a space and `Runtime.exec` will not escape the string correctly otherwise.
1742 privacyBrowserRuntime.exec(new String[] {"rm", "-rf", privateDataDirectoryString + "/app_webview/Service Worker/"});
1743 } catch (IOException e) {
1744 // Do nothing if an error is thrown.
1748 // Update the URL text box and apply domain settings if not waiting on Orbot.
1749 if (!waitingForOrbot) {
1750 // Check to see if `WebView` has set `url` to be `about:blank`.
1751 if (url.equals("about:blank")) { // `WebView` is blank, so `formattedUrlString` should be `""` and `urlTextBox` should display a hint.
1752 // Set `formattedUrlString` to `""`.
1753 formattedUrlString = "";
1755 urlTextBox.setText(formattedUrlString);
1757 // Request focus for `urlTextBox`.
1758 urlTextBox.requestFocus();
1760 // Display the keyboard.
1761 inputMethodManager.showSoftInput(urlTextBox, 0);
1763 // Apply the domain settings. This clears any settings from the previous domain.
1764 applyDomainSettings(formattedUrlString, true, false);
1765 } else { // `WebView` has loaded a webpage.
1766 // 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.
1767 formattedUrlString = mainWebView.getUrl();
1769 // Only update the URL text box if the user is not typing in it.
1770 if (!urlTextBox.hasFocus()) {
1771 // Display the formatted URL text.
1772 urlTextBox.setText(formattedUrlString);
1774 // Apply text highlighting to `urlTextBox`.
1779 // Store the SSL certificate so it can be accessed from `ViewSslCertificateDialog` and `PinnedMismatchDialog`.
1780 sslCertificate = mainWebView.getCertificate();
1782 // Check the current website information against any pinned domain information if the current IP addresses have been loaded.
1783 if (!gettingIpAddresses) {
1784 checkPinnedMismatch();
1788 // Reset `urlIsLoading`, which is used to prevent reloads on redirect if the user agent changes. It is also used to determine when to check for pinned mismatches.
1789 urlIsLoading = false;
1792 // Handle SSL Certificate errors.
1794 public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
1795 // Get the current website SSL certificate.
1796 SslCertificate currentWebsiteSslCertificate = error.getCertificate();
1798 // Extract the individual pieces of information from the current website SSL certificate.
1799 String currentWebsiteIssuedToCName = currentWebsiteSslCertificate.getIssuedTo().getCName();
1800 String currentWebsiteIssuedToOName = currentWebsiteSslCertificate.getIssuedTo().getOName();
1801 String currentWebsiteIssuedToUName = currentWebsiteSslCertificate.getIssuedTo().getUName();
1802 String currentWebsiteIssuedByCName = currentWebsiteSslCertificate.getIssuedBy().getCName();
1803 String currentWebsiteIssuedByOName = currentWebsiteSslCertificate.getIssuedBy().getOName();
1804 String currentWebsiteIssuedByUName = currentWebsiteSslCertificate.getIssuedBy().getUName();
1805 Date currentWebsiteSslStartDate = currentWebsiteSslCertificate.getValidNotBeforeDate();
1806 Date currentWebsiteSslEndDate = currentWebsiteSslCertificate.getValidNotAfterDate();
1808 // Proceed to the website if the current SSL website certificate matches the pinned domain certificate.
1809 if (pinnedSslCertificate &&
1810 currentWebsiteIssuedToCName.equals(pinnedSslIssuedToCName) && currentWebsiteIssuedToOName.equals(pinnedSslIssuedToOName) &&
1811 currentWebsiteIssuedToUName.equals(pinnedSslIssuedToUName) && currentWebsiteIssuedByCName.equals(pinnedSslIssuedByCName) &&
1812 currentWebsiteIssuedByOName.equals(pinnedSslIssuedByOName) && currentWebsiteIssuedByUName.equals(pinnedSslIssuedByUName) &&
1813 currentWebsiteSslStartDate.equals(pinnedSslStartDate) && currentWebsiteSslEndDate.equals(pinnedSslEndDate)) {
1815 // An SSL certificate is pinned and matches the current domain certificate. Proceed to the website without displaying an error.
1817 } else { // Either there isn't a pinned SSL certificate or it doesn't match the current website certificate.
1818 // Store `handler` so it can be accesses from `onSslErrorCancel()` and `onSslErrorProceed()`.
1819 sslErrorHandler = handler;
1821 // Display the SSL error `AlertDialog`.
1822 AppCompatDialogFragment sslCertificateErrorDialogFragment = SslCertificateErrorDialog.displayDialog(error);
1823 sslCertificateErrorDialogFragment.show(supportFragmentManager, getString(R.string.ssl_certificate_error));
1828 // Get the intent that started the app.
1829 Intent launchingIntent = getIntent();
1831 // Get the information from the intent.
1832 String launchingIntentAction = launchingIntent.getAction();
1833 Uri launchingIntentUriData = launchingIntent.getData();
1835 // If the intent action is a web search, perform the search.
1836 if ((launchingIntentAction != null) && launchingIntentAction.equals(Intent.ACTION_WEB_SEARCH)) {
1837 // Create an encoded URL string.
1838 String encodedUrlString;
1840 // Sanitize the search input and convert it to a search.
1842 encodedUrlString = URLEncoder.encode(launchingIntent.getStringExtra(SearchManager.QUERY), "UTF-8");
1843 } catch (UnsupportedEncodingException exception) {
1844 encodedUrlString = "";
1847 // Add the base search URL.
1848 formattedUrlString = searchURL + encodedUrlString;
1849 } else if (launchingIntentUriData != null){ // Check to see if the intent contains a new URL.
1850 // Set the formatted URL string.
1851 formattedUrlString = launchingIntentUriData.toString();
1854 // Load the website if not waiting for Orbot to connect.
1855 if (!waitingForOrbot) {
1856 loadUrl(formattedUrlString);
1861 protected void onNewIntent(Intent intent) {
1862 // Sets the new intent as the activity intent, so that any future `getIntent()`s pick up this one instead of creating a new activity.
1865 // Get the information from the intent.
1866 String intentAction = intent.getAction();
1867 Uri intentUriData = intent.getData();
1869 // If the intent action is a web search, perform the search.
1870 if ((intentAction != null) && intentAction.equals(Intent.ACTION_WEB_SEARCH)) {
1871 // Create an encoded URL string.
1872 String encodedUrlString;
1874 // Sanitize the search input and convert it to a search.
1876 encodedUrlString = URLEncoder.encode(intent.getStringExtra(SearchManager.QUERY), "UTF-8");
1877 } catch (UnsupportedEncodingException exception) {
1878 encodedUrlString = "";
1881 // Add the base search URL.
1882 formattedUrlString = searchURL + encodedUrlString;
1883 } else if (intentUriData != null){ // Check to see if the intent contains a new URL.
1884 // Set the formatted URL string.
1885 formattedUrlString = intentUriData.toString();
1889 loadUrl(formattedUrlString);
1891 // Close the navigation drawer if it is open.
1892 if (drawerLayout.isDrawerVisible(GravityCompat.START)) {
1893 drawerLayout.closeDrawer(GravityCompat.START);
1896 // Close the bookmarks drawer if it is open.
1897 if (drawerLayout.isDrawerVisible(GravityCompat.END)) {
1898 drawerLayout.closeDrawer(GravityCompat.END);
1901 // Clear the keyboard if displayed and remove the focus on the urlTextBar if it has it.
1902 mainWebView.requestFocus();
1906 public void onRestart() {
1907 // Run the default commands.
1910 // Make sure Orbot is running if Privacy Browser is proxying through Orbot.
1911 if (proxyThroughOrbot) {
1912 // Request Orbot to start. If Orbot is already running no hard will be caused by this request.
1913 Intent orbotIntent = new Intent("org.torproject.android.intent.action.START");
1915 // Send the intent to the Orbot package.
1916 orbotIntent.setPackage("org.torproject.android");
1919 sendBroadcast(orbotIntent);
1922 // Apply the app settings if returning from the Settings activity..
1923 if (reapplyAppSettingsOnRestart) {
1924 // Apply the app settings.
1927 // Reload the webpage if displaying of images has been disabled in the Settings activity.
1928 if (reloadOnRestart) {
1929 // Reload `mainWebView`.
1930 mainWebView.reload();
1932 // Reset `reloadOnRestartBoolean`.
1933 reloadOnRestart = false;
1936 // Reset the return from settings flag.
1937 reapplyAppSettingsOnRestart = false;
1940 // Apply the domain settings if returning from the Domains activity.
1941 if (reapplyDomainSettingsOnRestart) {
1942 // Reapply the domain settings.
1943 applyDomainSettings(formattedUrlString, false, true);
1945 // Reset `reapplyDomainSettingsOnRestart`.
1946 reapplyDomainSettingsOnRestart = false;
1949 // Load the URL on restart to apply changes to night mode.
1950 if (loadUrlOnRestart) {
1951 // Load the current `formattedUrlString`.
1952 loadUrl(formattedUrlString);
1954 // Reset `loadUrlOnRestart.
1955 loadUrlOnRestart = false;
1958 // Update the bookmarks drawer if returning from the Bookmarks activity.
1959 if (restartFromBookmarksActivity) {
1960 // Close the bookmarks drawer.
1961 drawerLayout.closeDrawer(GravityCompat.END);
1963 // Reload the bookmarks drawer.
1964 loadBookmarksFolder();
1966 // Reset `restartFromBookmarksActivity`.
1967 restartFromBookmarksActivity = false;
1970 // Update the privacy icon. `true` runs `invalidateOptionsMenu` as the last step. This can be important if the screen was rotated.
1971 updatePrivacyIcons(true);
1974 // `onResume()` runs after `onStart()`, which runs after `onCreate()` and `onRestart()`.
1976 public void onResume() {
1977 // Run the default commands.
1980 // Resume JavaScript (if enabled).
1981 mainWebView.resumeTimers();
1983 // Resume `mainWebView`.
1984 mainWebView.onResume();
1986 // Resume the adView for the free flavor.
1987 if (BuildConfig.FLAVOR.contentEquals("free")) {
1989 AdHelper.resumeAd(findViewById(R.id.adview));
1992 // Display a message to the user if waiting for Orbot.
1993 if (waitingForOrbot && !orbotStatus.equals("ON")) {
1994 // Disable the wide view port so that the waiting for Orbot text is displayed correctly.
1995 mainWebView.getSettings().setUseWideViewPort(false);
1997 // Load a waiting page. `null` specifies no encoding, which defaults to ASCII.
1998 mainWebView.loadData(waitingForOrbotHtmlString, "text/html", null);
2001 if (displayingFullScreenVideo) {
2002 // Remove the translucent overlays.
2003 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
2005 // Remove the translucent status bar overlay on the `Drawer Layout`, which is special and needs its own command.
2006 drawerLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
2008 /* SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
2009 * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
2010 * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
2012 rootCoordinatorLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
2017 public void onPause() {
2018 // Run the default commands.
2021 // Pause `mainWebView`.
2022 mainWebView.onPause();
2024 // Stop all JavaScript.
2025 mainWebView.pauseTimers();
2027 // Pause the ad or it will continue to consume resources in the background on the free flavor.
2028 if (BuildConfig.FLAVOR.contentEquals("free")) {
2030 AdHelper.pauseAd(findViewById(R.id.adview));
2035 public void onDestroy() {
2036 // Unregister the Orbot status broadcast receiver.
2037 this.unregisterReceiver(orbotStatusBroadcastReceiver);
2039 // Close the bookmarks cursor and database.
2040 bookmarksCursor.close();
2041 bookmarksDatabaseHelper.close();
2043 // Run the default commands.
2048 public boolean onCreateOptionsMenu(Menu menu) {
2049 // Inflate the menu. This adds items to the action bar if it is present.
2050 getMenuInflater().inflate(R.menu.webview_options_menu, menu);
2052 // Set mainMenu so it can be used by `onOptionsItemSelected()` and `updatePrivacyIcons`.
2055 // Set the initial status of the privacy icons. `false` does not call `invalidateOptionsMenu` as the last step.
2056 updatePrivacyIcons(false);
2058 // Get handles for the menu items.
2059 MenuItem toggleFirstPartyCookiesMenuItem = menu.findItem(R.id.toggle_first_party_cookies);
2060 MenuItem toggleThirdPartyCookiesMenuItem = menu.findItem(R.id.toggle_third_party_cookies);
2061 MenuItem toggleDomStorageMenuItem = menu.findItem(R.id.toggle_dom_storage);
2062 MenuItem toggleSaveFormDataMenuItem = menu.findItem(R.id.toggle_save_form_data); // Form data can be removed once the minimum API >= 26.
2063 MenuItem clearFormDataMenuItem = menu.findItem(R.id.clear_form_data); // Form data can be removed once the minimum API >= 26.
2064 refreshMenuItem = menu.findItem(R.id.refresh);
2065 blocklistsMenuItem = menu.findItem(R.id.blocklists);
2066 easyListMenuItem = menu.findItem(R.id.easylist);
2067 easyPrivacyMenuItem = menu.findItem(R.id.easyprivacy);
2068 fanboysAnnoyanceListMenuItem = menu.findItem(R.id.fanboys_annoyance_list);
2069 fanboysSocialBlockingListMenuItem = menu.findItem(R.id.fanboys_social_blocking_list);
2070 ultraPrivacyMenuItem = menu.findItem(R.id.ultraprivacy);
2071 blockAllThirdPartyRequestsMenuItem = menu.findItem(R.id.block_all_third_party_requests);
2072 MenuItem adConsentMenuItem = menu.findItem(R.id.ad_consent);
2074 // Only display third-party cookies if API >= 21
2075 toggleThirdPartyCookiesMenuItem.setVisible(Build.VERSION.SDK_INT >= 21);
2077 // Only display the form data menu items if the API < 26.
2078 toggleSaveFormDataMenuItem.setVisible(Build.VERSION.SDK_INT < 26);
2079 clearFormDataMenuItem.setVisible(Build.VERSION.SDK_INT < 26);
2081 // Only show Ad Consent if this is the free flavor.
2082 adConsentMenuItem.setVisible(BuildConfig.FLAVOR.contentEquals("free"));
2084 // Get the shared preference values.
2085 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
2087 // Get the status of the additional AppBar icons.
2088 displayAdditionalAppBarIcons = sharedPreferences.getBoolean("display_additional_app_bar_icons", false);
2090 // 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.
2091 if (displayAdditionalAppBarIcons) {
2092 toggleFirstPartyCookiesMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
2093 toggleDomStorageMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
2094 refreshMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
2095 } else { //Do not display the additional icons.
2096 toggleFirstPartyCookiesMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
2097 toggleDomStorageMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
2098 refreshMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
2101 // Replace Refresh with Stop if a URL is already loading.
2104 refreshMenuItem.setTitle(R.string.stop);
2106 // If the icon is displayed in the AppBar, set it according to the theme.
2107 if (displayAdditionalAppBarIcons) {
2109 refreshMenuItem.setIcon(R.drawable.close_dark);
2111 refreshMenuItem.setIcon(R.drawable.close_light);
2120 public boolean onPrepareOptionsMenu(Menu menu) {
2121 // Get handles for the menu items.
2122 MenuItem addOrEditDomain = menu.findItem(R.id.add_or_edit_domain);
2123 MenuItem toggleFirstPartyCookiesMenuItem = menu.findItem(R.id.toggle_first_party_cookies);
2124 MenuItem toggleThirdPartyCookiesMenuItem = menu.findItem(R.id.toggle_third_party_cookies);
2125 MenuItem toggleDomStorageMenuItem = menu.findItem(R.id.toggle_dom_storage);
2126 MenuItem toggleSaveFormDataMenuItem = menu.findItem(R.id.toggle_save_form_data); // Form data can be removed once the minimum API >= 26.
2127 MenuItem clearDataMenuItem = menu.findItem(R.id.clear_data);
2128 MenuItem clearCookiesMenuItem = menu.findItem(R.id.clear_cookies);
2129 MenuItem clearDOMStorageMenuItem = menu.findItem(R.id.clear_dom_storage);
2130 MenuItem clearFormDataMenuItem = menu.findItem(R.id.clear_form_data); // Form data can be removed once the minimum API >= 26.
2131 MenuItem fontSizeMenuItem = menu.findItem(R.id.font_size);
2132 MenuItem swipeToRefreshMenuItem = menu.findItem(R.id.swipe_to_refresh);
2133 MenuItem displayImagesMenuItem = menu.findItem(R.id.display_images);
2134 MenuItem nightModeMenuItem = menu.findItem(R.id.night_mode);
2135 MenuItem proxyThroughOrbotMenuItem = menu.findItem(R.id.proxy_through_orbot);
2137 // Set the text for the domain menu item.
2138 if (domainSettingsApplied) {
2139 addOrEditDomain.setTitle(R.string.edit_domain_settings);
2141 addOrEditDomain.setTitle(R.string.add_domain_settings);
2144 // Set the status of the menu item checkboxes.
2145 toggleFirstPartyCookiesMenuItem.setChecked(firstPartyCookiesEnabled);
2146 toggleThirdPartyCookiesMenuItem.setChecked(thirdPartyCookiesEnabled);
2147 toggleDomStorageMenuItem.setChecked(domStorageEnabled);
2148 toggleSaveFormDataMenuItem.setChecked(saveFormDataEnabled); // Form data can be removed once the minimum API >= 26.
2149 easyListMenuItem.setChecked(easyListEnabled);
2150 easyPrivacyMenuItem.setChecked(easyPrivacyEnabled);
2151 fanboysAnnoyanceListMenuItem.setChecked(fanboysAnnoyanceListEnabled);
2152 fanboysSocialBlockingListMenuItem.setChecked(fanboysSocialBlockingListEnabled);
2153 ultraPrivacyMenuItem.setChecked(ultraPrivacyEnabled);
2154 blockAllThirdPartyRequestsMenuItem.setChecked(blockAllThirdPartyRequests);
2155 swipeToRefreshMenuItem.setChecked(swipeRefreshLayout.isEnabled());
2156 displayImagesMenuItem.setChecked(mainWebView.getSettings().getLoadsImagesAutomatically());
2157 nightModeMenuItem.setChecked(nightMode);
2158 proxyThroughOrbotMenuItem.setChecked(proxyThroughOrbot);
2160 // Enable third-party cookies if first-party cookies are enabled.
2161 toggleThirdPartyCookiesMenuItem.setEnabled(firstPartyCookiesEnabled);
2163 // Enable DOM Storage if JavaScript is enabled.
2164 toggleDomStorageMenuItem.setEnabled(javaScriptEnabled);
2166 // Enable Clear Cookies if there are any.
2167 clearCookiesMenuItem.setEnabled(cookieManager.hasCookies());
2169 // Get a count of the number of files in the Local Storage directory.
2170 File localStorageDirectory = new File (privateDataDirectoryString + "/app_webview/Local Storage/");
2171 int localStorageDirectoryNumberOfFiles = 0;
2172 if (localStorageDirectory.exists()) {
2173 localStorageDirectoryNumberOfFiles = localStorageDirectory.list().length;
2176 // Get a count of the number of files in the IndexedDB directory.
2177 File indexedDBDirectory = new File (privateDataDirectoryString + "/app_webview/IndexedDB");
2178 int indexedDBDirectoryNumberOfFiles = 0;
2179 if (indexedDBDirectory.exists()) {
2180 indexedDBDirectoryNumberOfFiles = indexedDBDirectory.list().length;
2183 // Enable Clear DOM Storage if there is any.
2184 clearDOMStorageMenuItem.setEnabled(localStorageDirectoryNumberOfFiles > 0 || indexedDBDirectoryNumberOfFiles > 0);
2186 // Enable Clear Form Data is there is any. This can be removed once the minimum API >= 26.
2187 if (Build.VERSION.SDK_INT < 26) {
2188 WebViewDatabase mainWebViewDatabase = WebViewDatabase.getInstance(this);
2189 clearFormDataMenuItem.setEnabled(mainWebViewDatabase.hasFormData());
2191 // Disable clear form data because it is not supported on current version of Android.
2192 clearFormDataMenuItem.setEnabled(false);
2195 // Enable Clear Data if any of the submenu items are enabled.
2196 clearDataMenuItem.setEnabled(clearCookiesMenuItem.isEnabled() || clearDOMStorageMenuItem.isEnabled() || clearFormDataMenuItem.isEnabled());
2198 // Disable Fanboy's Social Blocking List if Fanboy's Annoyance List is checked.
2199 fanboysSocialBlockingListMenuItem.setEnabled(!fanboysAnnoyanceListEnabled);
2201 // Initialize the display names for the blocklists with the number of blocked requests.
2202 blocklistsMenuItem.setTitle(getString(R.string.blocklists) + " - " + blockedRequests);
2203 easyListMenuItem.setTitle(easyListBlockedRequests + " - " + getString(R.string.easylist));
2204 easyPrivacyMenuItem.setTitle(easyPrivacyBlockedRequests + " - " + getString(R.string.easyprivacy));
2205 fanboysAnnoyanceListMenuItem.setTitle(fanboysAnnoyanceListBlockedRequests + " - " + getString(R.string.fanboys_annoyance_list));
2206 fanboysSocialBlockingListMenuItem.setTitle(fanboysSocialBlockingListBlockedRequests + " - " + getString(R.string.fanboys_social_blocking_list));
2207 ultraPrivacyMenuItem.setTitle(ultraPrivacyBlockedRequests + " - " + getString(R.string.ultraprivacy));
2208 blockAllThirdPartyRequestsMenuItem.setTitle(thirdPartyBlockedRequests + " - " + getString(R.string.block_all_third_party_requests));
2210 // Get the current user agent.
2211 String currentUserAgent = mainWebView.getSettings().getUserAgentString();
2213 // Select the current user agent menu item. A switch statement cannot be used because the user agents are not compile time constants.
2214 if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[0])) { // Privacy Browser.
2215 menu.findItem(R.id.user_agent_privacy_browser).setChecked(true);
2216 } else if (currentUserAgent.equals(webViewDefaultUserAgent)) { // WebView Default.
2217 menu.findItem(R.id.user_agent_webview_default).setChecked(true);
2218 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[2])) { // Firefox on Android.
2219 menu.findItem(R.id.user_agent_firefox_on_android).setChecked(true);
2220 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[3])) { // Chrome on Android.
2221 menu.findItem(R.id.user_agent_chrome_on_android).setChecked(true);
2222 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[4])) { // Safari on iOS.
2223 menu.findItem(R.id.user_agent_safari_on_ios).setChecked(true);
2224 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[5])) { // Firefox on Linux.
2225 menu.findItem(R.id.user_agent_firefox_on_linux).setChecked(true);
2226 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[6])) { // Chromium on Linux.
2227 menu.findItem(R.id.user_agent_chromium_on_linux).setChecked(true);
2228 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[7])) { // Firefox on Windows.
2229 menu.findItem(R.id.user_agent_firefox_on_windows).setChecked(true);
2230 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[8])) { // Chrome on Windows.
2231 menu.findItem(R.id.user_agent_chrome_on_windows).setChecked(true);
2232 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[9])) { // Edge on Windows.
2233 menu.findItem(R.id.user_agent_edge_on_windows).setChecked(true);
2234 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[10])) { // Internet Explorer on Windows.
2235 menu.findItem(R.id.user_agent_internet_explorer_on_windows).setChecked(true);
2236 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[11])) { // Safari on macOS.
2237 menu.findItem(R.id.user_agent_safari_on_macos).setChecked(true);
2238 } else { // Custom user agent.
2239 menu.findItem(R.id.user_agent_custom).setChecked(true);
2242 // Initialize font size variables.
2243 int fontSize = mainWebView.getSettings().getTextZoom();
2244 String fontSizeTitle;
2245 MenuItem selectedFontSizeMenuItem;
2247 // Prepare the font size title and current size menu item.
2250 fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.twenty_five_percent);
2251 selectedFontSizeMenuItem = menu.findItem(R.id.font_size_twenty_five_percent);
2255 fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.fifty_percent);
2256 selectedFontSizeMenuItem = menu.findItem(R.id.font_size_fifty_percent);
2260 fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.seventy_five_percent);
2261 selectedFontSizeMenuItem = menu.findItem(R.id.font_size_seventy_five_percent);
2265 fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.one_hundred_percent);
2266 selectedFontSizeMenuItem = menu.findItem(R.id.font_size_one_hundred_percent);
2270 fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.one_hundred_twenty_five_percent);
2271 selectedFontSizeMenuItem = menu.findItem(R.id.font_size_one_hundred_twenty_five_percent);
2275 fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.one_hundred_fifty_percent);
2276 selectedFontSizeMenuItem = menu.findItem(R.id.font_size_one_hundred_fifty_percent);
2280 fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.one_hundred_seventy_five_percent);
2281 selectedFontSizeMenuItem = menu.findItem(R.id.font_size_one_hundred_seventy_five_percent);
2285 fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.two_hundred_percent);
2286 selectedFontSizeMenuItem = menu.findItem(R.id.font_size_two_hundred_percent);
2290 fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.one_hundred_percent);
2291 selectedFontSizeMenuItem = menu.findItem(R.id.font_size_one_hundred_percent);
2295 // Set the font size title and select the current size menu item.
2296 fontSizeMenuItem.setTitle(fontSizeTitle);
2297 selectedFontSizeMenuItem.setChecked(true);
2299 // Run all the other default commands.
2300 super.onPrepareOptionsMenu(menu);
2302 // Display the menu.
2307 // Remove Android Studio's warning about the dangers of using SetJavaScriptEnabled.
2308 @SuppressLint("SetJavaScriptEnabled")
2309 // removeAllCookies is deprecated, but it is required for API < 21.
2310 @SuppressWarnings("deprecation")
2311 public boolean onOptionsItemSelected(MenuItem menuItem) {
2312 // Get the selected menu item ID.
2313 int menuItemId = menuItem.getItemId();
2315 // Run the commands that correlate to the selected menu item.
2316 switch (menuItemId) {
2317 case R.id.toggle_javascript:
2318 // Switch the status of javaScriptEnabled.
2319 javaScriptEnabled = !javaScriptEnabled;
2321 // Apply the new JavaScript status.
2322 mainWebView.getSettings().setJavaScriptEnabled(javaScriptEnabled);
2324 // Update the privacy icon. `true` runs `invalidateOptionsMenu` as the last step.
2325 updatePrivacyIcons(true);
2327 // Display a `Snackbar`.
2328 if (javaScriptEnabled) { // JavaScrip is enabled.
2329 Snackbar.make(findViewById(R.id.main_webview), R.string.javascript_enabled, Snackbar.LENGTH_SHORT).show();
2330 } else if (firstPartyCookiesEnabled) { // JavaScript is disabled, but first-party cookies are enabled.
2331 Snackbar.make(findViewById(R.id.main_webview), R.string.javascript_disabled, Snackbar.LENGTH_SHORT).show();
2332 } else { // Privacy mode.
2333 Snackbar.make(findViewById(R.id.main_webview), R.string.privacy_mode, Snackbar.LENGTH_SHORT).show();
2336 // Reload the WebView.
2337 mainWebView.reload();
2340 case R.id.add_or_edit_domain:
2341 if (domainSettingsApplied) { // Edit the current domain settings.
2342 // Reapply the domain settings on returning to `MainWebViewActivity`.
2343 reapplyDomainSettingsOnRestart = true;
2344 currentDomainName = "";
2346 // Create an intent to launch the domains activity.
2347 Intent domainsIntent = new Intent(this, DomainsActivity.class);
2349 // Put extra information instructing the domains activity to directly load the current domain and close on back instead of returning to the domains list.
2350 domainsIntent.putExtra("loadDomain", domainSettingsDatabaseId);
2351 domainsIntent.putExtra("closeOnBack", true);
2354 startActivity(domainsIntent);
2355 } else { // Add a new domain.
2356 // Apply the new domain settings on returning to `MainWebViewActivity`.
2357 reapplyDomainSettingsOnRestart = true;
2358 currentDomainName = "";
2360 // Get the current domain
2361 Uri currentUri = Uri.parse(formattedUrlString);
2362 String currentDomain = currentUri.getHost();
2364 // Initialize the database handler. The `0` specifies the database version, but that is ignored and set instead using a constant in `DomainsDatabaseHelper`.
2365 DomainsDatabaseHelper domainsDatabaseHelper = new DomainsDatabaseHelper(this, null, null, 0);
2367 // Create the domain and store the database ID.
2368 int newDomainDatabaseId = domainsDatabaseHelper.addDomain(currentDomain);
2370 // Create an intent to launch the domains activity.
2371 Intent domainsIntent = new Intent(this, DomainsActivity.class);
2373 // Put extra information instructing the domains activity to directly load the new domain and close on back instead of returning to the domains list.
2374 domainsIntent.putExtra("loadDomain", newDomainDatabaseId);
2375 domainsIntent.putExtra("closeOnBack", true);
2378 startActivity(domainsIntent);
2382 case R.id.toggle_first_party_cookies:
2383 // Switch the status of firstPartyCookiesEnabled.
2384 firstPartyCookiesEnabled = !firstPartyCookiesEnabled;
2386 // Update the menu checkbox.
2387 menuItem.setChecked(firstPartyCookiesEnabled);
2389 // Apply the new cookie status.
2390 cookieManager.setAcceptCookie(firstPartyCookiesEnabled);
2392 // Update the privacy icon. `true` runs `invalidateOptionsMenu` as the last step.
2393 updatePrivacyIcons(true);
2395 // Display a `Snackbar`.
2396 if (firstPartyCookiesEnabled) { // First-party cookies are enabled.
2397 Snackbar.make(findViewById(R.id.main_webview), R.string.first_party_cookies_enabled, Snackbar.LENGTH_SHORT).show();
2398 } else if (javaScriptEnabled) { // JavaScript is still enabled.
2399 Snackbar.make(findViewById(R.id.main_webview), R.string.first_party_cookies_disabled, Snackbar.LENGTH_SHORT).show();
2400 } else { // Privacy mode.
2401 Snackbar.make(findViewById(R.id.main_webview), R.string.privacy_mode, Snackbar.LENGTH_SHORT).show();
2404 // Reload the WebView.
2405 mainWebView.reload();
2408 case R.id.toggle_third_party_cookies:
2409 if (Build.VERSION.SDK_INT >= 21) {
2410 // Switch the status of thirdPartyCookiesEnabled.
2411 thirdPartyCookiesEnabled = !thirdPartyCookiesEnabled;
2413 // Update the menu checkbox.
2414 menuItem.setChecked(thirdPartyCookiesEnabled);
2416 // Apply the new cookie status.
2417 cookieManager.setAcceptThirdPartyCookies(mainWebView, thirdPartyCookiesEnabled);
2419 // Display a `Snackbar`.
2420 if (thirdPartyCookiesEnabled) {
2421 Snackbar.make(findViewById(R.id.main_webview), R.string.third_party_cookies_enabled, Snackbar.LENGTH_SHORT).show();
2423 Snackbar.make(findViewById(R.id.main_webview), R.string.third_party_cookies_disabled, Snackbar.LENGTH_SHORT).show();
2426 // Reload the WebView.
2427 mainWebView.reload();
2428 } // Else do nothing because SDK < 21.
2431 case R.id.toggle_dom_storage:
2432 // Switch the status of domStorageEnabled.
2433 domStorageEnabled = !domStorageEnabled;
2435 // Update the menu checkbox.
2436 menuItem.setChecked(domStorageEnabled);
2438 // Apply the new DOM Storage status.
2439 mainWebView.getSettings().setDomStorageEnabled(domStorageEnabled);
2441 // Update the privacy icon. `true` runs `invalidateOptionsMenu` as the last step.
2442 updatePrivacyIcons(true);
2444 // Display a `Snackbar`.
2445 if (domStorageEnabled) {
2446 Snackbar.make(findViewById(R.id.main_webview), R.string.dom_storage_enabled, Snackbar.LENGTH_SHORT).show();
2448 Snackbar.make(findViewById(R.id.main_webview), R.string.dom_storage_disabled, Snackbar.LENGTH_SHORT).show();
2451 // Reload the WebView.
2452 mainWebView.reload();
2455 // Form data can be removed once the minimum API >= 26.
2456 case R.id.toggle_save_form_data:
2457 // Switch the status of saveFormDataEnabled.
2458 saveFormDataEnabled = !saveFormDataEnabled;
2460 // Update the menu checkbox.
2461 menuItem.setChecked(saveFormDataEnabled);
2463 // Apply the new form data status.
2464 mainWebView.getSettings().setSaveFormData(saveFormDataEnabled);
2466 // Display a `Snackbar`.
2467 if (saveFormDataEnabled) {
2468 Snackbar.make(findViewById(R.id.main_webview), R.string.form_data_enabled, Snackbar.LENGTH_SHORT).show();
2470 Snackbar.make(findViewById(R.id.main_webview), R.string.form_data_disabled, Snackbar.LENGTH_SHORT).show();
2473 // Update the privacy icon. `true` runs `invalidateOptionsMenu` as the last step.
2474 updatePrivacyIcons(true);
2476 // Reload the WebView.
2477 mainWebView.reload();
2480 case R.id.clear_cookies:
2481 Snackbar.make(findViewById(R.id.main_webview), R.string.cookies_deleted, Snackbar.LENGTH_LONG)
2482 .setAction(R.string.undo, v -> {
2483 // Do nothing because everything will be handled by `onDismissed()` below.
2485 .addCallback(new Snackbar.Callback() {
2487 public void onDismissed(Snackbar snackbar, int event) {
2489 // The user pushed the `Undo` button.
2490 case Snackbar.Callback.DISMISS_EVENT_ACTION:
2494 // The `Snackbar` was dismissed without the `Undo` button being pushed.
2496 // `cookieManager.removeAllCookie()` varies by SDK.
2497 if (Build.VERSION.SDK_INT < 21) {
2498 cookieManager.removeAllCookie();
2500 // `null` indicates no callback.
2501 cookieManager.removeAllCookies(null);
2509 case R.id.clear_dom_storage:
2510 Snackbar.make(findViewById(R.id.main_webview), R.string.dom_storage_deleted, Snackbar.LENGTH_LONG)
2511 .setAction(R.string.undo, v -> {
2512 // Do nothing because everything will be handled by `onDismissed()` below.
2514 .addCallback(new Snackbar.Callback() {
2516 public void onDismissed(Snackbar snackbar, int event) {
2518 // The user pushed the `Undo` button.
2519 case Snackbar.Callback.DISMISS_EVENT_ACTION:
2523 // The `Snackbar` was dismissed without the `Undo` button being pushed.
2525 // Delete the DOM Storage.
2526 WebStorage webStorage = WebStorage.getInstance();
2527 webStorage.deleteAllData();
2529 // Initialize a handler to manually delete the DOM storage files and directories.
2530 Handler deleteDomStorageHandler = new Handler();
2532 // Setup a runnable to manually delete the DOM storage files and directories.
2533 Runnable deleteDomStorageRunnable = () -> {
2535 // A string array must be used because the directory contains a space and `Runtime.exec` will otherwise not escape the string correctly.
2536 Process deleteLocalStorageProcess = privacyBrowserRuntime.exec(new String[]{"rm", "-rf", privateDataDirectoryString + "/app_webview/Local Storage/"});
2538 // Multiple commands must be used because `Runtime.exec()` does not like `*`.
2539 Process deleteIndexProcess = privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/IndexedDB");
2540 Process deleteQuotaManagerProcess = privacyBrowserRuntime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager");
2541 Process deleteQuotaManagerJournalProcess = privacyBrowserRuntime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager-journal");
2542 Process deleteDatabasesProcess = privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/databases");
2544 // Wait for the processes to finish.
2545 deleteLocalStorageProcess.waitFor();
2546 deleteIndexProcess.waitFor();
2547 deleteQuotaManagerProcess.waitFor();
2548 deleteQuotaManagerJournalProcess.waitFor();
2549 deleteDatabasesProcess.waitFor();
2550 } catch (Exception exception) {
2551 // Do nothing if an error is thrown.
2555 // Manually delete the DOM storage files after 200 milliseconds.
2556 deleteDomStorageHandler.postDelayed(deleteDomStorageRunnable, 200);
2563 // Form data can be remove once the minimum API >= 26.
2564 case R.id.clear_form_data:
2565 Snackbar.make(findViewById(R.id.main_webview), R.string.form_data_deleted, Snackbar.LENGTH_LONG)
2566 .setAction(R.string.undo, v -> {
2567 // Do nothing because everything will be handled by `onDismissed()` below.
2569 .addCallback(new Snackbar.Callback() {
2571 public void onDismissed(Snackbar snackbar, int event) {
2573 // The user pushed the `Undo` button.
2574 case Snackbar.Callback.DISMISS_EVENT_ACTION:
2578 // The `Snackbar` was dismissed without the `Undo` button being pushed.
2580 // Delete the form data.
2581 WebViewDatabase mainWebViewDatabase = WebViewDatabase.getInstance(getApplicationContext());
2582 mainWebViewDatabase.clearFormData();
2590 // Toggle the EasyList status.
2591 easyListEnabled = !easyListEnabled;
2593 // Update the menu checkbox.
2594 menuItem.setChecked(easyListEnabled);
2596 // Reload the main WebView.
2597 mainWebView.reload();
2600 case R.id.easyprivacy:
2601 // Toggle the EasyPrivacy status.
2602 easyPrivacyEnabled = !easyPrivacyEnabled;
2604 // Update the menu checkbox.
2605 menuItem.setChecked(easyPrivacyEnabled);
2607 // Reload the main WebView.
2608 mainWebView.reload();
2611 case R.id.fanboys_annoyance_list:
2612 // Toggle Fanboy's Annoyance List status.
2613 fanboysAnnoyanceListEnabled = !fanboysAnnoyanceListEnabled;
2615 // Update the menu checkbox.
2616 menuItem.setChecked(fanboysAnnoyanceListEnabled);
2618 // Update the staus of Fanboy's Social Blocking List.
2619 MenuItem fanboysSocialBlockingListMenuItem = mainMenu.findItem(R.id.fanboys_social_blocking_list);
2620 fanboysSocialBlockingListMenuItem.setEnabled(!fanboysAnnoyanceListEnabled);
2622 // Reload the main WebView.
2623 mainWebView.reload();
2626 case R.id.fanboys_social_blocking_list:
2627 // Toggle Fanboy's Social Blocking List status.
2628 fanboysSocialBlockingListEnabled = !fanboysSocialBlockingListEnabled;
2630 // Update the menu checkbox.
2631 menuItem.setChecked(fanboysSocialBlockingListEnabled);
2633 // Reload the main WebView.
2634 mainWebView.reload();
2637 case R.id.ultraprivacy:
2638 // Toggle the UltraPrivacy status.
2639 ultraPrivacyEnabled = !ultraPrivacyEnabled;
2641 // Update the menu checkbox.
2642 menuItem.setChecked(ultraPrivacyEnabled);
2644 // Reload the main WebView.
2645 mainWebView.reload();
2648 case R.id.block_all_third_party_requests:
2649 //Toggle the third-party requests blocker status.
2650 blockAllThirdPartyRequests = !blockAllThirdPartyRequests;
2652 // Update the menu checkbox.
2653 menuItem.setChecked(blockAllThirdPartyRequests);
2655 // Reload the main WebView.
2656 mainWebView.reload();
2659 case R.id.user_agent_privacy_browser:
2660 // Update the user agent.
2661 mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[0]);
2663 // Reload the WebView.
2664 mainWebView.reload();
2667 case R.id.user_agent_webview_default:
2668 // Update the user agent.
2669 mainWebView.getSettings().setUserAgentString("");
2671 // Reload the WebView.
2672 mainWebView.reload();
2675 case R.id.user_agent_firefox_on_android:
2676 // Update the user agent.
2677 mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[2]);
2679 // Reload the WebView.
2680 mainWebView.reload();
2683 case R.id.user_agent_chrome_on_android:
2684 // Update the user agent.
2685 mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[3]);
2687 // Reload the WebView.
2688 mainWebView.reload();
2691 case R.id.user_agent_safari_on_ios:
2692 // Update the user agent.
2693 mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[4]);
2695 // Reload the WebView.
2696 mainWebView.reload();
2699 case R.id.user_agent_firefox_on_linux:
2700 // Update the user agent.
2701 mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[5]);
2703 // Reload the WebView.
2704 mainWebView.reload();
2707 case R.id.user_agent_chromium_on_linux:
2708 // Update the user agent.
2709 mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[6]);
2711 // Reload the WebView.
2712 mainWebView.reload();
2715 case R.id.user_agent_firefox_on_windows:
2716 // Update the user agent.
2717 mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[7]);
2719 // Reload the WebView.
2720 mainWebView.reload();
2723 case R.id.user_agent_chrome_on_windows:
2724 // Update the user agent.
2725 mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[8]);
2727 // Reload the WebView.
2728 mainWebView.reload();
2731 case R.id.user_agent_edge_on_windows:
2732 // Update the user agent.
2733 mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[9]);
2735 // Reload the WebView.
2736 mainWebView.reload();
2739 case R.id.user_agent_internet_explorer_on_windows:
2740 // Update the user agent.
2741 mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[10]);
2743 // Reload the WebView.
2744 mainWebView.reload();
2747 case R.id.user_agent_safari_on_macos:
2748 // Update the user agent.
2749 mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[11]);
2751 // Reload the WebView.
2752 mainWebView.reload();
2755 case R.id.user_agent_custom:
2756 // Update the user agent.
2757 mainWebView.getSettings().setUserAgentString(defaultCustomUserAgentString);
2759 // Reload the WebView.
2760 mainWebView.reload();
2763 case R.id.font_size_twenty_five_percent:
2764 mainWebView.getSettings().setTextZoom(25);
2767 case R.id.font_size_fifty_percent:
2768 mainWebView.getSettings().setTextZoom(50);
2771 case R.id.font_size_seventy_five_percent:
2772 mainWebView.getSettings().setTextZoom(75);
2775 case R.id.font_size_one_hundred_percent:
2776 mainWebView.getSettings().setTextZoom(100);
2779 case R.id.font_size_one_hundred_twenty_five_percent:
2780 mainWebView.getSettings().setTextZoom(125);
2783 case R.id.font_size_one_hundred_fifty_percent:
2784 mainWebView.getSettings().setTextZoom(150);
2787 case R.id.font_size_one_hundred_seventy_five_percent:
2788 mainWebView.getSettings().setTextZoom(175);
2791 case R.id.font_size_two_hundred_percent:
2792 mainWebView.getSettings().setTextZoom(200);
2795 case R.id.swipe_to_refresh:
2796 // Toggle swipe to refresh.
2797 swipeRefreshLayout.setEnabled(!swipeRefreshLayout.isEnabled());
2800 case R.id.display_images:
2801 if (mainWebView.getSettings().getLoadsImagesAutomatically()) { // Images are currently loaded automatically.
2802 mainWebView.getSettings().setLoadsImagesAutomatically(false);
2803 mainWebView.reload();
2804 } else { // Images are not currently loaded automatically.
2805 mainWebView.getSettings().setLoadsImagesAutomatically(true);
2809 case R.id.night_mode:
2810 // Toggle night mode.
2811 nightMode = !nightMode;
2813 // Enable or disable JavaScript according to night mode, the global preference, and any domain settings.
2814 if (nightMode) { // Night mode is enabled. Enable JavaScript.
2815 // Update the global variable.
2816 javaScriptEnabled = true;
2817 } else if (domainSettingsApplied) { // Night mode is disabled and domain settings are applied. Set JavaScript according to the domain settings.
2818 // Get the JavaScript preference that was stored the last time domain settings were loaded.
2819 javaScriptEnabled = domainSettingsJavaScriptEnabled;
2820 } else { // Night mode is disabled and domain settings are not applied. Set JavaScript according to the global preference.
2821 // Get a handle for the shared preference.
2822 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
2824 // Get the JavaScript preference.
2825 javaScriptEnabled = sharedPreferences.getBoolean("javascript", false);
2828 // Apply the JavaScript setting to the WebView.
2829 mainWebView.getSettings().setJavaScriptEnabled(javaScriptEnabled);
2831 // Update the privacy icons.
2832 updatePrivacyIcons(false);
2834 // Reload the website.
2835 mainWebView.reload();
2838 case R.id.find_on_page:
2839 // Hide the URL app bar.
2840 supportAppBar.setVisibility(View.GONE);
2842 // Show the Find on Page `RelativeLayout`.
2843 findOnPageLinearLayout.setVisibility(View.VISIBLE);
2845 // Display the keyboard. We have to wait 200 ms before running the command to work around a bug in Android.
2846 // http://stackoverflow.com/questions/5520085/android-show-softkeyboard-with-showsoftinput-is-not-working
2847 findOnPageEditText.postDelayed(() -> {
2848 // Set the focus on `findOnPageEditText`.
2849 findOnPageEditText.requestFocus();
2851 // Display the keyboard. `0` sets no input flags.
2852 inputMethodManager.showSoftInput(findOnPageEditText, 0);
2856 case R.id.view_source:
2857 // Launch the View Source activity.
2858 Intent viewSourceIntent = new Intent(this, ViewSourceActivity.class);
2859 startActivity(viewSourceIntent);
2862 case R.id.share_url:
2863 // Setup the share string.
2864 String shareString = webViewTitle + " – " + urlTextBox.getText().toString();
2866 // Create the share intent.
2867 Intent shareIntent = new Intent(Intent.ACTION_SEND);
2868 shareIntent.putExtra(Intent.EXTRA_TEXT, shareString);
2869 shareIntent.setType("text/plain");
2872 startActivity(Intent.createChooser(shareIntent, getString(R.string.share_url)));
2876 // Get a `PrintManager` instance.
2877 PrintManager printManager = (PrintManager) getSystemService(Context.PRINT_SERVICE);
2879 // Convert `mainWebView` to `printDocumentAdapter`.
2880 PrintDocumentAdapter printDocumentAdapter = mainWebView.createPrintDocumentAdapter();
2882 // Remove the lint error below that `printManager` might be `null`.
2883 assert printManager != null;
2885 // Print the document. The print attributes are `null`.
2886 printManager.print(getString(R.string.privacy_browser_web_page), printDocumentAdapter, null);
2889 case R.id.open_with_app:
2890 openWithApp(formattedUrlString);
2893 case R.id.open_with_browser:
2894 openWithBrowser(formattedUrlString);
2897 case R.id.add_to_homescreen:
2898 // Show the alert dialog.
2899 AppCompatDialogFragment createHomeScreenShortcutDialogFragment = new CreateHomeScreenShortcutDialog();
2900 createHomeScreenShortcutDialogFragment.show(supportFragmentManager, getString(R.string.create_shortcut));
2902 //Everything else will be handled by the alert dialog and the associated listener below.
2905 case R.id.proxy_through_orbot:
2906 // Toggle the proxy through Orbot variable.
2907 proxyThroughOrbot = !proxyThroughOrbot;
2909 // Apply the proxy through Orbot settings.
2910 applyProxyThroughOrbot(true);
2914 if (menuItem.getTitle().equals(getString(R.string.refresh))) { // The refresh button was pushed.
2915 // Reload the WebView.
2916 mainWebView.reload();
2917 } else { // The stop button was pushed.
2918 // Stop the loading of the WebView.
2919 mainWebView.stopLoading();
2923 case R.id.ad_consent:
2924 // Display the ad consent dialog.
2925 DialogFragment adConsentDialogFragment = new AdConsentDialog();
2926 adConsentDialogFragment.show(getFragmentManager(), getString(R.string.ad_consent));
2930 // Don't consume the event.
2931 return super.onOptionsItemSelected(menuItem);
2935 // removeAllCookies is deprecated, but it is required for API < 21.
2936 @SuppressWarnings("deprecation")
2938 public boolean onNavigationItemSelected(@NonNull MenuItem menuItem) {
2939 int menuItemId = menuItem.getItemId();
2941 switch (menuItemId) {
2947 if (mainWebView.canGoBack()) {
2948 // Reset the formatted URL string so the page will load correctly if blocking of third-party requests is enabled.
2949 formattedUrlString = "";
2951 // Set `navigatingHistory` so that the domain settings are applied when the new URL is loaded.
2952 navigatingHistory = true;
2954 // Load the previous website in the history.
2955 mainWebView.goBack();
2960 if (mainWebView.canGoForward()) {
2961 // Reset the formatted URL string so the page will load correctly if blocking of third-party requests is enabled.
2962 formattedUrlString = "";
2964 // Set `navigatingHistory` so that the domain settings are applied when the new URL is loaded.
2965 navigatingHistory = true;
2967 // Load the next website in the history.
2968 mainWebView.goForward();
2973 // Get the `WebBackForwardList`.
2974 WebBackForwardList webBackForwardList = mainWebView.copyBackForwardList();
2976 // Show the `UrlHistoryDialog` `AlertDialog` and name this instance `R.string.history`. `this` is the `Context`.
2977 AppCompatDialogFragment urlHistoryDialogFragment = UrlHistoryDialog.loadBackForwardList(this, webBackForwardList);
2978 urlHistoryDialogFragment.show(supportFragmentManager, getString(R.string.history));
2982 // Launch the requests activity.
2983 Intent requestsIntent = new Intent(this, RequestsActivity.class);
2984 startActivity(requestsIntent);
2987 case R.id.downloads:
2988 // Launch the system Download Manager.
2989 Intent downloadManagerIntent = new Intent(DownloadManager.ACTION_VIEW_DOWNLOADS);
2991 // Launch as a new task so that Download Manager and Privacy Browser show as separate windows in the recent tasks list.
2992 downloadManagerIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
2994 startActivity(downloadManagerIntent);
2998 // Set the flag to reapply the domain settings on restart when returning from Domain Settings.
2999 reapplyDomainSettingsOnRestart = true;
3000 currentDomainName = "";
3002 // Launch the domains activity.
3003 Intent domainsIntent = new Intent(this, DomainsActivity.class);
3004 startActivity(domainsIntent);
3008 // Set the flag to reapply app settings on restart when returning from Settings.
3009 reapplyAppSettingsOnRestart = true;
3011 // Set the flag to reapply the domain settings on restart when returning from Settings.
3012 reapplyDomainSettingsOnRestart = true;
3013 currentDomainName = "";
3015 // Launch the settings activity.
3016 Intent settingsIntent = new Intent(this, SettingsActivity.class);
3017 startActivity(settingsIntent);
3020 case R.id.import_export:
3021 // Launch the import/export activity.
3022 Intent importExportIntent = new Intent (this, ImportExportActivity.class);
3023 startActivity(importExportIntent);
3027 // Launch the logcat activity.
3028 Intent logcatIntent = new Intent(this, LogcatActivity.class);
3029 startActivity(logcatIntent);
3033 // Launch `GuideActivity`.
3034 Intent guideIntent = new Intent(this, GuideActivity.class);
3035 startActivity(guideIntent);
3039 // Launch `AboutActivity`.
3040 Intent aboutIntent = new Intent(this, AboutActivity.class);
3041 startActivity(aboutIntent);
3044 case R.id.clear_and_exit:
3045 // Close the bookmarks cursor and database.
3046 bookmarksCursor.close();
3047 bookmarksDatabaseHelper.close();
3049 // Get a handle for the shared preferences.
3050 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
3052 // Get the status of the clear everything preference.
3053 boolean clearEverything = sharedPreferences.getBoolean("clear_everything", true);
3056 if (clearEverything || sharedPreferences.getBoolean("clear_cookies", true)) {
3057 // The command to remove cookies changed slightly in API 21.
3058 if (Build.VERSION.SDK_INT >= 21) {
3059 cookieManager.removeAllCookies(null);
3061 cookieManager.removeAllCookie();
3064 // Manually delete the cookies database, as `CookieManager` sometimes will not flush its changes to disk before `System.exit(0)` is run.
3066 // Two commands must be used because `Runtime.exec()` does not like `*`.
3067 Process deleteCookiesProcess = privacyBrowserRuntime.exec("rm -f " + privateDataDirectoryString + "/app_webview/Cookies");
3068 Process deleteCookiesJournalProcess = privacyBrowserRuntime.exec("rm -f " + privateDataDirectoryString + "/app_webview/Cookies-journal");
3070 // Wait until the processes have finished.
3071 deleteCookiesProcess.waitFor();
3072 deleteCookiesJournalProcess.waitFor();
3073 } catch (Exception exception) {
3074 // Do nothing if an error is thrown.
3078 // Clear DOM storage.
3079 if (clearEverything || sharedPreferences.getBoolean("clear_dom_storage", true)) {
3080 // Ask `WebStorage` to clear the DOM storage.
3081 WebStorage webStorage = WebStorage.getInstance();
3082 webStorage.deleteAllData();
3084 // Manually delete the DOM storage files and directories, as `WebStorage` sometimes will not flush its changes to disk before `System.exit(0)` is run.
3086 // A `String[]` must be used because the directory contains a space and `Runtime.exec` will otherwise not escape the string correctly.
3087 Process deleteLocalStorageProcess = privacyBrowserRuntime.exec(new String[] {"rm", "-rf", privateDataDirectoryString + "/app_webview/Local Storage/"});
3089 // Multiple commands must be used because `Runtime.exec()` does not like `*`.
3090 Process deleteIndexProcess = privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/IndexedDB");
3091 Process deleteQuotaManagerProcess = privacyBrowserRuntime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager");
3092 Process deleteQuotaManagerJournalProcess = privacyBrowserRuntime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager-journal");
3093 Process deleteDatabaseProcess = privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/databases");
3095 // Wait until the processes have finished.
3096 deleteLocalStorageProcess.waitFor();
3097 deleteIndexProcess.waitFor();
3098 deleteQuotaManagerProcess.waitFor();
3099 deleteQuotaManagerJournalProcess.waitFor();
3100 deleteDatabaseProcess.waitFor();
3101 } catch (Exception exception) {
3102 // Do nothing if an error is thrown.
3106 // Clear form data if the API < 26.
3107 if ((Build.VERSION.SDK_INT < 26) && (clearEverything || sharedPreferences.getBoolean("clear_form_data", true))) {
3108 WebViewDatabase webViewDatabase = WebViewDatabase.getInstance(this);
3109 webViewDatabase.clearFormData();
3111 // Manually delete the form data database, as `WebViewDatabase` sometimes will not flush its changes to disk before `System.exit(0)` is run.
3113 // A string array must be used because the database contains a space and `Runtime.exec` will not otherwise escape the string correctly.
3114 Process deleteWebDataProcess = privacyBrowserRuntime.exec(new String[] {"rm", "-f", privateDataDirectoryString + "/app_webview/Web Data"});
3115 Process deleteWebDataJournalProcess = privacyBrowserRuntime.exec(new String[] {"rm", "-f", privateDataDirectoryString + "/app_webview/Web Data-journal"});
3117 // Wait until the processes have finished.
3118 deleteWebDataProcess.waitFor();
3119 deleteWebDataJournalProcess.waitFor();
3120 } catch (Exception exception) {
3121 // Do nothing if an error is thrown.
3126 if (clearEverything || sharedPreferences.getBoolean("clear_cache", true)) {
3127 // `true` includes disk files.
3128 mainWebView.clearCache(true);
3130 // Manually delete the cache directories.
3132 // Delete the main cache directory.
3133 Process deleteCacheProcess = privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/cache");
3135 // Delete the secondary `Service Worker` cache directory.
3136 // A string array must be used because the directory contains a space and `Runtime.exec` will otherwise not escape the string correctly.
3137 Process deleteServiceWorkerProcess = privacyBrowserRuntime.exec(new String[] {"rm", "-rf", privateDataDirectoryString + "/app_webview/Service Worker/"});
3139 // Wait until the processes have finished.
3140 deleteCacheProcess.waitFor();
3141 deleteServiceWorkerProcess.waitFor();
3142 } catch (Exception exception) {
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 // Delete the folder.
3170 Process deleteAppWebviewProcess = privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/app_webview");
3172 // Wait until the process has finished.
3173 deleteAppWebviewProcess.waitFor();
3174 } catch (Exception exception) {
3175 // Do nothing if an error is thrown.
3179 // Close Privacy Browser. `finishAndRemoveTask` also removes Privacy Browser from the recent app list.
3180 if (Build.VERSION.SDK_INT >= 21) {
3181 finishAndRemoveTask();
3186 // Remove the terminated program from RAM. The status code is `0`.
3191 // Close the navigation drawer.
3192 drawerLayout.closeDrawer(GravityCompat.START);
3197 public void onPostCreate(Bundle savedInstanceState) {
3198 super.onPostCreate(savedInstanceState);
3200 // Sync the state of the DrawerToggle after onRestoreInstanceState has finished.
3201 drawerToggle.syncState();
3205 public void onConfigurationChanged(Configuration newConfig) {
3206 super.onConfigurationChanged(newConfig);
3208 // Get the status bar pixel size.
3209 int statusBarResourceId = getResources().getIdentifier("status_bar_height", "dimen", "android");
3210 int statusBarPixelSize = getResources().getDimensionPixelSize(statusBarResourceId);
3212 // Get the resource density.
3213 float screenDensity = getResources().getDisplayMetrics().density;
3215 // Recalculate the drawer header padding.
3216 drawerHeaderPaddingLeftAndRight = (int) (15 * screenDensity);
3217 drawerHeaderPaddingTop = statusBarPixelSize + (int) (4 * screenDensity);
3218 drawerHeaderPaddingBottom = (int) (8 * screenDensity);
3220 // Reload the ad for the free flavor if not in full screen mode.
3221 if (BuildConfig.FLAVOR.contentEquals("free") && !inFullScreenBrowsingMode) {
3222 // Reload the ad. The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations.
3223 AdHelper.loadAd(findViewById(R.id.adview), getApplicationContext(), getString(R.string.ad_unit_id));
3226 // `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:
3227 // https://code.google.com/p/android/issues/detail?id=20493#c8
3228 // ActivityCompat.invalidateOptionsMenu(this);
3232 public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.ContextMenuInfo menuInfo) {
3233 // Store the `HitTestResult`.
3234 final WebView.HitTestResult hitTestResult = mainWebView.getHitTestResult();
3237 final String imageUrl;
3238 final String linkUrl;
3240 // Get a handle for the `ClipboardManager`.
3241 final ClipboardManager clipboardManager = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
3243 // Remove the lint errors below that `clipboardManager` might be `null`.
3244 assert clipboardManager != null;
3246 switch (hitTestResult.getType()) {
3247 // `SRC_ANCHOR_TYPE` is a link.
3248 case WebView.HitTestResult.SRC_ANCHOR_TYPE:
3249 // Get the target URL.
3250 linkUrl = hitTestResult.getExtra();
3252 // Set the target URL as the title of the `ContextMenu`.
3253 menu.setHeaderTitle(linkUrl);
3255 // Add a Load URL entry.
3256 menu.add(R.string.load_url).setOnMenuItemClickListener((MenuItem item) -> {
3261 // Add a Copy URL entry.
3262 menu.add(R.string.copy_url).setOnMenuItemClickListener((MenuItem item) -> {
3263 // Save the link URL in a `ClipData`.
3264 ClipData srcAnchorTypeClipData = ClipData.newPlainText(getString(R.string.url), linkUrl);
3266 // Set the `ClipData` as the clipboard's primary clip.
3267 clipboardManager.setPrimaryClip(srcAnchorTypeClipData);
3271 // Add a Download URL entry.
3272 menu.add(R.string.download_url).setOnMenuItemClickListener((MenuItem item) -> {
3273 // Check if the download should be processed by an external app.
3274 if (downloadWithExternalApp) { // Download with an external app.
3275 openUrlWithExternalApp(linkUrl);
3276 } else { // Download with Android's download manager.
3277 // Check to see if the storage permission has already been granted.
3278 if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) { // The storage permission needs to be requested.
3279 // Store the variables for future use by `onRequestPermissionsResult()`.
3280 downloadUrl = linkUrl;
3281 downloadContentDisposition = "none";
3282 downloadContentLength = -1;
3284 // Show a dialog if the user has previously denied the permission.
3285 if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) { // Show a dialog explaining the request first.
3286 // Instantiate the download location permission alert dialog and set the download type to DOWNLOAD_FILE.
3287 DialogFragment downloadLocationPermissionDialogFragment = DownloadLocationPermissionDialog.downloadType(DownloadLocationPermissionDialog.DOWNLOAD_FILE);
3289 // Show the download location permission alert dialog. The permission will be requested when the the dialog is closed.
3290 downloadLocationPermissionDialogFragment.show(getFragmentManager(), getString(R.string.download_location));
3291 } else { // Show the permission request directly.
3292 // Request the permission. The download dialog will be launched by `onRequestPermissionResult()`.
3293 ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_FILE_REQUEST_CODE);
3295 } else { // The storage permission has already been granted.
3296 // Get a handle for the download file alert dialog.
3297 AppCompatDialogFragment downloadFileDialogFragment = DownloadFileDialog.fromUrl(linkUrl, "none", -1);
3299 // Show the download file alert dialog.
3300 downloadFileDialogFragment.show(supportFragmentManager, getString(R.string.download));
3306 // Add an Open with App entry.
3307 menu.add(R.string.open_with_app).setOnMenuItemClickListener((MenuItem item) -> {
3308 openWithApp(linkUrl);
3312 // Add an Open with Browser entry.
3313 menu.add(R.string.open_with_browser).setOnMenuItemClickListener((MenuItem item) -> {
3314 openWithBrowser(linkUrl);
3318 // Add a Cancel entry, which by default closes the context menu.
3319 menu.add(R.string.cancel);
3322 case WebView.HitTestResult.EMAIL_TYPE:
3323 // Get the target URL.
3324 linkUrl = hitTestResult.getExtra();
3326 // Set the target URL as the title of the `ContextMenu`.
3327 menu.setHeaderTitle(linkUrl);
3329 // Add a Write Email entry.
3330 menu.add(R.string.write_email).setOnMenuItemClickListener(item -> {
3331 // Use `ACTION_SENDTO` instead of `ACTION_SEND` so that only email programs are launched.
3332 Intent emailIntent = new Intent(Intent.ACTION_SENDTO);
3334 // Parse the url and set it as the data for the `Intent`.
3335 emailIntent.setData(Uri.parse("mailto:" + linkUrl));
3337 // `FLAG_ACTIVITY_NEW_TASK` opens the email program in a new task instead as part of Privacy Browser.
3338 emailIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
3341 startActivity(emailIntent);
3345 // Add a Copy Email Address entry.
3346 menu.add(R.string.copy_email_address).setOnMenuItemClickListener(item -> {
3347 // Save the email address in a `ClipData`.
3348 ClipData srcEmailTypeClipData = ClipData.newPlainText(getString(R.string.email_address), linkUrl);
3350 // Set the `ClipData` as the clipboard's primary clip.
3351 clipboardManager.setPrimaryClip(srcEmailTypeClipData);
3355 // Add a `Cancel` entry, which by default closes the `ContextMenu`.
3356 menu.add(R.string.cancel);
3359 // `IMAGE_TYPE` is an image.
3360 case WebView.HitTestResult.IMAGE_TYPE:
3361 // Get the image URL.
3362 imageUrl = hitTestResult.getExtra();
3364 // Set the image URL as the title of the `ContextMenu`.
3365 menu.setHeaderTitle(imageUrl);
3367 // Add a View Image entry.
3368 menu.add(R.string.view_image).setOnMenuItemClickListener(item -> {
3373 // Add a Download Image entry.
3374 menu.add(R.string.download_image).setOnMenuItemClickListener((MenuItem item) -> {
3375 // Check if the download should be processed by an external app.
3376 if (downloadWithExternalApp) { // Download with an external app.
3377 openUrlWithExternalApp(imageUrl);
3378 } else { // Download with Android's download manager.
3379 // Check to see if the storage permission has already been granted.
3380 if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) { // The storage permission needs to be requested.
3381 // Store the image URL for use by `onRequestPermissionResult()`.
3382 downloadImageUrl = imageUrl;
3384 // Show a dialog if the user has previously denied the permission.
3385 if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) { // Show a dialog explaining the request first.
3386 // Instantiate the download location permission alert dialog and set the download type to DOWNLOAD_IMAGE.
3387 DialogFragment downloadLocationPermissionDialogFragment = DownloadLocationPermissionDialog.downloadType(DownloadLocationPermissionDialog.DOWNLOAD_IMAGE);
3389 // Show the download location permission alert dialog. The permission will be requested when the dialog is closed.
3390 downloadLocationPermissionDialogFragment.show(getFragmentManager(), getString(R.string.download_location));
3391 } else { // Show the permission request directly.
3392 // Request the permission. The download dialog will be launched by `onRequestPermissionResult().
3393 ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_IMAGE_REQUEST_CODE);
3395 } else { // The storage permission has already been granted.
3396 // Get a handle for the download image alert dialog.
3397 AppCompatDialogFragment downloadImageDialogFragment = DownloadImageDialog.imageUrl(imageUrl);
3399 // Show the download image alert dialog.
3400 downloadImageDialogFragment.show(supportFragmentManager, getString(R.string.download));
3406 // Add a Copy URL entry.
3407 menu.add(R.string.copy_url).setOnMenuItemClickListener(item -> {
3408 // Save the image URL in a `ClipData`.
3409 ClipData srcImageTypeClipData = ClipData.newPlainText(getString(R.string.url), imageUrl);
3411 // Set the `ClipData` as the clipboard's primary clip.
3412 clipboardManager.setPrimaryClip(srcImageTypeClipData);
3416 // Add an Open with App entry.
3417 menu.add(R.string.open_with_app).setOnMenuItemClickListener((MenuItem item) -> {
3418 openWithApp(imageUrl);
3422 // Add an Open with Browser entry.
3423 menu.add(R.string.open_with_browser).setOnMenuItemClickListener((MenuItem item) -> {
3424 openWithBrowser(imageUrl);
3428 // Add a `Cancel` entry, which by default closes the `ContextMenu`.
3429 menu.add(R.string.cancel);
3433 // `SRC_IMAGE_ANCHOR_TYPE` is an image that is also a link.
3434 case WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE:
3435 // Get the image URL.
3436 imageUrl = hitTestResult.getExtra();
3438 // Set the image URL as the title of the `ContextMenu`.
3439 menu.setHeaderTitle(imageUrl);
3441 // Add a `View Image` entry.
3442 menu.add(R.string.view_image).setOnMenuItemClickListener(item -> {
3447 // Add a `Download Image` entry.
3448 menu.add(R.string.download_image).setOnMenuItemClickListener((MenuItem item) -> {
3449 // Check if the download should be processed by an external app.
3450 if (downloadWithExternalApp) { // Download with an external app.
3451 openUrlWithExternalApp(imageUrl);
3452 } else { // Download with Android's download manager.
3453 // Check to see if the storage permission has already been granted.
3454 if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) { // The storage permission needs to be requested.
3455 // Store the image URL for use by `onRequestPermissionResult()`.
3456 downloadImageUrl = imageUrl;
3458 // Show a dialog if the user has previously denied the permission.
3459 if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) { // Show a dialog explaining the request first.
3460 // Instantiate the download location permission alert dialog and set the download type to DOWNLOAD_IMAGE.
3461 DialogFragment downloadLocationPermissionDialogFragment = DownloadLocationPermissionDialog.downloadType(DownloadLocationPermissionDialog.DOWNLOAD_IMAGE);
3463 // Show the download location permission alert dialog. The permission will be requested when the dialog is closed.
3464 downloadLocationPermissionDialogFragment.show(getFragmentManager(), getString(R.string.download_location));
3465 } else { // Show the permission request directly.
3466 // Request the permission. The download dialog will be launched by `onRequestPermissionResult().
3467 ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_IMAGE_REQUEST_CODE);
3469 } else { // The storage permission has already been granted.
3470 // Get a handle for the download image alert dialog.
3471 AppCompatDialogFragment downloadImageDialogFragment = DownloadImageDialog.imageUrl(imageUrl);
3473 // Show the download image alert dialog.
3474 downloadImageDialogFragment.show(supportFragmentManager, getString(R.string.download));
3480 // Add a `Copy URL` entry.
3481 menu.add(R.string.copy_url).setOnMenuItemClickListener(item -> {
3482 // Save the image URL in a `ClipData`.
3483 ClipData srcImageAnchorTypeClipData = ClipData.newPlainText(getString(R.string.url), imageUrl);
3485 // Set the `ClipData` as the clipboard's primary clip.
3486 clipboardManager.setPrimaryClip(srcImageAnchorTypeClipData);
3490 // Add an Open with App entry.
3491 menu.add(R.string.open_with_app).setOnMenuItemClickListener((MenuItem item) -> {
3492 openWithApp(imageUrl);
3496 // Add an Open with Browser entry.
3497 menu.add(R.string.open_with_browser).setOnMenuItemClickListener((MenuItem item) -> {
3498 openWithBrowser(imageUrl);
3502 // Add a `Cancel` entry, which by default closes the `ContextMenu`.
3503 menu.add(R.string.cancel);
3509 public void onCreateBookmark(AppCompatDialogFragment dialogFragment) {
3510 // Get the `EditTexts` from the `dialogFragment`.
3511 EditText createBookmarkNameEditText = dialogFragment.getDialog().findViewById(R.id.create_bookmark_name_edittext);
3512 EditText createBookmarkUrlEditText = dialogFragment.getDialog().findViewById(R.id.create_bookmark_url_edittext);
3514 // Extract the strings from the `EditTexts`.
3515 String bookmarkNameString = createBookmarkNameEditText.getText().toString();
3516 String bookmarkUrlString = createBookmarkUrlEditText.getText().toString();
3518 // Convert the favoriteIcon Bitmap to a byte array. `0` is for lossless compression (the only option for a PNG).
3519 ByteArrayOutputStream favoriteIconByteArrayOutputStream = new ByteArrayOutputStream();
3520 favoriteIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, favoriteIconByteArrayOutputStream);
3521 byte[] favoriteIconByteArray = favoriteIconByteArrayOutputStream.toByteArray();
3523 // Display the new bookmark below the current items in the (0 indexed) list.
3524 int newBookmarkDisplayOrder = bookmarksListView.getCount();
3526 // Create the bookmark.
3527 bookmarksDatabaseHelper.createBookmark(bookmarkNameString, bookmarkUrlString, currentBookmarksFolder, newBookmarkDisplayOrder, favoriteIconByteArray);
3529 // Update the bookmarks cursor with the current contents of this folder.
3530 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder);
3532 // Update the `ListView`.
3533 bookmarksCursorAdapter.changeCursor(bookmarksCursor);
3535 // Scroll to the new bookmark.
3536 bookmarksListView.setSelection(newBookmarkDisplayOrder);
3540 public void onCreateBookmarkFolder(AppCompatDialogFragment dialogFragment) {
3541 // Get handles for the views in `dialogFragment`.
3542 EditText createFolderNameEditText = dialogFragment.getDialog().findViewById(R.id.create_folder_name_edittext);
3543 RadioButton defaultFolderIconRadioButton = dialogFragment.getDialog().findViewById(R.id.create_folder_default_icon_radiobutton);
3544 ImageView folderIconImageView = dialogFragment.getDialog().findViewById(R.id.create_folder_default_icon);
3546 // Get new folder name string.
3547 String folderNameString = createFolderNameEditText.getText().toString();
3549 // Get the new folder icon `Bitmap`.
3550 Bitmap folderIconBitmap;
3551 if (defaultFolderIconRadioButton.isChecked()) { // Use the default folder icon.
3552 // Get the default folder icon and convert it to a `Bitmap`.
3553 Drawable folderIconDrawable = folderIconImageView.getDrawable();
3554 BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable;
3555 folderIconBitmap = folderIconBitmapDrawable.getBitmap();
3556 } else { // Use the `WebView` favorite icon.
3557 folderIconBitmap = favoriteIconBitmap;
3560 // Convert `folderIconBitmap` to a byte array. `0` is for lossless compression (the only option for a PNG).
3561 ByteArrayOutputStream folderIconByteArrayOutputStream = new ByteArrayOutputStream();
3562 folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, folderIconByteArrayOutputStream);
3563 byte[] folderIconByteArray = folderIconByteArrayOutputStream.toByteArray();
3565 // Move all the bookmarks down one in the display order.
3566 for (int i = 0; i < bookmarksListView.getCount(); i++) {
3567 int databaseId = (int) bookmarksListView.getItemIdAtPosition(i);
3568 bookmarksDatabaseHelper.updateDisplayOrder(databaseId, i + 1);
3571 // Create the folder, which will be placed at the top of the `ListView`.
3572 bookmarksDatabaseHelper.createFolder(folderNameString, currentBookmarksFolder, folderIconByteArray);
3574 // Update the bookmarks cursor with the current contents of this folder.
3575 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder);
3577 // Update the `ListView`.
3578 bookmarksCursorAdapter.changeCursor(bookmarksCursor);
3580 // Scroll to the new folder.
3581 bookmarksListView.setSelection(0);
3585 public void onCreateHomeScreenShortcut(AppCompatDialogFragment dialogFragment) {
3586 // Get the shortcut name.
3587 EditText shortcutNameEditText = dialogFragment.getDialog().findViewById(R.id.shortcut_name_edittext);
3588 String shortcutNameString = shortcutNameEditText.getText().toString();
3590 // Convert the favorite icon bitmap to an `Icon`. `IconCompat` is required until API >= 26.
3591 IconCompat favoriteIcon = IconCompat.createWithBitmap(favoriteIconBitmap);
3593 // Setup the shortcut intent.
3594 Intent shortcutIntent = new Intent(Intent.ACTION_VIEW);
3595 shortcutIntent.setData(Uri.parse(formattedUrlString));
3597 // Create a shortcut info builder. The shortcut name becomes the shortcut ID.
3598 ShortcutInfoCompat.Builder shortcutInfoBuilder = new ShortcutInfoCompat.Builder(this, shortcutNameString);
3600 // Add the required fields to the shortcut info builder.
3601 shortcutInfoBuilder.setIcon(favoriteIcon);
3602 shortcutInfoBuilder.setIntent(shortcutIntent);
3603 shortcutInfoBuilder.setShortLabel(shortcutNameString);
3605 // Request the pin. `ShortcutManagerCompat` can be switched to `ShortcutManager` once API >= 26.
3606 ShortcutManagerCompat.requestPinShortcut(this, shortcutInfoBuilder.build(), null);
3610 public void onCloseDownloadLocationPermissionDialog(int downloadType) {
3611 switch (downloadType) {
3612 case DownloadLocationPermissionDialog.DOWNLOAD_FILE:
3613 // Request the WRITE_EXTERNAL_STORAGE permission with a file request code.
3614 ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_FILE_REQUEST_CODE);
3617 case DownloadLocationPermissionDialog.DOWNLOAD_IMAGE:
3618 // Request the WRITE_EXTERNAL_STORAGE permission with an image request code.
3619 ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_IMAGE_REQUEST_CODE);
3625 public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
3626 switch (requestCode) {
3627 case DOWNLOAD_FILE_REQUEST_CODE:
3628 // Show the download file alert dialog. When the dialog closes, the correct command will be used based on the permission status.
3629 AppCompatDialogFragment downloadFileDialogFragment = DownloadFileDialog.fromUrl(downloadUrl, downloadContentDisposition, downloadContentLength);
3631 // On API 23, displaying the fragment must be delayed or the app will crash.
3632 if (Build.VERSION.SDK_INT == 23) {
3633 new Handler().postDelayed(() -> downloadFileDialogFragment.show(supportFragmentManager, getString(R.string.download)), 500);
3635 downloadFileDialogFragment.show(supportFragmentManager, getString(R.string.download));
3638 // Reset the download variables.
3640 downloadContentDisposition = "";
3641 downloadContentLength = 0;
3644 case DOWNLOAD_IMAGE_REQUEST_CODE:
3645 // Show the download image alert dialog. When the dialog closes, the correct command will be used based on the permission status.
3646 AppCompatDialogFragment downloadImageDialogFragment = DownloadImageDialog.imageUrl(downloadImageUrl);
3648 // On API 23, displaying the fragment must be delayed or the app will crash.
3649 if (Build.VERSION.SDK_INT == 23) {
3650 new Handler().postDelayed(() -> downloadImageDialogFragment.show(supportFragmentManager, getString(R.string.download)), 500);
3652 downloadImageDialogFragment.show(supportFragmentManager, getString(R.string.download));
3655 // Reset the image URL variable.
3656 downloadImageUrl = "";
3662 public void onDownloadImage(AppCompatDialogFragment dialogFragment, String imageUrl) {
3663 // Download the image if it has an HTTP or HTTPS URI.
3664 if (imageUrl.startsWith("http")) {
3665 // Get a handle for the system `DOWNLOAD_SERVICE`.
3666 DownloadManager downloadManager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
3668 // Parse `imageUrl`.
3669 DownloadManager.Request downloadRequest = new DownloadManager.Request(Uri.parse(imageUrl));
3671 // Pass cookies to download manager if cookies are enabled. This is required to download images from websites that require a login.
3672 // Code contributed 2017 Hendrik Knackstedt. Copyright assigned to Soren Stoutner <soren@stoutner.com>.
3673 if (firstPartyCookiesEnabled) {
3674 // Get the cookies for `imageUrl`.
3675 String cookies = cookieManager.getCookie(imageUrl);
3677 // Add the cookies to `downloadRequest`. In the HTTP request header, cookies are named `Cookie`.
3678 downloadRequest.addRequestHeader("Cookie", cookies);
3681 // Get the file name from the dialog fragment.
3682 EditText downloadImageNameEditText = dialogFragment.getDialog().findViewById(R.id.download_image_name);
3683 String imageName = downloadImageNameEditText.getText().toString();
3685 // Specify the download location.
3686 if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { // External write permission granted.
3687 // Download to the public download directory.
3688 downloadRequest.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, imageName);
3689 } else { // External write permission denied.
3690 // Download to the app's external download directory.
3691 downloadRequest.setDestinationInExternalFilesDir(this, Environment.DIRECTORY_DOWNLOADS, imageName);
3694 // Allow `MediaScanner` to index the download if it is a media file.
3695 downloadRequest.allowScanningByMediaScanner();
3697 // Add the URL as the description for the download.
3698 downloadRequest.setDescription(imageUrl);
3700 // Show the download notification after the download is completed.
3701 downloadRequest.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
3703 // Remove the lint warning below that `downloadManager` might be `null`.
3704 assert downloadManager != null;
3706 // Initiate the download.
3707 downloadManager.enqueue(downloadRequest);
3708 } else { // The image is not an HTTP or HTTPS URI.
3709 Snackbar.make(mainWebView, R.string.cannot_download_image, Snackbar.LENGTH_INDEFINITE).show();
3714 public void onDownloadFile(AppCompatDialogFragment dialogFragment, String downloadUrl) {
3715 // Download the file if it has an HTTP or HTTPS URI.
3716 if (downloadUrl.startsWith("http")) {
3717 // Get a handle for the system `DOWNLOAD_SERVICE`.
3718 DownloadManager downloadManager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
3720 // Parse `downloadUrl`.
3721 DownloadManager.Request downloadRequest = new DownloadManager.Request(Uri.parse(downloadUrl));
3723 // Pass cookies to download manager if cookies are enabled. This is required to download files from websites that require a login.
3724 // Code contributed 2017 Hendrik Knackstedt. Copyright assigned to Soren Stoutner <soren@stoutner.com>.
3725 if (firstPartyCookiesEnabled) {
3726 // Get the cookies for `downloadUrl`.
3727 String cookies = cookieManager.getCookie(downloadUrl);
3729 // Add the cookies to `downloadRequest`. In the HTTP request header, cookies are named `Cookie`.
3730 downloadRequest.addRequestHeader("Cookie", cookies);
3733 // Get the file name from the dialog fragment.
3734 EditText downloadFileNameEditText = dialogFragment.getDialog().findViewById(R.id.download_file_name);
3735 String fileName = downloadFileNameEditText.getText().toString();
3737 // Specify the download location.
3738 if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { // External write permission granted.
3739 // Download to the public download directory.
3740 downloadRequest.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, fileName);
3741 } else { // External write permission denied.
3742 // Download to the app's external download directory.
3743 downloadRequest.setDestinationInExternalFilesDir(this, Environment.DIRECTORY_DOWNLOADS, fileName);
3746 // Allow `MediaScanner` to index the download if it is a media file.
3747 downloadRequest.allowScanningByMediaScanner();
3749 // Add the URL as the description for the download.
3750 downloadRequest.setDescription(downloadUrl);
3752 // Show the download notification after the download is completed.
3753 downloadRequest.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
3755 // Remove the lint warning below that `downloadManager` might be `null`.
3756 assert downloadManager != null;
3758 // Initiate the download.
3759 downloadManager.enqueue(downloadRequest);
3760 } else { // The download is not an HTTP or HTTPS URI.
3761 Snackbar.make(mainWebView, R.string.cannot_download_file, Snackbar.LENGTH_INDEFINITE).show();
3766 public void onSaveBookmark(AppCompatDialogFragment dialogFragment, int selectedBookmarkDatabaseId) {
3767 // Get handles for the views from `dialogFragment`.
3768 EditText editBookmarkNameEditText = dialogFragment.getDialog().findViewById(R.id.edit_bookmark_name_edittext);
3769 EditText editBookmarkUrlEditText = dialogFragment.getDialog().findViewById(R.id.edit_bookmark_url_edittext);
3770 RadioButton currentBookmarkIconRadioButton = dialogFragment.getDialog().findViewById(R.id.edit_bookmark_current_icon_radiobutton);
3772 // Store the bookmark strings.
3773 String bookmarkNameString = editBookmarkNameEditText.getText().toString();
3774 String bookmarkUrlString = editBookmarkUrlEditText.getText().toString();
3776 // Update the bookmark.
3777 if (currentBookmarkIconRadioButton.isChecked()) { // Update the bookmark without changing the favorite icon.
3778 bookmarksDatabaseHelper.updateBookmark(selectedBookmarkDatabaseId, bookmarkNameString, bookmarkUrlString);
3779 } else { // Update the bookmark using the `WebView` favorite icon.
3780 // Convert the favorite icon to a byte array. `0` is for lossless compression (the only option for a PNG).
3781 ByteArrayOutputStream newFavoriteIconByteArrayOutputStream = new ByteArrayOutputStream();
3782 favoriteIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, newFavoriteIconByteArrayOutputStream);
3783 byte[] newFavoriteIconByteArray = newFavoriteIconByteArrayOutputStream.toByteArray();
3785 // Update the bookmark and the favorite icon.
3786 bookmarksDatabaseHelper.updateBookmark(selectedBookmarkDatabaseId, bookmarkNameString, bookmarkUrlString, newFavoriteIconByteArray);
3789 // Update the bookmarks cursor with the current contents of this folder.
3790 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder);
3792 // Update the `ListView`.
3793 bookmarksCursorAdapter.changeCursor(bookmarksCursor);
3797 public void onSaveBookmarkFolder(AppCompatDialogFragment dialogFragment, int selectedFolderDatabaseId) {
3798 // Get handles for the views from `dialogFragment`.
3799 EditText editFolderNameEditText = dialogFragment.getDialog().findViewById(R.id.edit_folder_name_edittext);
3800 RadioButton currentFolderIconRadioButton = dialogFragment.getDialog().findViewById(R.id.edit_folder_current_icon_radiobutton);
3801 RadioButton defaultFolderIconRadioButton = dialogFragment.getDialog().findViewById(R.id.edit_folder_default_icon_radiobutton);
3802 ImageView folderIconImageView = dialogFragment.getDialog().findViewById(R.id.edit_folder_default_icon_imageview);
3804 // Get the new folder name.
3805 String newFolderNameString = editFolderNameEditText.getText().toString();
3807 // Check if the favorite icon has changed.
3808 if (currentFolderIconRadioButton.isChecked()) { // Only the name has changed.
3809 // Update the name in the database.
3810 bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, oldFolderNameString, newFolderNameString);
3811 } else if (!currentFolderIconRadioButton.isChecked() && newFolderNameString.equals(oldFolderNameString)) { // Only the icon has changed.
3812 // Get the new folder icon `Bitmap`.
3813 Bitmap folderIconBitmap;
3814 if (defaultFolderIconRadioButton.isChecked()) {
3815 // Get the default folder icon and convert it to a `Bitmap`.
3816 Drawable folderIconDrawable = folderIconImageView.getDrawable();
3817 BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable;
3818 folderIconBitmap = folderIconBitmapDrawable.getBitmap();
3819 } else { // Use the `WebView` favorite icon.
3820 folderIconBitmap = favoriteIconBitmap;
3823 // Convert the folder `Bitmap` to a byte array. `0` is for lossless compression (the only option for a PNG).
3824 ByteArrayOutputStream folderIconByteArrayOutputStream = new ByteArrayOutputStream();
3825 folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, folderIconByteArrayOutputStream);
3826 byte[] folderIconByteArray = folderIconByteArrayOutputStream.toByteArray();
3828 // Update the folder icon in the database.
3829 bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, folderIconByteArray);
3830 } else { // The folder icon and the name have changed.
3831 // Get the new folder icon `Bitmap`.
3832 Bitmap folderIconBitmap;
3833 if (defaultFolderIconRadioButton.isChecked()) {
3834 // Get the default folder icon and convert it to a `Bitmap`.
3835 Drawable folderIconDrawable = folderIconImageView.getDrawable();
3836 BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable;
3837 folderIconBitmap = folderIconBitmapDrawable.getBitmap();
3838 } else { // Use the `WebView` favorite icon.
3839 folderIconBitmap = MainWebViewActivity.favoriteIconBitmap;
3842 // Convert the folder `Bitmap` to a byte array. `0` is for lossless compression (the only option for a PNG).
3843 ByteArrayOutputStream folderIconByteArrayOutputStream = new ByteArrayOutputStream();
3844 folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, folderIconByteArrayOutputStream);
3845 byte[] folderIconByteArray = folderIconByteArrayOutputStream.toByteArray();
3847 // Update the folder name and icon in the database.
3848 bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, oldFolderNameString, newFolderNameString, folderIconByteArray);
3851 // Update the bookmarks cursor with the current contents of this folder.
3852 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder);
3854 // Update the `ListView`.
3855 bookmarksCursorAdapter.changeCursor(bookmarksCursor);
3859 public void onHttpAuthenticationCancel() {
3860 // Cancel the `HttpAuthHandler`.
3861 httpAuthHandler.cancel();
3865 public void onHttpAuthenticationProceed(AppCompatDialogFragment dialogFragment) {
3866 // Get handles for the `EditTexts`.
3867 EditText usernameEditText = dialogFragment.getDialog().findViewById(R.id.http_authentication_username);
3868 EditText passwordEditText = dialogFragment.getDialog().findViewById(R.id.http_authentication_password);
3870 // Proceed with the HTTP authentication.
3871 httpAuthHandler.proceed(usernameEditText.getText().toString(), passwordEditText.getText().toString());
3874 public void viewSslCertificate(View view) {
3875 // Show the `ViewSslCertificateDialog` `AlertDialog` and name this instance `@string/view_ssl_certificate`.
3876 DialogFragment viewSslCertificateDialogFragment = new ViewSslCertificateDialog();
3877 viewSslCertificateDialogFragment.show(getFragmentManager(), getString(R.string.view_ssl_certificate));
3881 public void onSslErrorCancel() {
3882 sslErrorHandler.cancel();
3886 public void onSslErrorProceed() {
3887 sslErrorHandler.proceed();
3891 public void onPinnedMismatchBack() {
3892 if (mainWebView.canGoBack()) { // There is a back page in the history.
3893 // Reset the formatted URL string so the page will load correctly if blocking of third-party requests is enabled.
3894 formattedUrlString = "";
3896 // Set `navigatingHistory` so that the domain settings are applied when the new URL is loaded.
3897 navigatingHistory = true;
3900 mainWebView.goBack();
3901 } else { // There are no pages to go back to.
3902 // Load a blank page
3908 public void onPinnedMismatchProceed() {
3909 // Do not check the pinned information for this domain again until the domain changes.
3910 ignorePinnedDomainInformation = true;
3914 public void onUrlHistoryEntrySelected(int moveBackOrForwardSteps) {
3915 // Reset the formatted URL string so the page will load correctly if blocking of third-party requests is enabled.
3916 formattedUrlString = "";
3918 // Set `navigatingHistory` so that the domain settings are applied when the new URL is loaded.
3919 navigatingHistory = true;
3921 // Load the history entry.
3922 mainWebView.goBackOrForward(moveBackOrForwardSteps);
3926 public void onClearHistory() {
3927 // Clear the history.
3928 mainWebView.clearHistory();
3931 // Override `onBackPressed` to handle the navigation drawer and `mainWebView`.
3933 public void onBackPressed() {
3934 if (drawerLayout.isDrawerVisible(GravityCompat.START)) { // The navigation drawer is open.
3935 // Close the navigation drawer.
3936 drawerLayout.closeDrawer(GravityCompat.START);
3937 } else if (drawerLayout.isDrawerVisible(GravityCompat.END)){ // The bookmarks drawer is open.
3938 if (currentBookmarksFolder.isEmpty()) { // The home folder is displayed.
3939 // close the bookmarks drawer.
3940 drawerLayout.closeDrawer(GravityCompat.END);
3941 } else { // A subfolder is displayed.
3942 // Place the former parent folder in `currentFolder`.
3943 currentBookmarksFolder = bookmarksDatabaseHelper.getParentFolderName(currentBookmarksFolder);
3945 // Load the new folder.
3946 loadBookmarksFolder();
3949 } else if (mainWebView.canGoBack()) { // There is at least one item in the `WebView` history.
3950 // Reset the formatted URL string so the page will load correctly if blocking of third-party requests is enabled.
3951 formattedUrlString = "";
3953 // Set `navigatingHistory` so that the domain settings are applied when the new URL is loaded.
3954 navigatingHistory = true;
3957 mainWebView.goBack();
3958 } else { // There isn't anything to do in Privacy Browser.
3959 // Pass `onBackPressed()` to the system.
3960 super.onBackPressed();
3964 // 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.
3966 public void onActivityResult(int requestCode, int resultCode, Intent data) {
3967 // File uploads only work on API >= 21.
3968 if (Build.VERSION.SDK_INT >= 21) {
3969 // Pass the file to the WebView.
3970 fileChooserCallback.onReceiveValue(WebChromeClient.FileChooserParams.parseResult(resultCode, data));
3974 private void loadUrlFromTextBox() {
3975 // Get the text from urlTextBox and convert it to a string. trim() removes white spaces from the beginning and end of the string.
3976 String unformattedUrlString = urlTextBox.getText().toString().trim();
3978 // Check to see if `unformattedUrlString` is a valid URL. Otherwise, convert it into a search.
3979 if (unformattedUrlString.startsWith("content://")) {
3980 // Load the entire content URL.
3981 formattedUrlString = unformattedUrlString;
3982 } else if (Patterns.WEB_URL.matcher(unformattedUrlString).matches() || unformattedUrlString.startsWith("http://") || unformattedUrlString.startsWith("https://")
3983 || unformattedUrlString.startsWith("file://")) {
3984 // Add `https://` at the beginning if there is no protocol. Otherwise the app will segfault.
3985 if (!unformattedUrlString.startsWith("http") && !unformattedUrlString.startsWith("file://") && !unformattedUrlString.startsWith("content://")) {
3986 unformattedUrlString = "https://" + unformattedUrlString;
3989 // Initialize `unformattedUrl`.
3990 URL unformattedUrl = null;
3992 // 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.
3994 unformattedUrl = new URL(unformattedUrlString);
3995 } catch (MalformedURLException e) {
3996 e.printStackTrace();
3999 // The ternary operator (? :) makes sure that a null pointer exception is not thrown, which would happen if `.get` was called on a `null` value.
4000 String scheme = unformattedUrl != null ? unformattedUrl.getProtocol() : null;
4001 String authority = unformattedUrl != null ? unformattedUrl.getAuthority() : null;
4002 String path = unformattedUrl != null ? unformattedUrl.getPath() : null;
4003 String query = unformattedUrl != null ? unformattedUrl.getQuery() : null;
4004 String fragment = unformattedUrl != null ? unformattedUrl.getRef() : null;
4007 Uri.Builder formattedUri = new Uri.Builder();
4008 formattedUri.scheme(scheme).authority(authority).path(path).query(query).fragment(fragment);
4010 // Decode `formattedUri` as a `String` in `UTF-8`.
4012 formattedUrlString = URLDecoder.decode(formattedUri.build().toString(), "UTF-8");
4013 } catch (UnsupportedEncodingException exception) {
4014 // Load a blank string.
4015 formattedUrlString = "";
4017 } else if (unformattedUrlString.isEmpty()){ // Load a blank web site.
4018 // Load a blank string.
4019 formattedUrlString = "";
4020 } else { // Search for the contents of the URL box.
4021 // Create an encoded URL String.
4022 String encodedUrlString;
4024 // Sanitize the search input.
4026 encodedUrlString = URLEncoder.encode(unformattedUrlString, "UTF-8");
4027 } catch (UnsupportedEncodingException exception) {
4028 encodedUrlString = "";
4031 // Add the base search URL.
4032 formattedUrlString = searchURL + encodedUrlString;
4035 // 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.
4036 urlTextBox.clearFocus();
4039 loadUrl(formattedUrlString);
4042 private void loadUrl(String url) {// Apply any custom domain settings.
4043 // Set the URL as the formatted URL string so that checking third-party requests works correctly.
4044 formattedUrlString = url;
4046 // Apply the domain settings.
4047 applyDomainSettings(url, true, false);
4049 // If loading a website, set `urlIsLoading` to prevent changes in the user agent on websites with redirects from reloading the current website.
4050 urlIsLoading = !url.equals("");
4053 mainWebView.loadUrl(url, customHeaders);
4056 public void findPreviousOnPage(View view) {
4057 // Go to the previous highlighted phrase on the page. `false` goes backwards instead of forwards.
4058 mainWebView.findNext(false);
4061 public void findNextOnPage(View view) {
4062 // Go to the next highlighted phrase on the page. `true` goes forwards instead of backwards.
4063 mainWebView.findNext(true);
4066 public void closeFindOnPage(View view) {
4067 // Delete the contents of `find_on_page_edittext`.
4068 findOnPageEditText.setText(null);
4070 // Clear the highlighted phrases.
4071 mainWebView.clearMatches();
4073 // Hide the Find on Page `RelativeLayout`.
4074 findOnPageLinearLayout.setVisibility(View.GONE);
4076 // Show the URL app bar.
4077 supportAppBar.setVisibility(View.VISIBLE);
4079 // Hide the keyboard so we can see the webpage. `0` indicates no additional flags.
4080 inputMethodManager.hideSoftInputFromWindow(mainWebView.getWindowToken(), 0);
4083 private void applyAppSettings() {
4084 // Get a handle for the shared preferences.
4085 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
4087 // Store the values from the shared preferences in variables.
4088 incognitoModeEnabled = sharedPreferences.getBoolean("incognito_mode", false);
4089 boolean doNotTrackEnabled = sharedPreferences.getBoolean("do_not_track", false);
4090 proxyThroughOrbot = sharedPreferences.getBoolean("proxy_through_orbot", false);
4091 fullScreenBrowsingModeEnabled = sharedPreferences.getBoolean("full_screen_browsing_mode", false);
4092 hideSystemBarsOnFullscreen = sharedPreferences.getBoolean("hide_system_bars", false);
4093 translucentNavigationBarOnFullscreen = sharedPreferences.getBoolean("translucent_navigation_bar", true);
4094 downloadWithExternalApp = sharedPreferences.getBoolean("download_with_external_app", false);
4096 // Apply the proxy through Orbot settings.
4097 applyProxyThroughOrbot(false);
4099 // Set Do Not Track status.
4100 if (doNotTrackEnabled) {
4101 customHeaders.put("DNT", "1");
4103 customHeaders.remove("DNT");
4106 // Apply the appropriate full screen mode the `SYSTEM_UI` flags.
4107 if (fullScreenBrowsingModeEnabled && inFullScreenBrowsingMode) { // Privacy Browser is currently in full screen browsing mode.
4108 if (hideSystemBarsOnFullscreen) { // Hide everything.
4109 // Remove the translucent navigation setting if it is currently flagged.
4110 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
4112 // Remove the translucent status bar overlay.
4113 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
4115 // Remove the translucent status bar overlay on the `Drawer Layout`, which is special and needs its own command.
4116 drawerLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
4118 /* SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
4119 * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
4120 * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
4122 rootCoordinatorLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
4123 } else { // Hide everything except the status and navigation bars.
4124 // Remove any `SYSTEM_UI` flags from `rootCoordinatorLayout`.
4125 rootCoordinatorLayout.setSystemUiVisibility(0);
4127 // Add the translucent status flag if it is unset.
4128 getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
4130 if (translucentNavigationBarOnFullscreen) {
4131 // Set the navigation bar to be translucent.
4132 getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
4134 // Set the navigation bar to be black.
4135 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
4138 } else { // Privacy Browser is not in full screen browsing mode.
4139 // 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.
4140 inFullScreenBrowsingMode = false;
4142 // Show the `appBar` if `findOnPageLinearLayout` is not visible.
4143 if (findOnPageLinearLayout.getVisibility() == View.GONE) {
4147 // Show the `BannerAd` in the free flavor.
4148 if (BuildConfig.FLAVOR.contentEquals("free")) {
4149 // Initialize the ad. The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations.
4150 AdHelper.initializeAds(findViewById(R.id.adview), getApplicationContext(), getFragmentManager(), getString(R.string.google_app_id), getString(R.string.ad_unit_id));
4153 // Remove any `SYSTEM_UI` flags from `rootCoordinatorLayout`.
4154 rootCoordinatorLayout.setSystemUiVisibility(0);
4156 // Remove the translucent navigation bar flag if it is set.
4157 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
4159 // Add the translucent status flag if it is unset. This also resets `drawerLayout's` `View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN`.
4160 getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
4162 // Constrain `rootCoordinatorLayout` inside the status and navigation bars.
4163 rootCoordinatorLayout.setFitsSystemWindows(true);
4167 // `reloadWebsite` is used if returning from the Domains activity. Otherwise JavaScript might not function correctly if it is newly enabled.
4168 // The deprecated `.getDrawable()` must be used until the minimum API >= 21.
4169 @SuppressWarnings("deprecation")
4170 private boolean applyDomainSettings(String url, boolean resetFavoriteIcon, boolean reloadWebsite) {
4171 // Get the current user agent.
4172 String initialUserAgent = mainWebView.getSettings().getUserAgentString();
4174 // Initialize a variable to track if the user agent changes.
4175 boolean userAgentChanged = false;
4177 // Parse the URL into a URI.
4178 Uri uri = Uri.parse(url);
4180 // Extract the domain from `uri`.
4181 String hostName = uri.getHost();
4183 // Initialize `loadingNewDomainName`.
4184 boolean loadingNewDomainName;
4186 // If either `hostName` or `currentDomainName` are `null`, run the options for loading a new domain name.
4187 // The lint suggestion to simplify the `if` statement is incorrect, because `hostName.equals(currentDomainName)` can produce a `null object reference.`
4188 //noinspection SimplifiableIfStatement
4189 if ((hostName == null) || (currentDomainName == null)) {
4190 loadingNewDomainName = true;
4191 } else { // Determine if `hostName` equals `currentDomainName`.
4192 loadingNewDomainName = !hostName.equals(currentDomainName);
4195 // Strings don't like to be null.
4196 if (hostName == null) {
4200 // 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.
4201 if (loadingNewDomainName) {
4202 // Set the new `hostname` as the `currentDomainName`.
4203 currentDomainName = hostName;
4205 // Reset the ignoring of pinned domain information.
4206 ignorePinnedDomainInformation = false;
4208 // Reset the favorite icon if specified.
4209 if (resetFavoriteIcon) {
4210 favoriteIconBitmap = favoriteIconDefaultBitmap;
4211 favoriteIconImageView.setImageBitmap(Bitmap.createScaledBitmap(favoriteIconBitmap, 64, 64, true));
4214 // Initialize the database handler. The `0` specifies the database version, but that is ignored and set instead using a constant in `DomainsDatabaseHelper`.
4215 DomainsDatabaseHelper domainsDatabaseHelper = new DomainsDatabaseHelper(this, null, null, 0);
4217 // Get a full cursor from `domainsDatabaseHelper`.
4218 Cursor domainNameCursor = domainsDatabaseHelper.getDomainNameCursorOrderedByDomain();
4220 // Initialize `domainSettingsSet`.
4221 Set<String> domainSettingsSet = new HashSet<>();
4223 // Get the domain name column index.
4224 int domainNameColumnIndex = domainNameCursor.getColumnIndex(DomainsDatabaseHelper.DOMAIN_NAME);
4226 // Populate `domainSettingsSet`.
4227 for (int i = 0; i < domainNameCursor.getCount(); i++) {
4228 // Move `domainsCursor` to the current row.
4229 domainNameCursor.moveToPosition(i);
4231 // Store the domain name in `domainSettingsSet`.
4232 domainSettingsSet.add(domainNameCursor.getString(domainNameColumnIndex));
4235 // Close `domainNameCursor.
4236 domainNameCursor.close();
4238 // Initialize variables to track if domain settings will be applied and, if so, under which name.
4239 domainSettingsApplied = false;
4240 String domainNameInDatabase = null;
4242 // Check the hostname.
4243 if (domainSettingsSet.contains(hostName)) {
4244 domainSettingsApplied = true;
4245 domainNameInDatabase = hostName;
4248 // Check all the subdomains of the host name against wildcard domains in the domain cursor.
4249 while (!domainSettingsApplied && hostName.contains(".")) { // Stop checking if domain settings are already applied or there are no more `.` in the host name.
4250 if (domainSettingsSet.contains("*." + hostName)) { // Check the host name prepended by `*.`.
4251 // Apply the domain settings.
4252 domainSettingsApplied = true;
4254 // Store the applied domain names as it appears in the database.
4255 domainNameInDatabase = "*." + hostName;
4258 // Strip out the lowest subdomain of of the host name.
4259 hostName = hostName.substring(hostName.indexOf(".") + 1);
4263 // Get a handle for the shared preference.
4264 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
4266 // Store the general preference information.
4267 String defaultFontSizeString = sharedPreferences.getString("font_size", getString(R.string.font_size_default_value));
4268 String defaultUserAgentName = sharedPreferences.getString("user_agent", getString(R.string.user_agent_default_value));
4269 defaultCustomUserAgentString = sharedPreferences.getString("custom_user_agent", getString(R.string.custom_user_agent_default_value));
4270 boolean defaultSwipeToRefresh = sharedPreferences.getBoolean("swipe_to_refresh", true);
4271 nightMode = sharedPreferences.getBoolean("night_mode", false);
4272 boolean displayWebpageImages = sharedPreferences.getBoolean("display_webpage_images", true);
4274 if (domainSettingsApplied) { // The url has custom domain settings.
4275 // Get a cursor for the current host and move it to the first position.
4276 Cursor currentHostDomainSettingsCursor = domainsDatabaseHelper.getCursorForDomainName(domainNameInDatabase);
4277 currentHostDomainSettingsCursor.moveToFirst();
4279 // Get the settings from the cursor.
4280 domainSettingsDatabaseId = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper._ID)));
4281 javaScriptEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_JAVASCRIPT)) == 1);
4282 firstPartyCookiesEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FIRST_PARTY_COOKIES)) == 1);
4283 thirdPartyCookiesEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_THIRD_PARTY_COOKIES)) == 1);
4284 domStorageEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_DOM_STORAGE)) == 1);
4285 // Form data can be removed once the minimum API >= 26.
4286 saveFormDataEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FORM_DATA)) == 1);
4287 easyListEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_EASYLIST)) == 1);
4288 easyPrivacyEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_EASYPRIVACY)) == 1);
4289 fanboysAnnoyanceListEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FANBOYS_ANNOYANCE_LIST)) == 1);
4290 fanboysSocialBlockingListEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FANBOYS_SOCIAL_BLOCKING_LIST)) == 1);
4291 ultraPrivacyEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_ULTRAPRIVACY)) == 1);
4292 blockAllThirdPartyRequests = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.BLOCK_ALL_THIRD_PARTY_REQUESTS)) == 1);
4293 String userAgentName = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.USER_AGENT));
4294 int fontSize = currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.FONT_SIZE));
4295 int swipeToRefreshInt = currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SWIPE_TO_REFRESH));
4296 int nightModeInt = currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.NIGHT_MODE));
4297 int displayWebpageImagesInt = currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.DISPLAY_IMAGES));
4298 pinnedSslCertificate = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.PINNED_SSL_CERTIFICATE)) == 1);
4299 pinnedSslIssuedToCName = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_TO_COMMON_NAME));
4300 pinnedSslIssuedToOName = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_TO_ORGANIZATION));
4301 pinnedSslIssuedToUName = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_TO_ORGANIZATIONAL_UNIT));
4302 pinnedSslIssuedByCName = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_BY_COMMON_NAME));
4303 pinnedSslIssuedByOName = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_BY_ORGANIZATION));
4304 pinnedSslIssuedByUName = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_BY_ORGANIZATIONAL_UNIT));
4305 pinnedIpAddresses = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.PINNED_IP_ADDRESSES)) == 1);
4306 pinnedHostIpAddresses = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.IP_ADDRESSES));
4308 // Set `nightMode` according to `nightModeInt`. If `nightModeInt` is `DomainsDatabaseHelper.NIGHT_MODE_SYSTEM_DEFAULT` the current setting from `sharedPreferences` will be used.
4309 switch (nightModeInt) {
4310 case DomainsDatabaseHelper.NIGHT_MODE_ENABLED:
4314 case DomainsDatabaseHelper.NIGHT_MODE_DISABLED:
4319 // Store the domain JavaScript status. This is used by the options menu night mode toggle.
4320 domainSettingsJavaScriptEnabled = javaScriptEnabled;
4322 // Enable JavaScript if night mode is enabled.
4324 javaScriptEnabled = true;
4327 // Set the pinned SSL certificate start date to `null` if the saved date `long` is 0.
4328 if (currentHostDomainSettingsCursor.getLong(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_START_DATE)) == 0) {
4329 pinnedSslStartDate = null;
4331 pinnedSslStartDate = new Date(currentHostDomainSettingsCursor.getLong(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_START_DATE)));
4334 // Set the pinned SSL certificate end date to `null` if the saved date `long` is 0.
4335 if (currentHostDomainSettingsCursor.getLong(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_END_DATE)) == 0) {
4336 pinnedSslEndDate = null;
4338 pinnedSslEndDate = new Date(currentHostDomainSettingsCursor.getLong(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_END_DATE)));
4341 // Close `currentHostDomainSettingsCursor`.
4342 currentHostDomainSettingsCursor.close();
4344 // Apply the domain settings.
4345 mainWebView.getSettings().setJavaScriptEnabled(javaScriptEnabled);
4346 cookieManager.setAcceptCookie(firstPartyCookiesEnabled);
4347 mainWebView.getSettings().setDomStorageEnabled(domStorageEnabled);
4349 // Apply the form data setting if the API < 26.
4350 if (Build.VERSION.SDK_INT < 26) {
4351 mainWebView.getSettings().setSaveFormData(saveFormDataEnabled);
4354 // Apply the font size.
4355 if (fontSize == 0) { // Apply the default font size.
4356 mainWebView.getSettings().setTextZoom(Integer.valueOf(defaultFontSizeString));
4357 } else { // Apply the specified font size.
4358 mainWebView.getSettings().setTextZoom(fontSize);
4361 // Set third-party cookies status if API >= 21.
4362 if (Build.VERSION.SDK_INT >= 21) {
4363 cookieManager.setAcceptThirdPartyCookies(mainWebView, thirdPartyCookiesEnabled);
4366 // 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.
4367 // <https://redmine.stoutner.com/issues/160>
4368 if (!urlIsLoading) {
4369 // Set the user agent.
4370 if (userAgentName.equals(getString(R.string.system_default_user_agent))) { // Use the system default user agent.
4371 // Get the array position of the default user agent name.
4372 int defaultUserAgentArrayPosition = userAgentNamesArray.getPosition(defaultUserAgentName);
4374 // Set the user agent according to the system default.
4375 switch (defaultUserAgentArrayPosition) {
4376 case UNRECOGNIZED_USER_AGENT: // The default user agent name is not on the canonical list.
4377 // This is probably because it was set in an older version of Privacy Browser before the switch to persistent user agent names.
4378 mainWebView.getSettings().setUserAgentString(defaultUserAgentName);
4381 case SETTINGS_WEBVIEW_DEFAULT_USER_AGENT:
4382 // Set the user agent to `""`, which uses the default value.
4383 mainWebView.getSettings().setUserAgentString("");
4386 case SETTINGS_CUSTOM_USER_AGENT:
4387 // Set the custom user agent.
4388 mainWebView.getSettings().setUserAgentString(defaultCustomUserAgentString);
4392 // Get the user agent string from the user agent data array
4393 mainWebView.getSettings().setUserAgentString(userAgentDataArray[defaultUserAgentArrayPosition]);
4395 } else { // Set the user agent according to the stored name.
4396 // Get the array position of the user agent name.
4397 int userAgentArrayPosition = userAgentNamesArray.getPosition(userAgentName);
4399 switch (userAgentArrayPosition) {
4400 case UNRECOGNIZED_USER_AGENT: // The user agent name contains a custom user agent.
4401 mainWebView.getSettings().setUserAgentString(userAgentName);
4404 case SETTINGS_WEBVIEW_DEFAULT_USER_AGENT:
4405 // Set the user agent to `""`, which uses the default value.
4406 mainWebView.getSettings().setUserAgentString("");
4410 // Get the user agent string from the user agent data array.
4411 mainWebView.getSettings().setUserAgentString(userAgentDataArray[userAgentArrayPosition]);
4415 // Store the applied user agent string, which is used in the View Source activity.
4416 appliedUserAgentString = mainWebView.getSettings().getUserAgentString();
4418 // Update the user agent change tracker.
4419 userAgentChanged = !appliedUserAgentString.equals(initialUserAgent);
4422 // Set swipe to refresh.
4423 switch (swipeToRefreshInt) {
4424 case DomainsDatabaseHelper.SWIPE_TO_REFRESH_SYSTEM_DEFAULT:
4425 // Set swipe to refresh according to the default.
4426 swipeRefreshLayout.setEnabled(defaultSwipeToRefresh);
4429 case DomainsDatabaseHelper.SWIPE_TO_REFRESH_ENABLED:
4430 // Enable swipe to refresh.
4431 swipeRefreshLayout.setEnabled(true);
4434 case DomainsDatabaseHelper.SWIPE_TO_REFRESH_DISABLED:
4435 // Disable swipe to refresh.
4436 swipeRefreshLayout.setEnabled(false);
4439 // Set the loading of webpage images.
4440 switch (displayWebpageImagesInt) {
4441 case DomainsDatabaseHelper.DISPLAY_WEBPAGE_IMAGES_SYSTEM_DEFAULT:
4442 mainWebView.getSettings().setLoadsImagesAutomatically(displayWebpageImages);
4445 case DomainsDatabaseHelper.DISPLAY_WEBPAGE_IMAGES_ENABLED:
4446 mainWebView.getSettings().setLoadsImagesAutomatically(true);
4449 case DomainsDatabaseHelper.DISPLAY_WEBPAGE_IMAGES_DISABLED:
4450 mainWebView.getSettings().setLoadsImagesAutomatically(false);
4454 // 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.
4456 urlAppBarRelativeLayout.setBackground(getResources().getDrawable(R.drawable.url_bar_background_dark_blue));
4458 urlAppBarRelativeLayout.setBackground(getResources().getDrawable(R.drawable.url_bar_background_light_green));
4460 } else { // The new URL does not have custom domain settings. Load the defaults.
4461 // Store the values from `sharedPreferences` in variables.
4462 javaScriptEnabled = sharedPreferences.getBoolean("javascript", false);
4463 firstPartyCookiesEnabled = sharedPreferences.getBoolean("first_party_cookies", false);
4464 thirdPartyCookiesEnabled = sharedPreferences.getBoolean("third_party_cookies", false);
4465 domStorageEnabled = sharedPreferences.getBoolean("dom_storage", false);
4466 saveFormDataEnabled = sharedPreferences.getBoolean("save_form_data", false); // Form data can be removed once the minimum API >= 26.
4467 easyListEnabled = sharedPreferences.getBoolean("easylist", true);
4468 easyPrivacyEnabled = sharedPreferences.getBoolean("easyprivacy", true);
4469 fanboysAnnoyanceListEnabled = sharedPreferences.getBoolean("fanboys_annoyance_list", true);
4470 fanboysSocialBlockingListEnabled = sharedPreferences.getBoolean("fanboys_social_blocking_list", true);
4471 ultraPrivacyEnabled = sharedPreferences.getBoolean("ultraprivacy", true);
4472 blockAllThirdPartyRequests = sharedPreferences.getBoolean("block_all_third_party_requests", false);
4474 // Set `javaScriptEnabled` to be `true` if `night_mode` is `true`.
4476 javaScriptEnabled = true;
4479 // Apply the default settings.
4480 mainWebView.getSettings().setJavaScriptEnabled(javaScriptEnabled);
4481 cookieManager.setAcceptCookie(firstPartyCookiesEnabled);
4482 mainWebView.getSettings().setDomStorageEnabled(domStorageEnabled);
4483 mainWebView.getSettings().setTextZoom(Integer.valueOf(defaultFontSizeString));
4484 swipeRefreshLayout.setEnabled(defaultSwipeToRefresh);
4486 // Apply the form data setting if the API < 26.
4487 if (Build.VERSION.SDK_INT < 26) {
4488 mainWebView.getSettings().setSaveFormData(saveFormDataEnabled);
4491 // Reset the pinned variables.
4492 domainSettingsDatabaseId = -1;
4493 pinnedSslCertificate = false;
4494 pinnedSslIssuedToCName = "";
4495 pinnedSslIssuedToOName = "";
4496 pinnedSslIssuedToUName = "";
4497 pinnedSslIssuedByCName = "";
4498 pinnedSslIssuedByOName = "";
4499 pinnedSslIssuedByUName = "";
4500 pinnedSslStartDate = null;
4501 pinnedSslEndDate = null;
4502 pinnedIpAddresses = false;
4503 pinnedHostIpAddresses = "";
4505 // Set third-party cookies status if API >= 21.
4506 if (Build.VERSION.SDK_INT >= 21) {
4507 cookieManager.setAcceptThirdPartyCookies(mainWebView, thirdPartyCookiesEnabled);
4510 // 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.
4511 // <https://redmine.stoutner.com/issues/160>
4512 if (!urlIsLoading) {
4513 // Get the array position of the user agent name.
4514 int userAgentArrayPosition = userAgentNamesArray.getPosition(defaultUserAgentName);
4516 // Set the user agent.
4517 switch (userAgentArrayPosition) {
4518 case UNRECOGNIZED_USER_AGENT: // The default user agent name is not on the canonical list.
4519 // This is probably because it was set in an older version of Privacy Browser before the switch to persistent user agent names.
4520 mainWebView.getSettings().setUserAgentString(defaultUserAgentName);
4523 case SETTINGS_WEBVIEW_DEFAULT_USER_AGENT:
4524 // Set the user agent to `""`, which uses the default value.
4525 mainWebView.getSettings().setUserAgentString("");
4528 case SETTINGS_CUSTOM_USER_AGENT:
4529 // Set the custom user agent.
4530 mainWebView.getSettings().setUserAgentString(defaultCustomUserAgentString);
4534 // Get the user agent string from the user agent data array
4535 mainWebView.getSettings().setUserAgentString(userAgentDataArray[userAgentArrayPosition]);
4538 // Store the applied user agent string, which is used in the View Source activity.
4539 appliedUserAgentString = mainWebView.getSettings().getUserAgentString();
4541 // Update the user agent change tracker.
4542 userAgentChanged = !appliedUserAgentString.equals(initialUserAgent);
4545 // Set the loading of webpage images.
4546 mainWebView.getSettings().setLoadsImagesAutomatically(displayWebpageImages);
4548 // Set a transparent background on `urlTextBox`. The deprecated `.getDrawable()` must be used until the minimum API >= 21.
4549 urlAppBarRelativeLayout.setBackgroundDrawable(getResources().getDrawable(R.color.transparent));
4552 // Close the domains database helper.
4553 domainsDatabaseHelper.close();
4555 // Update the privacy icons, but only if `mainMenu` has already been populated.
4556 if (mainMenu != null) {
4557 updatePrivacyIcons(true);
4561 // Reload the website if returning from the Domains activity.
4562 if (reloadWebsite) {
4563 mainWebView.reload();
4566 // Return the user agent changed status.
4567 return userAgentChanged;
4570 private void applyProxyThroughOrbot(boolean reloadWebsite) {
4571 // Get a handle for the shared preferences.
4572 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
4574 // Get the search preferences.
4575 String homepageString = sharedPreferences.getString("homepage", getString(R.string.homepage_default_value));
4576 String torHomepageString = sharedPreferences.getString("tor_homepage", getString(R.string.tor_homepage_default_value));
4577 String torSearchString = sharedPreferences.getString("tor_search", getString(R.string.tor_search_default_value));
4578 String torSearchCustomUrlString = sharedPreferences.getString("tor_search_custom_url", getString(R.string.tor_search_custom_url_default_value));
4579 String searchString = sharedPreferences.getString("search", getString(R.string.search_default_value));
4580 String searchCustomUrlString = sharedPreferences.getString("search_custom_url", getString(R.string.search_custom_url_default_value));
4582 // Set the homepage, search, and proxy options.
4583 if (proxyThroughOrbot) { // Set the Tor options.
4584 // Set `torHomepageString` as `homepage`.
4585 homepage = torHomepageString;
4587 // If formattedUrlString is null assign the homepage to it.
4588 if (formattedUrlString == null) {
4589 formattedUrlString = homepage;
4592 // Set the search URL.
4593 if (torSearchString.equals("Custom URL")) { // Get the custom URL string.
4594 searchURL = torSearchCustomUrlString;
4595 } else { // Use the string from the pre-built list.
4596 searchURL = torSearchString;
4599 // Set the proxy. `this` refers to the current activity where an `AlertDialog` might be displayed.
4600 OrbotProxyHelper.setProxy(getApplicationContext(), this, "localhost", "8118");
4602 // Set the `appBar` background to indicate proxying through Orbot is enabled. `this` refers to the context.
4604 appBar.setBackgroundDrawable(ContextCompat.getDrawable(this, R.color.dark_blue_30));
4606 appBar.setBackgroundDrawable(ContextCompat.getDrawable(this, R.color.blue_50));
4609 // Check to see if Orbot is ready.
4610 if (!orbotStatus.equals("ON")) { // Orbot is not ready.
4611 // Set `waitingForOrbot`.
4612 waitingForOrbot = true;
4614 // Disable the wide view port so that the waiting for Orbot text is displayed correctly.
4615 mainWebView.getSettings().setUseWideViewPort(false);
4617 // Load a waiting page. `null` specifies no encoding, which defaults to ASCII.
4618 mainWebView.loadData(waitingForOrbotHtmlString, "text/html", null);
4619 } else if (reloadWebsite) { // Orbot is ready and the website should be reloaded.
4620 // Reload the website.
4621 mainWebView.reload();
4623 } else { // Set the non-Tor options.
4624 // Set `homepageString` as `homepage`.
4625 homepage = homepageString;
4627 // If formattedUrlString is null assign the homepage to it.
4628 if (formattedUrlString == null) {
4629 formattedUrlString = homepage;
4632 // Set the search URL.
4633 if (searchString.equals("Custom URL")) { // Get the custom URL string.
4634 searchURL = searchCustomUrlString;
4635 } else { // Use the string from the pre-built list.
4636 searchURL = searchString;
4639 // Reset the proxy to default. The host is `""` and the port is `"0"`.
4640 OrbotProxyHelper.setProxy(getApplicationContext(), this, "", "0");
4642 // Set the default `appBar` background. `this` refers to the context.
4644 appBar.setBackgroundDrawable(ContextCompat.getDrawable(this, R.color.gray_900));
4646 appBar.setBackgroundDrawable(ContextCompat.getDrawable(this, R.color.gray_100));
4649 // Reset `waitingForOrbot.
4650 waitingForOrbot = false;
4652 // Reload the website if requested.
4653 if (reloadWebsite) {
4654 mainWebView.reload();
4659 private void updatePrivacyIcons(boolean runInvalidateOptionsMenu) {
4660 // Get handles for the menu items.
4661 MenuItem privacyMenuItem = mainMenu.findItem(R.id.toggle_javascript);
4662 MenuItem firstPartyCookiesMenuItem = mainMenu.findItem(R.id.toggle_first_party_cookies);
4663 MenuItem domStorageMenuItem = mainMenu.findItem(R.id.toggle_dom_storage);
4664 MenuItem refreshMenuItem = mainMenu.findItem(R.id.refresh);
4666 // Update the privacy icon.
4667 if (javaScriptEnabled) { // JavaScript is enabled.
4668 privacyMenuItem.setIcon(R.drawable.javascript_enabled);
4669 } else if (firstPartyCookiesEnabled) { // JavaScript is disabled but cookies are enabled.
4670 privacyMenuItem.setIcon(R.drawable.warning);
4671 } else { // All the dangerous features are disabled.
4672 privacyMenuItem.setIcon(R.drawable.privacy_mode);
4675 // Update the first-party cookies icon.
4676 if (firstPartyCookiesEnabled) { // First-party cookies are enabled.
4677 firstPartyCookiesMenuItem.setIcon(R.drawable.cookies_enabled);
4678 } else { // First-party cookies are disabled.
4680 firstPartyCookiesMenuItem.setIcon(R.drawable.cookies_disabled_dark);
4682 firstPartyCookiesMenuItem.setIcon(R.drawable.cookies_disabled_light);
4686 // Update the DOM storage icon.
4687 if (javaScriptEnabled && domStorageEnabled) { // Both JavaScript and DOM storage are enabled.
4688 domStorageMenuItem.setIcon(R.drawable.dom_storage_enabled);
4689 } else if (javaScriptEnabled) { // JavaScript is enabled but DOM storage is disabled.
4691 domStorageMenuItem.setIcon(R.drawable.dom_storage_disabled_dark);
4693 domStorageMenuItem.setIcon(R.drawable.dom_storage_disabled_light);
4695 } else { // JavaScript is disabled, so DOM storage is ghosted.
4697 domStorageMenuItem.setIcon(R.drawable.dom_storage_ghosted_dark);
4699 domStorageMenuItem.setIcon(R.drawable.dom_storage_ghosted_light);
4703 // Update the refresh icon.
4705 refreshMenuItem.setIcon(R.drawable.refresh_enabled_dark);
4707 refreshMenuItem.setIcon(R.drawable.refresh_enabled_light);
4710 // `invalidateOptionsMenu` calls `onPrepareOptionsMenu()` and redraws the icons in the `AppBar`.
4711 if (runInvalidateOptionsMenu) {
4712 invalidateOptionsMenu();
4716 private void openUrlWithExternalApp(String url) {
4717 // Create a download intent. Not specifying the action type will display the maximum number of options.
4718 Intent downloadIntent = new Intent();
4720 // Set the URI and the MIME type. Specifying `text/html` displays a good number of options.
4721 downloadIntent.setDataAndType(Uri.parse(url), "text/html");
4723 // Flag the intent to open in a new task.
4724 downloadIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
4726 // Show the chooser.
4727 startActivity(Intent.createChooser(downloadIntent, getString(R.string.open_with)));
4730 private void highlightUrlText() {
4731 // Only highlight the URL text if the box is not currently selected.
4732 if (!urlTextBox.hasFocus()) {
4733 // Get the URL string.
4734 String urlString = urlTextBox.getText().toString();
4736 // Highlight the URL according to the protocol.
4737 if (urlString.startsWith("file://")) { // This is a file URL.
4738 // De-emphasize only the protocol.
4739 urlTextBox.getText().setSpan(initialGrayColorSpan, 0, 7, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
4740 } else if (urlString.startsWith("content://")) {
4741 // De-emphasize only the protocol.
4742 urlTextBox.getText().setSpan(initialGrayColorSpan, 0, 10, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
4743 } else { // This is a web URL.
4744 // Get the index of the `/` immediately after the domain name.
4745 int endOfDomainName = urlString.indexOf("/", (urlString.indexOf("//") + 2));
4747 // Create a base URL string.
4750 // Get the base URL.
4751 if (endOfDomainName > 0) { // There is at least one character after the base URL.
4752 // Get the base URL.
4753 baseUrl = urlString.substring(0, endOfDomainName);
4754 } else { // There are no characters after the base URL.
4755 // Set the base URL to be the entire URL string.
4756 baseUrl = urlString;
4759 // Get the index of the last `.` in the domain.
4760 int lastDotIndex = baseUrl.lastIndexOf(".");
4762 // Get the index of the penultimate `.` in the domain.
4763 int penultimateDotIndex = baseUrl.lastIndexOf(".", lastDotIndex - 1);
4765 // Markup the beginning of the URL.
4766 if (urlString.startsWith("http://")) { // Highlight the protocol of connections that are not encrypted.
4767 urlTextBox.getText().setSpan(redColorSpan, 0, 7, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
4769 // De-emphasize subdomains.
4770 if (penultimateDotIndex > 0) { // There is more than one subdomain in the domain name.
4771 urlTextBox.getText().setSpan(initialGrayColorSpan, 7, penultimateDotIndex + 1, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
4773 } else if (urlString.startsWith("https://")) { // De-emphasize the protocol of connections that are encrypted.
4774 if (penultimateDotIndex > 0) { // There is more than one subdomain in the domain name.
4775 // De-emphasize the protocol and the additional subdomains.
4776 urlTextBox.getText().setSpan(initialGrayColorSpan, 0, penultimateDotIndex + 1, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
4777 } else { // There is only one subdomain in the domain name.
4778 // De-emphasize only the protocol.
4779 urlTextBox.getText().setSpan(initialGrayColorSpan, 0, 8, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
4783 // De-emphasize the text after the domain name.
4784 if (endOfDomainName > 0) {
4785 urlTextBox.getText().setSpan(finalGrayColorSpan, endOfDomainName, urlString.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
4791 private void loadBookmarksFolder() {
4792 // Update the bookmarks cursor with the contents of the bookmarks database for the current folder.
4793 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder);
4795 // Populate the bookmarks cursor adapter. `this` specifies the `Context`. `false` disables `autoRequery`.
4796 bookmarksCursorAdapter = new CursorAdapter(this, bookmarksCursor, false) {
4798 public View newView(Context context, Cursor cursor, ViewGroup parent) {
4799 // Inflate the individual item layout. `false` does not attach it to the root.
4800 return getLayoutInflater().inflate(R.layout.bookmarks_drawer_item_linearlayout, parent, false);
4804 public void bindView(View view, Context context, Cursor cursor) {
4805 // Get handles for the views.
4806 ImageView bookmarkFavoriteIcon = view.findViewById(R.id.bookmark_favorite_icon);
4807 TextView bookmarkNameTextView = view.findViewById(R.id.bookmark_name);
4809 // Get the favorite icon byte array from the cursor.
4810 byte[] favoriteIconByteArray = cursor.getBlob(cursor.getColumnIndex(BookmarksDatabaseHelper.FAVORITE_ICON));
4812 // Convert the byte array to a `Bitmap` beginning at the first byte and ending at the last.
4813 Bitmap favoriteIconBitmap = BitmapFactory.decodeByteArray(favoriteIconByteArray, 0, favoriteIconByteArray.length);
4815 // Display the bitmap in `bookmarkFavoriteIcon`.
4816 bookmarkFavoriteIcon.setImageBitmap(favoriteIconBitmap);
4818 // Get the bookmark name from the cursor and display it in `bookmarkNameTextView`.
4819 String bookmarkNameString = cursor.getString(cursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME));
4820 bookmarkNameTextView.setText(bookmarkNameString);
4822 // Make the font bold for folders.
4823 if (cursor.getInt(cursor.getColumnIndex(BookmarksDatabaseHelper.IS_FOLDER)) == 1) {
4824 bookmarkNameTextView.setTypeface(Typeface.DEFAULT_BOLD);
4825 } else { // Reset the font to default for normal bookmarks.
4826 bookmarkNameTextView.setTypeface(Typeface.DEFAULT);
4831 // Populate the `ListView` with the adapter.
4832 bookmarksListView.setAdapter(bookmarksCursorAdapter);
4834 // Set the bookmarks drawer title.
4835 if (currentBookmarksFolder.isEmpty()) {
4836 bookmarksTitleTextView.setText(R.string.bookmarks);
4838 bookmarksTitleTextView.setText(currentBookmarksFolder);
4842 private void openWithApp(String url) {
4843 // Create the open with intent with `ACTION_VIEW`.
4844 Intent openWithAppIntent = new Intent(Intent.ACTION_VIEW);
4846 // Set the URI but not the MIME type. This should open all available apps.
4847 openWithAppIntent.setData(Uri.parse(url));
4849 // Flag the intent to open in a new task.
4850 openWithAppIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
4852 // Show the chooser.
4853 startActivity(openWithAppIntent);
4856 private void openWithBrowser(String url) {
4857 // Create the open with intent with `ACTION_VIEW`.
4858 Intent openWithBrowserIntent = new Intent(Intent.ACTION_VIEW);
4860 // Set the URI and the MIME type. `"text/html"` should load browser options.
4861 openWithBrowserIntent.setDataAndType(Uri.parse(url), "text/html");
4863 // Flag the intent to open in a new task.
4864 openWithBrowserIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
4866 // Show the chooser.
4867 startActivity(openWithBrowserIntent);
4870 private static void checkPinnedMismatch() {
4871 if ((pinnedSslCertificate || pinnedIpAddresses) && !ignorePinnedDomainInformation) {
4872 // Initialize the current SSL certificate variables.
4873 String currentWebsiteIssuedToCName = "";
4874 String currentWebsiteIssuedToOName = "";
4875 String currentWebsiteIssuedToUName = "";
4876 String currentWebsiteIssuedByCName = "";
4877 String currentWebsiteIssuedByOName = "";
4878 String currentWebsiteIssuedByUName = "";
4879 Date currentWebsiteSslStartDate = null;
4880 Date currentWebsiteSslEndDate = null;
4883 // Extract the individual pieces of information from the current website SSL certificate if it is not null.
4884 if (sslCertificate != null) {
4885 currentWebsiteIssuedToCName = sslCertificate.getIssuedTo().getCName();
4886 currentWebsiteIssuedToOName = sslCertificate.getIssuedTo().getOName();
4887 currentWebsiteIssuedToUName = sslCertificate.getIssuedTo().getUName();
4888 currentWebsiteIssuedByCName = sslCertificate.getIssuedBy().getCName();
4889 currentWebsiteIssuedByOName = sslCertificate.getIssuedBy().getOName();
4890 currentWebsiteIssuedByUName = sslCertificate.getIssuedBy().getUName();
4891 currentWebsiteSslStartDate = sslCertificate.getValidNotBeforeDate();
4892 currentWebsiteSslEndDate = sslCertificate.getValidNotAfterDate();
4895 // 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`.
4896 String currentWebsiteSslStartDateString = "";
4897 String currentWebsiteSslEndDateString = "";
4898 String pinnedSslStartDateString = "";
4899 String pinnedSslEndDateString = "";
4901 // Convert the `Dates` to `Strings` if they are not `null`.
4902 if (currentWebsiteSslStartDate != null) {
4903 currentWebsiteSslStartDateString = currentWebsiteSslStartDate.toString();
4906 if (currentWebsiteSslEndDate != null) {
4907 currentWebsiteSslEndDateString = currentWebsiteSslEndDate.toString();
4910 if (pinnedSslStartDate != null) {
4911 pinnedSslStartDateString = pinnedSslStartDate.toString();
4914 if (pinnedSslEndDate != null) {
4915 pinnedSslEndDateString = pinnedSslEndDate.toString();
4918 // Check to see if the pinned information matches the current information.
4919 if ((pinnedIpAddresses && !currentHostIpAddresses.equals(pinnedHostIpAddresses)) || (pinnedSslCertificate && (!currentWebsiteIssuedToCName.equals(pinnedSslIssuedToCName) ||
4920 !currentWebsiteIssuedToOName.equals(pinnedSslIssuedToOName) || !currentWebsiteIssuedToUName.equals(pinnedSslIssuedToUName) ||
4921 !currentWebsiteIssuedByCName.equals(pinnedSslIssuedByCName) || !currentWebsiteIssuedByOName.equals(pinnedSslIssuedByOName) ||
4922 !currentWebsiteIssuedByUName.equals(pinnedSslIssuedByUName) || !currentWebsiteSslStartDateString.equals(pinnedSslStartDateString) ||
4923 !currentWebsiteSslEndDateString.equals(pinnedSslEndDateString)))) {
4925 // Get a handle for the pinned mismatch alert dialog.
4926 AppCompatDialogFragment pinnedMismatchDialogFragment = PinnedMismatchDialog.displayDialog(pinnedSslCertificate, pinnedIpAddresses);
4928 // Show the pinned mismatch alert dialog.
4929 pinnedMismatchDialogFragment.show(supportFragmentManager, "Pinned Mismatch");
4934 // This must run asynchronously because it involves a network request. `String` declares the parameters. `Void` does not declare progress units. `String` contains the results.
4935 private static class GetHostIpAddresses extends AsyncTask<String, Void, String> {
4936 // The weak references are used to determine if the activity have disappeared while the AsyncTask is running.
4937 private final WeakReference<Activity> activityWeakReference;
4939 GetHostIpAddresses(Activity activity) {
4940 // Populate the weak references.
4941 activityWeakReference = new WeakReference<>(activity);
4944 // `onPreExecute()` operates on the UI thread.
4946 protected void onPreExecute() {
4947 // Get a handle for the activity.
4948 Activity activity = activityWeakReference.get();
4950 // Abort if the activity is gone.
4951 if ((activity == null) || activity.isFinishing()) {
4955 // Set the getting IP addresses tracker.
4956 gettingIpAddresses = true;
4961 protected String doInBackground(String... domainName) {
4962 // Get a handle for the activity.
4963 Activity activity = activityWeakReference.get();
4965 // Abort if the activity is gone.
4966 if ((activity == null) || activity.isFinishing()) {
4967 // Return an empty spannable string builder.
4971 // Initialize an IP address string builder.
4972 StringBuilder ipAddresses = new StringBuilder();
4974 // Get an array with the IP addresses for the host.
4976 // Get an array with all the IP addresses for the domain.
4977 InetAddress[] inetAddressesArray = InetAddress.getAllByName(domainName[0]);
4979 // Add each IP address to the string builder.
4980 for (InetAddress inetAddress : inetAddressesArray) {
4981 if (ipAddresses.length() == 0) { // This is the first IP address.
4982 // Add the IP address to the string builder.
4983 ipAddresses.append(inetAddress.getHostAddress());
4984 } else { // This is not the first IP address.
4985 // Add a line break to the string builder first.
4986 ipAddresses.append("\n");
4988 // Add the IP address to the string builder.
4989 ipAddresses.append(inetAddress.getHostAddress());
4992 } catch (UnknownHostException exception) {
4996 // Return the string.
4997 return ipAddresses.toString();
5000 // `onPostExecute()` operates on the UI thread.
5002 protected void onPostExecute(String ipAddresses) {
5003 // Get a handle for the activity.
5004 Activity activity = activityWeakReference.get();
5006 // Abort if the activity is gone.
5007 if ((activity == null) || activity.isFinishing()) {
5011 // Store the IP addresses.
5012 currentHostIpAddresses = ipAddresses;
5014 if (!urlIsLoading) {
5015 checkPinnedMismatch();
5018 // Reset the getting IP addresses tracker.
5019 gettingIpAddresses = false;