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.content.ContextCompat;
65 // `ShortcutInfoCompat`, `ShortcutManagerCompat`, and `IconCompat` can be switched to the non-compat version once API >= 26.
66 import android.support.v4.content.pm.ShortcutInfoCompat;
67 import android.support.v4.content.pm.ShortcutManagerCompat;
68 import android.support.v4.graphics.drawable.IconCompat;
69 import android.support.v4.view.GravityCompat;
70 import android.support.v4.widget.DrawerLayout;
71 import android.support.v4.widget.SwipeRefreshLayout;
72 import android.support.v7.app.ActionBar;
73 import android.support.v7.app.ActionBarDrawerToggle;
74 import android.support.v7.app.AppCompatActivity;
75 import android.support.v7.app.AppCompatDialogFragment;
76 import android.support.v7.widget.Toolbar;
77 import android.text.Editable;
78 import android.text.Spanned;
79 import android.text.TextWatcher;
80 import android.text.style.ForegroundColorSpan;
81 import android.util.Patterns;
82 import android.view.ContextMenu;
83 import android.view.GestureDetector;
84 import android.view.KeyEvent;
85 import android.view.Menu;
86 import android.view.MenuItem;
87 import android.view.MotionEvent;
88 import android.view.View;
89 import android.view.ViewGroup;
90 import android.view.WindowManager;
91 import android.view.inputmethod.InputMethodManager;
92 import android.webkit.CookieManager;
93 import android.webkit.HttpAuthHandler;
94 import android.webkit.SslErrorHandler;
95 import android.webkit.ValueCallback;
96 import android.webkit.WebBackForwardList;
97 import android.webkit.WebChromeClient;
98 import android.webkit.WebResourceResponse;
99 import android.webkit.WebSettings;
100 import android.webkit.WebStorage;
101 import android.webkit.WebView;
102 import android.webkit.WebViewClient;
103 import android.webkit.WebViewDatabase;
104 import android.widget.ArrayAdapter;
105 import android.widget.CursorAdapter;
106 import android.widget.EditText;
107 import android.widget.FrameLayout;
108 import android.widget.ImageView;
109 import android.widget.LinearLayout;
110 import android.widget.ListView;
111 import android.widget.ProgressBar;
112 import android.widget.RadioButton;
113 import android.widget.RelativeLayout;
114 import android.widget.TextView;
116 import com.stoutner.privacybrowser.BuildConfig;
117 import com.stoutner.privacybrowser.R;
118 import com.stoutner.privacybrowser.dialogs.AdConsentDialog;
119 import com.stoutner.privacybrowser.dialogs.CreateBookmarkDialog;
120 import com.stoutner.privacybrowser.dialogs.CreateBookmarkFolderDialog;
121 import com.stoutner.privacybrowser.dialogs.CreateHomeScreenShortcutDialog;
122 import com.stoutner.privacybrowser.dialogs.DownloadImageDialog;
123 import com.stoutner.privacybrowser.dialogs.DownloadLocationPermissionDialog;
124 import com.stoutner.privacybrowser.dialogs.EditBookmarkDialog;
125 import com.stoutner.privacybrowser.dialogs.EditBookmarkFolderDialog;
126 import com.stoutner.privacybrowser.dialogs.HttpAuthenticationDialog;
127 import com.stoutner.privacybrowser.dialogs.PinnedMismatchDialog;
128 import com.stoutner.privacybrowser.dialogs.UrlHistoryDialog;
129 import com.stoutner.privacybrowser.dialogs.ViewSslCertificateDialog;
130 import com.stoutner.privacybrowser.helpers.AdHelper;
131 import com.stoutner.privacybrowser.helpers.BlockListHelper;
132 import com.stoutner.privacybrowser.helpers.BookmarksDatabaseHelper;
133 import com.stoutner.privacybrowser.helpers.DomainsDatabaseHelper;
134 import com.stoutner.privacybrowser.helpers.OrbotProxyHelper;
135 import com.stoutner.privacybrowser.dialogs.DownloadFileDialog;
136 import com.stoutner.privacybrowser.dialogs.SslCertificateErrorDialog;
138 import java.io.ByteArrayInputStream;
139 import java.io.ByteArrayOutputStream;
141 import java.io.IOException;
142 import java.io.UnsupportedEncodingException;
143 import java.lang.ref.WeakReference;
144 import java.net.InetAddress;
145 import java.net.MalformedURLException;
147 import java.net.URLDecoder;
148 import java.net.URLEncoder;
149 import java.net.UnknownHostException;
150 import java.util.ArrayList;
151 import java.util.Date;
152 import java.util.HashMap;
153 import java.util.HashSet;
154 import java.util.List;
155 import java.util.Map;
156 import java.util.Set;
158 // AppCompatActivity from android.support.v7.app.AppCompatActivity must be used to have access to the SupportActionBar until the minimum API is >= 21.
159 public class MainWebViewActivity extends AppCompatActivity implements CreateBookmarkDialog.CreateBookmarkListener, CreateBookmarkFolderDialog.CreateBookmarkFolderListener,
160 CreateHomeScreenShortcutDialog.CreateHomeScreenShortcutListener, DownloadFileDialog.DownloadFileListener, DownloadImageDialog.DownloadImageListener,
161 DownloadLocationPermissionDialog.DownloadLocationPermissionDialogListener, EditBookmarkDialog.EditBookmarkListener, EditBookmarkFolderDialog.EditBookmarkFolderListener,
162 HttpAuthenticationDialog.HttpAuthenticationListener, NavigationView.OnNavigationItemSelectedListener, PinnedMismatchDialog.PinnedMismatchListener,
163 SslCertificateErrorDialog.SslCertificateErrorListener, UrlHistoryDialog.UrlHistoryListener {
165 // `darkTheme` is public static so it can be accessed from everywhere.
166 public static boolean darkTheme;
168 // `allowScreenshots` is public static so it can be accessed from everywhere. It is also used in `onCreate()`.
169 public static boolean allowScreenshots;
171 // `favoriteIconBitmap` is public static so it can be accessed from `CreateHomeScreenShortcutDialog`, `BookmarksActivity`, `BookmarksDatabaseViewActivity`, `CreateBookmarkDialog`,
172 // `CreateBookmarkFolderDialog`, `EditBookmarkDialog`, `EditBookmarkFolderDialog`, `EditBookmarkDatabaseViewDialog`, and `ViewSslCertificateDialog`. It is also used in `onCreate()`,
173 // `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onCreateHomeScreenShortcutCreate()`, `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and `applyDomainSettings()`.
174 public static Bitmap favoriteIconBitmap;
176 // `favoriteIconDefaultBitmap` public static so it can be accessed from `PinnedMismatchDialog`. It is also used in `onCreate()` and `applyDomainSettings`.
177 public static Bitmap favoriteIconDefaultBitmap;
179 // `formattedUrlString` is public static so it can be accessed from `AddDomainDialog`, `BookmarksActivity`, `DomainSettingsFragment`, `CreateBookmarkDialog`, and `PinnedMismatchDialog`.
180 // It is also used in `onCreate()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onCreateHomeScreenShortcutCreate()`, `loadUrlFromTextBox()`, and `applyProxyThroughOrbot()`.
181 public static String formattedUrlString;
183 // `sslCertificate` is public static so it can be accessed from `DomainsActivity`, `DomainsListFragment`, `DomainSettingsFragment`, `PinnedMismatchDialog`, and `ViewSslCertificateDialog`.
184 // It is also used in `onCreate()`.
185 public static SslCertificate sslCertificate;
187 // `currentHostIpAddresses` is public static so it can be accessed from `DomainSettingsFragment` and `ViewSslCertificateDialog`.
188 // It is also used in `onCreate()` and `GetHostIpAddresses()`.
189 public static String currentHostIpAddresses;
191 // `orbotStatus` is public static so it can be accessed from `OrbotProxyHelper`. It is also used in `onCreate()`, `onResume()`, and `applyProxyThroughOrbot()`.
192 public static String orbotStatus;
194 // `webViewTitle` is public static so it can be accessed from `CreateBookmarkDialog` and `CreateHomeScreenShortcutDialog`. It is also used in `onCreate()`.
195 public static String webViewTitle;
197 // `appliedUserAgentString` is public static so it can be accessed from `ViewSourceActivity`. It is also used in `applyDomainSettings()`.
198 public static String appliedUserAgentString;
200 // `reloadOnRestart` is public static so it can be accessed from `SettingsFragment`. It is also used in `onRestart()`
201 public static boolean reloadOnRestart;
203 // `reloadUrlOnRestart` is public static so it can be accessed from `SettingsFragment` and `BookmarksActivity`. It is also used in `onRestart()`.
204 public static boolean loadUrlOnRestart;
206 // `restartFromBookmarksActivity` is public static so it can be accessed from `BookmarksActivity`. It is also used in `onRestart()`.
207 public static boolean restartFromBookmarksActivity;
209 // The block list versions are public static so they can be accessed from `AboutTabFragment`. They are also used in `onCreate()`.
210 public static String easyListVersion;
211 public static String easyPrivacyVersion;
212 public static String fanboysAnnoyanceVersion;
213 public static String fanboysSocialVersion;
214 public static String ultraPrivacyVersion;
216 // The request items are public static so they can be accessed by `BlockListHelper`, `RequestsArrayAdapter`, and `ViewRequestsDialog`. They are also used in `onCreate()` and `onPrepareOptionsMenu()`.
217 public static List<String[]> resourceRequests;
218 public static String[] whiteListResultStringArray;
219 private int blockedRequests;
220 private int easyListBlockedRequests;
221 private int easyPrivacyBlockedRequests;
222 private int fanboysAnnoyanceListBlockedRequests;
223 private int fanboysSocialBlockingListBlockedRequests;
224 private int ultraPrivacyBlockedRequests;
225 private int thirdPartyBlockedRequests;
227 public final static int REQUEST_DISPOSITION = 0;
228 public final static int REQUEST_URL = 1;
229 public final static int REQUEST_BLOCKLIST = 2;
230 public final static int REQUEST_SUBLIST = 3;
231 public final static int REQUEST_BLOCKLIST_ENTRIES = 4;
232 public final static int REQUEST_BLOCKLIST_ORIGINAL_ENTRY = 5;
234 public final static int REQUEST_DEFAULT = 0;
235 public final static int REQUEST_ALLOWED = 1;
236 public final static int REQUEST_THIRD_PARTY = 2;
237 public final static int REQUEST_BLOCKED = 3;
239 public final static int MAIN_WHITELIST = 1;
240 public final static int FINAL_WHITELIST = 2;
241 public final static int DOMAIN_WHITELIST = 3;
242 public final static int DOMAIN_INITIAL_WHITELIST = 4;
243 public final static int DOMAIN_FINAL_WHITELIST = 5;
244 public final static int THIRD_PARTY_WHITELIST = 6;
245 public final static int THIRD_PARTY_DOMAIN_WHITELIST = 7;
246 public final static int THIRD_PARTY_DOMAIN_INITIAL_WHITELIST = 8;
248 public final static int MAIN_BLACKLIST = 9;
249 public final static int INITIAL_BLACKLIST = 10;
250 public final static int FINAL_BLACKLIST = 11;
251 public final static int DOMAIN_BLACKLIST = 12;
252 public final static int DOMAIN_INITIAL_BLACKLIST = 13;
253 public final static int DOMAIN_FINAL_BLACKLIST = 14;
254 public final static int DOMAIN_REGULAR_EXPRESSION_BLACKLIST = 15;
255 public final static int THIRD_PARTY_BLACKLIST = 16;
256 public final static int THIRD_PARTY_INITIAL_BLACKLIST = 17;
257 public final static int THIRD_PARTY_DOMAIN_BLACKLIST = 18;
258 public final static int THIRD_PARTY_DOMAIN_INITIAL_BLACKLIST = 19;
259 public final static int THIRD_PARTY_REGULAR_EXPRESSION_BLACKLIST = 20;
260 public final static int THIRD_PARTY_DOMAIN_REGULAR_EXPRESSION_BLACKLIST = 21;
261 public final static int REGULAR_EXPRESSION_BLACKLIST = 22;
263 // `blockAllThirdPartyRequests` is public static so it can be accessed from `RequestsActivity`.
264 // It is also used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, and `applyAppSettings()`
265 public static boolean blockAllThirdPartyRequests;
267 // `currentBookmarksFolder` is public static so it can be accessed from `BookmarksActivity`. It is also used in `onCreate()`, `onBackPressed()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`,
268 // `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and `loadBookmarksFolder()`.
269 public static String currentBookmarksFolder;
271 // `domainSettingsDatabaseId` is public static so it can be accessed from `PinnedMismatchDialog`. It is also used in `onCreate()`, `onOptionsItemSelected()`, and `applyDomainSettings()`.
272 public static int domainSettingsDatabaseId;
274 // The pinned variables are public static so they can be accessed from `PinnedMismatchDialog`. They are also used in `onCreate()` and `applyDomainSettings()`.
275 public static String pinnedSslIssuedToCName;
276 public static String pinnedSslIssuedToOName;
277 public static String pinnedSslIssuedToUName;
278 public static String pinnedSslIssuedByCName;
279 public static String pinnedSslIssuedByOName;
280 public static String pinnedSslIssuedByUName;
281 public static Date pinnedSslStartDate;
282 public static Date pinnedSslEndDate;
283 public static String pinnedHostIpAddresses;
285 // The user agent constants are public static so they can be accessed from `SettingsFragment`, `DomainsActivity`, and `DomainSettingsFragment`.
286 public final static int UNRECOGNIZED_USER_AGENT = -1;
287 public final static int SETTINGS_WEBVIEW_DEFAULT_USER_AGENT = 1;
288 public final static int SETTINGS_CUSTOM_USER_AGENT = 12;
289 public final static int DOMAINS_SYSTEM_DEFAULT_USER_AGENT = 0;
290 public final static int DOMAINS_WEBVIEW_DEFAULT_USER_AGENT = 2;
291 public final static int DOMAINS_CUSTOM_USER_AGENT = 13;
294 // `appBar` is used in `onCreate()`, `onOptionsItemSelected()`, `closeFindOnPage()`, `applyAppSettings()`, and `applyProxyThroughOrbot()`.
295 private ActionBar appBar;
297 // `navigatingHistory` is used in `onCreate()`, `onNavigationItemSelected()`, `onSslMismatchBack()`, and `applyDomainSettings()`.
298 private boolean navigatingHistory;
300 // `drawerLayout` is used in `onCreate()`, `onNewIntent()`, `onBackPressed()`, and `onRestart()`.
301 private DrawerLayout drawerLayout;
303 // `rootCoordinatorLayout` is used in `onCreate()` and `applyAppSettings()`.
304 private CoordinatorLayout rootCoordinatorLayout;
306 // `mainWebView` is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onRestart()`, `onCreateContextMenu()`, `findPreviousOnPage()`,
307 // `findNextOnPage()`, `closeFindOnPage()`, `loadUrlFromTextBox()`, `onSslMismatchBack()`, and `applyProxyThroughOrbot()`.
308 private WebView mainWebView;
310 // `fullScreenVideoFrameLayout` is used in `onCreate()` and `onConfigurationChanged()`.
311 private FrameLayout fullScreenVideoFrameLayout;
313 // `swipeRefreshLayout` is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, and `onRestart()`.
314 private SwipeRefreshLayout swipeRefreshLayout;
316 // `urlAppBarRelativeLayout` is used in `onCreate()` and `applyDomainSettings()`.
317 private RelativeLayout urlAppBarRelativeLayout;
319 // `favoriteIconImageView` is used in `onCreate()` and `applyDomainSettings()`
320 private ImageView favoriteIconImageView;
322 // `cookieManager` is used in `onCreate()`, `onOptionsItemSelected()`, and `onNavigationItemSelected()`, `loadUrlFromTextBox()`, `onDownloadImage()`, `onDownloadFile()`, and `onRestart()`.
323 private CookieManager cookieManager;
325 // `customHeader` is used in `onCreate()`, `onOptionsItemSelected()`, `onCreateContextMenu()`, and `loadUrl()`.
326 private final Map<String, String> customHeaders = new HashMap<>();
328 // `javaScriptEnabled` is also used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, `applyDomainSettings()`, and `updatePrivacyIcons()`.
329 private boolean javaScriptEnabled;
331 // `firstPartyCookiesEnabled` is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, `onDownloadImage()`, `onDownloadFile()`, and `applyDomainSettings()`.
332 private boolean firstPartyCookiesEnabled;
334 // `thirdPartyCookiesEnabled` used in `onCreate()`, `onPrepareOptionsMenu()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, and `applyDomainSettings()`.
335 private boolean thirdPartyCookiesEnabled;
337 // `domStorageEnabled` is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, and `applyDomainSettings()`.
338 private boolean domStorageEnabled;
340 // `saveFormDataEnabled` is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, and `applyDomainSettings()`. It can be removed once the minimum API >= 26.
341 private boolean saveFormDataEnabled;
343 // `nightMode` is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, and `applyDomainSettings()`.
344 private boolean nightMode;
346 // 'homepage' is used in `onCreate()`, `onNavigationItemSelected()`, and `applyProxyThroughOrbot()`.
347 private String homepage;
349 // `searchURL` is used in `loadURLFromTextBox()` and `applyProxyThroughOrbot()`.
350 private String searchURL;
352 // `mainMenu` is used in `onCreateOptionsMenu()` and `updatePrivacyIcons()`.
353 private Menu mainMenu;
355 // `refreshMenuItem` is used in `onCreate()` and `onCreateOptionsMenu()`.
356 private MenuItem refreshMenuItem;
358 // The blocklist menu items are used in `onCreate()`, `onCreateOptionsMenu()`, and `onPrepareOptionsMenu()`.
359 private MenuItem blocklistsMenuItem;
360 private MenuItem easyListMenuItem;
361 private MenuItem easyPrivacyMenuItem;
362 private MenuItem fanboysAnnoyanceListMenuItem;
363 private MenuItem fanboysSocialBlockingListMenuItem;
364 private MenuItem ultraPrivacyMenuItem;
365 private MenuItem blockAllThirdPartyRequestsMenuItem;
367 // The blocklist variables are used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, and `applyAppSettings()`.
368 private boolean easyListEnabled;
369 private boolean easyPrivacyEnabled;
370 private boolean fanboysAnnoyanceListEnabled;
371 private boolean fanboysSocialBlockingListEnabled;
372 private boolean ultraPrivacyEnabled;
374 // `webViewDefaultUserAgent` is used in `onCreate()` and `onPrepareOptionsMenu()`.
375 private String webViewDefaultUserAgent;
377 // `defaultCustomUserAgentString` is used in `onPrepareOptionsMenu()` and `applyDomainSettings()`.
378 private String defaultCustomUserAgentString;
380 // `privacyBrowserRuntime` is used in `onCreate()`, `onOptionsItemSelected()`, and `applyAppSettings()`.
381 private Runtime privacyBrowserRuntime;
383 // `proxyThroughOrbot` is used in `onRestart()`, `onOptionsItemSelected()`, `applyAppSettings()`, and `applyProxyThroughOrbot()`.
384 private boolean proxyThroughOrbot;
386 // `incognitoModeEnabled` is used in `onCreate()` and `applyAppSettings()`.
387 private boolean incognitoModeEnabled;
389 // `fullScreenBrowsingModeEnabled` is used in `onCreate()` and `applyAppSettings()`.
390 private boolean fullScreenBrowsingModeEnabled;
392 // `inFullScreenBrowsingMode` is used in `onCreate()`, `onConfigurationChanged()`, and `applyAppSettings()`.
393 private boolean inFullScreenBrowsingMode;
395 // `hideSystemBarsOnFullscreen` is used in `onCreate()` and `applyAppSettings()`.
396 private boolean hideSystemBarsOnFullscreen;
398 // `translucentNavigationBarOnFullscreen` is used in `onCreate()` and `applyAppSettings()`.
399 private boolean translucentNavigationBarOnFullscreen;
401 // `reapplyDomainSettingsOnRestart` is used in `onCreate()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onRestart()`, and `onAddDomain()`, .
402 private boolean reapplyDomainSettingsOnRestart;
404 // `reapplyAppSettingsOnRestart` is used in `onNavigationItemSelected()` and `onRestart()`.
405 private boolean reapplyAppSettingsOnRestart;
407 // `displayingFullScreenVideo` is used in `onCreate()` and `onResume()`.
408 private boolean displayingFullScreenVideo;
410 // `downloadWithExternalApp` is used in `onCreate()`, `onCreateContextMenu()`, and `applyDomainSettings()`.
411 private boolean downloadWithExternalApp;
413 // `currentDomainName` is used in `onCreate()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onAddDomain()`, and `applyDomainSettings()`.
414 private String currentDomainName;
416 // `pinnedDomainSslCertificate` is used in `onCreate()` and `applyDomainSettings()`.
417 private boolean pinnedSslCertificate;
419 // `pinnedIpAddress` is used in `onCreate()` and `applyDomainSettings()`.
420 private boolean pinnedIpAddresses;
422 // `ignorePinnedDomainInformation` is used in `onCreate()`, `onSslMismatchProceed()`, and `applyDomainSettings()`.
423 private boolean ignorePinnedDomainInformation;
425 // `orbotStatusBroadcastReceiver` is used in `onCreate()` and `onDestroy()`.
426 private BroadcastReceiver orbotStatusBroadcastReceiver;
428 // `waitingForOrbot` is used in `onCreate()`, `onResume()`, and `applyProxyThroughOrbot()`.
429 private boolean waitingForOrbot;
431 // `domainSettingsApplied` is used in `prepareOptionsMenu()` and `applyDomainSettings()`.
432 private boolean domainSettingsApplied;
434 // `domainSettingsJavaScriptEnabled` is used in `onOptionsItemSelected()` and `applyDomainSettings()`.
435 private Boolean domainSettingsJavaScriptEnabled;
437 // `waitingForOrbotHtmlString` is used in `onCreate()` and `applyProxyThroughOrbot()`.
438 private String waitingForOrbotHtmlString;
440 // `privateDataDirectoryString` is used in `onCreate()`, `onOptionsItemSelected()`, and `onNavigationItemSelected()`.
441 private String privateDataDirectoryString;
443 // `findOnPageLinearLayout` is used in `onCreate()`, `onOptionsItemSelected()`, and `closeFindOnPage()`.
444 private LinearLayout findOnPageLinearLayout;
446 // `findOnPageEditText` is used in `onCreate()`, `onOptionsItemSelected()`, and `closeFindOnPage()`.
447 private EditText findOnPageEditText;
449 // `displayAdditionalAppBarIcons` is used in `onCreate()` and `onCreateOptionsMenu()`.
450 private boolean displayAdditionalAppBarIcons;
452 // `drawerToggle` is used in `onCreate()`, `onPostCreate()`, `onConfigurationChanged()`, `onNewIntent()`, and `onNavigationItemSelected()`.
453 private ActionBarDrawerToggle drawerToggle;
455 // `supportAppBar` is used in `onCreate()`, `onOptionsItemSelected()`, and `closeFindOnPage()`.
456 private Toolbar supportAppBar;
458 // `urlTextBox` is used in `onCreate()`, `onOptionsItemSelected()`, `loadUrlFromTextBox()`, `loadUrl()`, and `highlightUrlText()`.
459 private EditText urlTextBox;
461 // The color spans are used in `onCreate()` and `highlightUrlText()`.
462 private ForegroundColorSpan redColorSpan;
463 private ForegroundColorSpan initialGrayColorSpan;
464 private ForegroundColorSpan finalGrayColorSpan;
466 // The drawer header padding variables are used in `onCreate()` and `onConfigurationChanged()`.
467 private int drawerHeaderPaddingLeftAndRight;
468 private int drawerHeaderPaddingTop;
469 private int drawerHeaderPaddingBottom;
471 // `sslErrorHandler` is used in `onCreate()`, `onSslErrorCancel()`, and `onSslErrorProceed`.
472 private SslErrorHandler sslErrorHandler;
474 // `httpAuthHandler` is used in `onCreate()`, `onHttpAuthenticationCancel()`, and `onHttpAuthenticationProceed()`.
475 private static HttpAuthHandler httpAuthHandler;
477 // `inputMethodManager` is used in `onOptionsItemSelected()`, `loadUrlFromTextBox()`, and `closeFindOnPage()`.
478 private InputMethodManager inputMethodManager;
480 // `mainWebViewRelativeLayout` is used in `onCreate()` and `onNavigationItemSelected()`.
481 private RelativeLayout mainWebViewRelativeLayout;
483 // `urlIsLoading` is used in `onCreate()`, `onCreateOptionsMenu()`, `loadUrl()`, and `applyDomainSettings()`.
484 private boolean urlIsLoading;
486 // `bookmarksDatabaseHelper` is used in `onCreate()`, `onDestroy`, `onOptionsItemSelected()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`,
487 // and `loadBookmarksFolder()`.
488 private BookmarksDatabaseHelper bookmarksDatabaseHelper;
490 // `bookmarksListView` is used in `onCreate()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, and `loadBookmarksFolder()`.
491 private ListView bookmarksListView;
493 // `bookmarksTitleTextView` is used in `onCreate()` and `loadBookmarksFolder()`.
494 private TextView bookmarksTitleTextView;
496 // `bookmarksCursor` is used in `onDestroy()`, `onOptionsItemSelected()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and `loadBookmarksFolder()`.
497 private Cursor bookmarksCursor;
499 // `bookmarksCursorAdapter` is used in `onCreateBookmark()`, `onCreateBookmarkFolder()` `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and `loadBookmarksFolder()`.
500 private CursorAdapter bookmarksCursorAdapter;
502 // `oldFolderNameString` is used in `onCreate()` and `onSaveEditBookmarkFolder()`.
503 private String oldFolderNameString;
505 // `fileChooserCallback` is used in `onCreate()` and `onActivityResult()`.
506 private ValueCallback<Uri[]> fileChooserCallback;
508 // The download strings are used in `onCreate()` and `onRequestPermissionResult()`.
509 private String downloadUrl;
510 private String downloadContentDisposition;
511 private long downloadContentLength;
513 // `downloadImageUrl` is used in `onCreateContextMenu()` and `onRequestPermissionResult()`.
514 private String downloadImageUrl;
516 // The user agent variables are used in `onCreate()` and `applyDomainSettings()`.
517 private ArrayAdapter<CharSequence> userAgentNamesArray;
518 private String[] userAgentDataArray;
520 // The request codes are used in `onCreate()`, `onCreateContextMenu()`, `onCloseDownloadLocationPermissionDialog()`, and `onRequestPermissionResult()`.
521 private final int DOWNLOAD_FILE_REQUEST_CODE = 1;
522 private final int DOWNLOAD_IMAGE_REQUEST_CODE = 2;
525 // 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.
526 // Also, remove the warning about needing to override `performClick()` when using an `OnTouchListener` with `WebView`.
527 @SuppressLint({"SetJavaScriptEnabled", "ClickableViewAccessibility"})
528 // Remove Android Studio's warning about deprecations. The deprecated `getColor()` must be used until API >= 23.
529 @SuppressWarnings("deprecation")
530 protected void onCreate(Bundle savedInstanceState) {
531 // Get a handle for the shared preferences.
532 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
534 // Get the theme and screenshot preferences.
535 darkTheme = sharedPreferences.getBoolean("dark_theme", false);
536 allowScreenshots = sharedPreferences.getBoolean("allow_screenshots", false);
538 // Disable screenshots if not allowed.
539 if (!allowScreenshots) {
540 getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE);
543 // Set the activity theme.
545 setTheme(R.style.PrivacyBrowserDark);
547 setTheme(R.style.PrivacyBrowserLight);
550 // Run the default commands.
551 super.onCreate(savedInstanceState);
553 // Set the content view.
554 setContentView(R.layout.main_drawerlayout);
556 // Get a handle for the resources.
557 Resources resources = getResources();
559 // Get a handle for `inputMethodManager`.
560 inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
562 // `SupportActionBar` from `android.support.v7.app.ActionBar` must be used until the minimum API is >= 21.
563 supportAppBar = findViewById(R.id.app_bar);
564 setSupportActionBar(supportAppBar);
565 appBar = getSupportActionBar();
567 // This is needed to get rid of the Android Studio warning that `appBar` might be null.
568 assert appBar != null;
570 // Add the custom `url_app_bar` layout, which shows the favorite icon and the URL text bar.
571 appBar.setCustomView(R.layout.url_app_bar);
572 appBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM);
574 // Initialize the foreground color spans for highlighting the URLs. We have to use the deprecated `getColor()` until API >= 23.
575 redColorSpan = new ForegroundColorSpan(resources.getColor(R.color.red_a700));
576 initialGrayColorSpan = new ForegroundColorSpan(resources.getColor(R.color.gray_500));
577 finalGrayColorSpan = new ForegroundColorSpan(resources.getColor(R.color.gray_500));
579 // Get a handle for `urlTextBox`.
580 urlTextBox = findViewById(R.id.url_edittext);
582 // Remove the formatting from `urlTextBar` when the user is editing the text.
583 urlTextBox.setOnFocusChangeListener((View v, boolean hasFocus) -> {
584 if (hasFocus) { // The user is editing the URL text box.
585 // Remove the highlighting.
586 urlTextBox.getText().removeSpan(redColorSpan);
587 urlTextBox.getText().removeSpan(initialGrayColorSpan);
588 urlTextBox.getText().removeSpan(finalGrayColorSpan);
589 } else { // The user has stopped editing the URL text box.
590 // Move to the beginning of the string.
591 urlTextBox.setSelection(0);
593 // Reapply the highlighting.
598 // Set the go button on the keyboard to load the URL in `urlTextBox`.
599 urlTextBox.setOnKeyListener((View v, int keyCode, KeyEvent event) -> {
600 // If the event is a key-down event on the `enter` button, load the URL.
601 if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) {
602 // Load the URL into the mainWebView and consume the event.
603 loadUrlFromTextBox();
605 // If the enter key was pressed, consume the event.
608 // If any other key was pressed, do not consume the event.
613 // Set `waitingForOrbotHTMLString`.
614 waitingForOrbotHtmlString = "<html><body><br/><center><h1>" + getString(R.string.waiting_for_orbot) + "</h1></center></body></html>";
616 // Initialize `currentDomainName`, `orbotStatus`, and `waitingForOrbot`.
617 currentDomainName = "";
618 orbotStatus = "unknown";
619 waitingForOrbot = false;
621 // Create an Orbot status `BroadcastReceiver`.
622 orbotStatusBroadcastReceiver = new BroadcastReceiver() {
624 public void onReceive(Context context, Intent intent) {
625 // Store the content of the status message in `orbotStatus`.
626 orbotStatus = intent.getStringExtra("org.torproject.android.intent.extra.STATUS");
628 // If Privacy Browser is waiting on Orbot, load the website now that Orbot is connected.
629 if (orbotStatus.equals("ON") && waitingForOrbot) {
630 // Reset `waitingForOrbot`.
631 waitingForOrbot = false;
633 // Load `formattedUrlString
634 loadUrl(formattedUrlString);
639 // Register `orbotStatusBroadcastReceiver` on `this` context.
640 this.registerReceiver(orbotStatusBroadcastReceiver, new IntentFilter("org.torproject.android.intent.action.STATUS"));
642 // Get handles for views that need to be accessed.
643 drawerLayout = findViewById(R.id.drawerlayout);
644 rootCoordinatorLayout = findViewById(R.id.root_coordinatorlayout);
645 bookmarksListView = findViewById(R.id.bookmarks_drawer_listview);
646 bookmarksTitleTextView = findViewById(R.id.bookmarks_title_textview);
647 FloatingActionButton launchBookmarksActivityFab = findViewById(R.id.launch_bookmarks_activity_fab);
648 FloatingActionButton createBookmarkFolderFab = findViewById(R.id.create_bookmark_folder_fab);
649 FloatingActionButton createBookmarkFab = findViewById(R.id.create_bookmark_fab);
650 mainWebViewRelativeLayout = findViewById(R.id.main_webview_relativelayout);
651 mainWebView = findViewById(R.id.main_webview);
652 findOnPageLinearLayout = findViewById(R.id.find_on_page_linearlayout);
653 findOnPageEditText = findViewById(R.id.find_on_page_edittext);
654 fullScreenVideoFrameLayout = findViewById(R.id.full_screen_video_framelayout);
655 urlAppBarRelativeLayout = findViewById(R.id.url_app_bar_relativelayout);
656 favoriteIconImageView = findViewById(R.id.favorite_icon);
658 // 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.
660 launchBookmarksActivityFab.setImageDrawable(resources.getDrawable(R.drawable.bookmarks_dark));
661 createBookmarkFolderFab.setImageDrawable(resources.getDrawable(R.drawable.create_folder_dark));
662 createBookmarkFab.setImageDrawable(resources.getDrawable(R.drawable.create_bookmark_dark));
663 bookmarksListView.setBackgroundColor(resources.getColor(R.color.gray_850));
665 launchBookmarksActivityFab.setImageDrawable(resources.getDrawable(R.drawable.bookmarks_light));
666 createBookmarkFolderFab.setImageDrawable(resources.getDrawable(R.drawable.create_folder_light));
667 createBookmarkFab.setImageDrawable(resources.getDrawable(R.drawable.create_bookmark_light));
668 bookmarksListView.setBackgroundColor(resources.getColor(R.color.white));
671 // Set the launch bookmarks activity FAB to launch the bookmarks activity.
672 launchBookmarksActivityFab.setOnClickListener(v -> {
673 // Create an intent to launch the bookmarks activity.
674 Intent bookmarksIntent = new Intent(getApplicationContext(), BookmarksActivity.class);
676 // Include the current folder with the `Intent`.
677 bookmarksIntent.putExtra("Current Folder", currentBookmarksFolder);
680 startActivity(bookmarksIntent);
683 // Set the create new bookmark folder FAB to display an alert dialog.
684 createBookmarkFolderFab.setOnClickListener(v -> {
685 // Show the `CreateBookmarkFolderDialog` `AlertDialog` and name the instance `@string/create_folder`.
686 AppCompatDialogFragment createBookmarkFolderDialog = new CreateBookmarkFolderDialog();
687 createBookmarkFolderDialog.show(getSupportFragmentManager(), resources.getString(R.string.create_folder));
690 // Set the create new bookmark FAB to display an alert dialog.
691 createBookmarkFab.setOnClickListener(view -> {
692 // Show the `CreateBookmarkDialog` `AlertDialog` and name the instance `@string/create_bookmark`.
693 AppCompatDialogFragment createBookmarkDialog = new CreateBookmarkDialog();
694 createBookmarkDialog.show(getSupportFragmentManager(), resources.getString(R.string.create_bookmark));
697 // Create a double-tap listener to toggle full-screen mode.
698 final GestureDetector gestureDetector = new GestureDetector(this, new GestureDetector.SimpleOnGestureListener() {
699 // Override `onDoubleTap()`. All other events are handled using the default settings.
701 public boolean onDoubleTap(MotionEvent event) {
702 if (fullScreenBrowsingModeEnabled) { // Only process the double-tap if full screen browsing mode is enabled.
703 // Toggle `inFullScreenBrowsingMode`.
704 inFullScreenBrowsingMode = !inFullScreenBrowsingMode;
706 if (inFullScreenBrowsingMode) { // Switch to full screen mode.
707 // Hide the `appBar`.
710 // Hide the banner ad in the free flavor.
711 if (BuildConfig.FLAVOR.contentEquals("free")) {
712 // The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations.
713 AdHelper.hideAd(findViewById(R.id.adview));
716 // Modify the system bars.
717 if (hideSystemBarsOnFullscreen) { // Hide everything.
718 // Remove the translucent overlays.
719 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
721 // Remove the translucent status bar overlay on the `Drawer Layout`, which is special and needs its own command.
722 drawerLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
724 /* SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
725 * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
726 * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
728 rootCoordinatorLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
730 // Set `rootCoordinatorLayout` to fill the whole screen.
731 rootCoordinatorLayout.setFitsSystemWindows(false);
732 } else { // Hide everything except the status and navigation bars.
733 // Set `rootCoordinatorLayout` to fit under the status and navigation bars.
734 rootCoordinatorLayout.setFitsSystemWindows(false);
736 // 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.
737 if (translucentNavigationBarOnFullscreen) {
738 // Set the navigation bar to be translucent.
739 getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
742 } else { // Switch to normal viewing mode.
743 // Show the `appBar`.
746 // Show the `BannerAd` in the free flavor.
747 if (BuildConfig.FLAVOR.contentEquals("free")) {
748 // Reload the ad. The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations.
749 AdHelper.loadAd(findViewById(R.id.adview), getApplicationContext(), getString(R.string.ad_unit_id));
752 // Remove the translucent navigation bar flag if it is set.
753 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
755 // Add the translucent status flag if it is unset. This also resets `drawerLayout's` `View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN`.
756 getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
758 // Remove any `SYSTEM_UI` flags from `rootCoordinatorLayout`.
759 rootCoordinatorLayout.setSystemUiVisibility(0);
761 // Constrain `rootCoordinatorLayout` inside the status and navigation bars.
762 rootCoordinatorLayout.setFitsSystemWindows(true);
765 // Consume the double-tap.
767 } else { // Do not consume the double-tap because full screen browsing mode is disabled.
773 // Pass all touch events on `mainWebView` through `gestureDetector` to check for double-taps.
774 mainWebView.setOnTouchListener((View v, MotionEvent event) -> {
775 // Call `performClick()` on the view, which is required for accessibility.
778 // Send the `event` to `gestureDetector`.
779 return gestureDetector.onTouchEvent(event);
782 // Update `findOnPageCountTextView`.
783 mainWebView.setFindListener(new WebView.FindListener() {
784 // Get a handle for `findOnPageCountTextView`.
785 final TextView findOnPageCountTextView = findViewById(R.id.find_on_page_count_textview);
788 public void onFindResultReceived(int activeMatchOrdinal, int numberOfMatches, boolean isDoneCounting) {
789 if ((isDoneCounting) && (numberOfMatches == 0)) { // There are no matches.
790 // Set `findOnPageCountTextView` to `0/0`.
791 findOnPageCountTextView.setText(R.string.zero_of_zero);
792 } else if (isDoneCounting) { // There are matches.
793 // `activeMatchOrdinal` is zero-based.
794 int activeMatch = activeMatchOrdinal + 1;
796 // Build the match string.
797 String matchString = activeMatch + "/" + numberOfMatches;
799 // Set `findOnPageCountTextView`.
800 findOnPageCountTextView.setText(matchString);
805 // Search for the string on the page whenever a character changes in the `findOnPageEditText`.
806 findOnPageEditText.addTextChangedListener(new TextWatcher() {
808 public void beforeTextChanged(CharSequence s, int start, int count, int after) {
813 public void onTextChanged(CharSequence s, int start, int before, int count) {
818 public void afterTextChanged(Editable s) {
819 // Search for the text in `mainWebView`.
820 mainWebView.findAllAsync(findOnPageEditText.getText().toString());
824 // Set the `check mark` button for the `findOnPageEditText` keyboard to close the soft keyboard.
825 findOnPageEditText.setOnKeyListener((v, keyCode, event) -> {
826 if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) { // The `enter` key was pressed.
827 // Hide the soft keyboard. `0` indicates no additional flags.
828 inputMethodManager.hideSoftInputFromWindow(mainWebView.getWindowToken(), 0);
830 // Consume the event.
832 } else { // A different key was pressed.
833 // Do not consume the event.
838 // Implement swipe to refresh.
839 swipeRefreshLayout = findViewById(R.id.swiperefreshlayout);
840 swipeRefreshLayout.setOnRefreshListener(() -> mainWebView.reload());
842 // Set the swipe to refresh color according to the theme.
844 swipeRefreshLayout.setColorSchemeResources(R.color.blue_600);
845 swipeRefreshLayout.setProgressBackgroundColorSchemeResource(R.color.gray_850);
847 swipeRefreshLayout.setColorSchemeResources(R.color.blue_700);
850 // `DrawerTitle` identifies the `DrawerLayouts` in accessibility mode.
851 drawerLayout.setDrawerTitle(GravityCompat.START, getString(R.string.navigation_drawer));
852 drawerLayout.setDrawerTitle(GravityCompat.END, getString(R.string.bookmarks));
854 // Listen for touches on the navigation menu.
855 final NavigationView navigationView = findViewById(R.id.navigationview);
856 navigationView.setNavigationItemSelectedListener(this);
858 // 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.
859 final Menu navigationMenu = navigationView.getMenu();
860 final MenuItem navigationBackMenuItem = navigationMenu.getItem(1);
861 final MenuItem navigationForwardMenuItem = navigationMenu.getItem(2);
862 final MenuItem navigationHistoryMenuItem = navigationMenu.getItem(3);
863 final MenuItem navigationRequestsMenuItem = navigationMenu.getItem(4);
865 // Initialize the bookmarks database helper. The `0` specifies a database version, but that is ignored and set instead using a constant in `BookmarksDatabaseHelper`.
866 bookmarksDatabaseHelper = new BookmarksDatabaseHelper(this, null, null, 0);
868 // Initialize `currentBookmarksFolder`. `""` is the home folder in the database.
869 currentBookmarksFolder = "";
871 // Load the home folder, which is `""` in the database.
872 loadBookmarksFolder();
874 bookmarksListView.setOnItemClickListener((parent, view, position, id) -> {
875 // Convert the id from long to int to match the format of the bookmarks database.
876 int databaseID = (int) id;
878 // Get the bookmark cursor for this ID and move it to the first row.
879 Cursor bookmarkCursor = bookmarksDatabaseHelper.getBookmark(databaseID);
880 bookmarkCursor.moveToFirst();
882 // Act upon the bookmark according to the type.
883 if (bookmarkCursor.getInt(bookmarkCursor.getColumnIndex(BookmarksDatabaseHelper.IS_FOLDER)) == 1) { // The selected bookmark is a folder.
884 // Store the new folder name in `currentBookmarksFolder`.
885 currentBookmarksFolder = bookmarkCursor.getString(bookmarkCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME));
887 // Load the new folder.
888 loadBookmarksFolder();
889 } else { // The selected bookmark is not a folder.
890 // Load the bookmark URL.
891 loadUrl(bookmarkCursor.getString(bookmarkCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_URL)));
893 // Close the bookmarks drawer.
894 drawerLayout.closeDrawer(GravityCompat.END);
897 // Close the `Cursor`.
898 bookmarkCursor.close();
901 bookmarksListView.setOnItemLongClickListener((parent, view, position, id) -> {
902 // Convert the database ID from `long` to `int`.
903 int databaseId = (int) id;
905 // Find out if the selected bookmark is a folder.
906 boolean isFolder = bookmarksDatabaseHelper.isFolder(databaseId);
909 // Save the current folder name, which is used in `onSaveEditBookmarkFolder()`.
910 oldFolderNameString = bookmarksCursor.getString(bookmarksCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME));
912 // Show the edit bookmark folder `AlertDialog` and name the instance `@string/edit_folder`.
913 AppCompatDialogFragment editFolderDialog = EditBookmarkFolderDialog.folderDatabaseId(databaseId);
914 editFolderDialog.show(getSupportFragmentManager(), resources.getString(R.string.edit_folder));
916 // Show the edit bookmark `AlertDialog` and name the instance `@string/edit_bookmark`.
917 AppCompatDialogFragment editBookmarkDialog = EditBookmarkDialog.bookmarkDatabaseId(databaseId);
918 editBookmarkDialog.show(getSupportFragmentManager(), resources.getString(R.string.edit_bookmark));
921 // Consume the event.
925 // Get the status bar pixel size.
926 int statusBarResourceId = resources.getIdentifier("status_bar_height", "dimen", "android");
927 int statusBarPixelSize = resources.getDimensionPixelSize(statusBarResourceId);
929 // Get the resource density.
930 float screenDensity = resources.getDisplayMetrics().density;
932 // Calculate the drawer header padding. This is used to move the text in the drawer headers below any cutouts.
933 drawerHeaderPaddingLeftAndRight = (int) (15 * screenDensity);
934 drawerHeaderPaddingTop = statusBarPixelSize + (int) (4 * screenDensity);
935 drawerHeaderPaddingBottom = (int) (8 * screenDensity);
937 // The drawer listener is used to update the navigation menu.`
938 drawerLayout.addDrawerListener(new DrawerLayout.DrawerListener() {
940 public void onDrawerSlide(@NonNull View drawerView, float slideOffset) {
944 public void onDrawerOpened(@NonNull View drawerView) {
948 public void onDrawerClosed(@NonNull View drawerView) {
952 public void onDrawerStateChanged(int newState) {
953 if ((newState == DrawerLayout.STATE_SETTLING) || (newState == DrawerLayout.STATE_DRAGGING)) { // A drawer is opening or closing.
954 // Get handles for the drawer headers.
955 TextView navigationHeaderTextView = findViewById(R.id.navigationText);
956 TextView bookmarksHeaderTextView = findViewById(R.id.bookmarks_title_textview);
958 // 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.
959 if (navigationHeaderTextView != null) {
960 navigationHeaderTextView.setPadding(drawerHeaderPaddingLeftAndRight, drawerHeaderPaddingTop, drawerHeaderPaddingLeftAndRight, drawerHeaderPaddingBottom);
963 // 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.
964 if (bookmarksHeaderTextView != null) {
965 bookmarksHeaderTextView.setPadding(drawerHeaderPaddingLeftAndRight, drawerHeaderPaddingTop, drawerHeaderPaddingLeftAndRight, drawerHeaderPaddingBottom);
968 // Update the back, forward, history, and requests menu items.
969 navigationBackMenuItem.setEnabled(mainWebView.canGoBack());
970 navigationForwardMenuItem.setEnabled(mainWebView.canGoForward());
971 navigationHistoryMenuItem.setEnabled((mainWebView.canGoBack() || mainWebView.canGoForward()));
972 navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
974 // Hide the keyboard (if displayed).
975 inputMethodManager.hideSoftInputFromWindow(mainWebView.getWindowToken(), 0);
977 // 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.
978 urlTextBox.clearFocus();
979 mainWebView.clearFocus();
984 // drawerToggle creates the hamburger icon at the start of the AppBar.
985 drawerToggle = new ActionBarDrawerToggle(this, drawerLayout, supportAppBar, R.string.open_navigation_drawer, R.string.close_navigation_drawer);
987 // Get a handle for the progress bar.
988 final ProgressBar progressBar = findViewById(R.id.progress_bar);
990 mainWebView.setWebChromeClient(new WebChromeClient() {
991 // Update the progress bar when a page is loading.
993 public void onProgressChanged(WebView view, int progress) {
994 // Inject the night mode CSS if night mode is enabled.
996 // `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
997 // used by WordPress. `text-decoration: none` removes all text underlines. `text-shadow: none` removes text shadows, which usually have a hard coded color.
998 // `border: none` removes all borders, which can also be used to underline text. `a {color: #1565C0}` sets links to be a dark blue.
999 // `::selection {background: #0D47A1}' sets the text selection highlight color to be a dark blue. `!important` takes precedent over any existing sub-settings.
1000 mainWebView.evaluateJavascript("(function() {var parent = document.getElementsByTagName('head').item(0); var style = document.createElement('style'); style.type = 'text/css'; " +
1001 "style.innerHTML = '* {background-color: #212121 !important; color: #BDBDBD !important; box-shadow: none !important; text-decoration: none !important;" +
1002 "text-shadow: none !important; border: none !important;} a {color: #1565C0 !important;} ::selection {background: #0D47A1 !important;}'; parent.appendChild(style)})()", value -> {
1003 // Initialize a handler to display `mainWebView`.
1004 Handler displayWebViewHandler = new Handler();
1006 // Setup a runnable to display `mainWebView` after a delay to allow the CSS to be applied.
1007 Runnable displayWebViewRunnable = () -> {
1008 // Only display `mainWebView` if the progress bar is gone. This prevents the display of the `WebView` while it is still loading.
1009 if (progressBar.getVisibility() == View.GONE) {
1010 mainWebView.setVisibility(View.VISIBLE);
1014 // Displaying of `mainWebView` after 500 milliseconds.
1015 displayWebViewHandler.postDelayed(displayWebViewRunnable, 500);
1019 // Update the progress bar.
1020 progressBar.setProgress(progress);
1022 // Set the visibility of the progress bar.
1023 if (progress < 100) {
1024 // Show the progress bar.
1025 progressBar.setVisibility(View.VISIBLE);
1027 // Hide the progress bar.
1028 progressBar.setVisibility(View.GONE);
1030 // Display `mainWebView` if night mode is disabled.
1031 // 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
1032 // currently enabled.
1034 mainWebView.setVisibility(View.VISIBLE);
1037 //Stop the swipe to refresh indicator if it is running
1038 swipeRefreshLayout.setRefreshing(false);
1042 // Set the favorite icon when it changes.
1044 public void onReceivedIcon(WebView view, Bitmap icon) {
1045 // Only update the favorite icon if the website has finished loading.
1046 if (progressBar.getVisibility() == View.GONE) {
1047 // Save a copy of the favorite icon.
1048 favoriteIconBitmap = icon;
1050 // Place the favorite icon in the appBar.
1051 favoriteIconImageView.setImageBitmap(Bitmap.createScaledBitmap(icon, 64, 64, true));
1055 // Save a copy of the title when it changes.
1057 public void onReceivedTitle(WebView view, String title) {
1058 // Save a copy of the title.
1059 webViewTitle = title;
1062 // Enter full screen video.
1064 public void onShowCustomView(View view, CustomViewCallback callback) {
1065 // Set the full screen video flag.
1066 displayingFullScreenVideo = true;
1068 // Pause the ad if this is the free flavor.
1069 if (BuildConfig.FLAVOR.contentEquals("free")) {
1070 // The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations.
1071 AdHelper.pauseAd(findViewById(R.id.adview));
1074 // Remove the translucent overlays.
1075 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
1077 // Remove the translucent status bar overlay on the `Drawer Layout`, which is special and needs its own command.
1078 drawerLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
1080 /* SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
1081 * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
1082 * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
1084 rootCoordinatorLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
1086 // Set `rootCoordinatorLayout` to fill the entire screen.
1087 rootCoordinatorLayout.setFitsSystemWindows(false);
1089 // Disable the sliding drawers.
1090 drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED);
1092 // Add `view` to `fullScreenVideoFrameLayout` and display it on the screen.
1093 fullScreenVideoFrameLayout.addView(view);
1094 fullScreenVideoFrameLayout.setVisibility(View.VISIBLE);
1097 // Exit full screen video.
1099 public void onHideCustomView() {
1100 // Unset the full screen video flag.
1101 displayingFullScreenVideo = false;
1103 // Hide `fullScreenVideoFrameLayout`.
1104 fullScreenVideoFrameLayout.removeAllViews();
1105 fullScreenVideoFrameLayout.setVisibility(View.GONE);
1107 // Enable the sliding drawers.
1108 drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED);
1110 // Apply the appropriate full screen mode the `SYSTEM_UI` flags.
1111 if (fullScreenBrowsingModeEnabled && inFullScreenBrowsingMode) { // Privacy Browser is currently in full screen browsing mode.
1112 if (hideSystemBarsOnFullscreen) { // Hide everything.
1113 // Remove the translucent navigation setting if it is currently flagged.
1114 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
1116 // Remove the translucent status bar overlay.
1117 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
1119 // Remove the translucent status bar overlay on the `Drawer Layout`, which is special and needs its own command.
1120 drawerLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
1122 /* SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
1123 * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
1124 * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
1126 rootCoordinatorLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
1127 } else { // Hide everything except the status and navigation bars.
1128 // Remove any `SYSTEM_UI` flags from `rootCoordinatorLayout`.
1129 rootCoordinatorLayout.setSystemUiVisibility(0);
1131 // Add the translucent status flag if it is unset.
1132 getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
1134 if (translucentNavigationBarOnFullscreen) {
1135 // Set the navigation bar to be translucent. This also resets `drawerLayout's` `View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN`.
1136 getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
1138 // Set the navigation bar to be black.
1139 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
1142 } else { // Switch to normal viewing mode.
1143 // Show the `appBar` if `findOnPageLinearLayout` is not visible.
1144 if (findOnPageLinearLayout.getVisibility() == View.GONE) {
1148 // Show the `BannerAd` in the free flavor.
1149 if (BuildConfig.FLAVOR.contentEquals("free")) {
1150 // Initialize the ad. The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations.
1151 AdHelper.initializeAds(findViewById(R.id.adview), getApplicationContext(), getFragmentManager(), getString(R.string.google_app_id), getString(R.string.ad_unit_id));
1154 // Remove any `SYSTEM_UI` flags from `rootCoordinatorLayout`.
1155 rootCoordinatorLayout.setSystemUiVisibility(0);
1157 // Remove the translucent navigation bar flag if it is set.
1158 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
1160 // Add the translucent status flag if it is unset. This also resets `drawerLayout's` `View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN`.
1161 getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
1163 // Constrain `rootCoordinatorLayout` inside the status and navigation bars.
1164 rootCoordinatorLayout.setFitsSystemWindows(true);
1167 // Show the ad if this is the free flavor.
1168 if (BuildConfig.FLAVOR.contentEquals("free")) {
1169 // Reload the ad. The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations.
1170 AdHelper.loadAd(findViewById(R.id.adview), getApplicationContext(), getString(R.string.ad_unit_id));
1176 public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) {
1177 // Show the file chooser if the device is running API >= 21.
1178 if (Build.VERSION.SDK_INT >= 21) {
1179 // Store the file path callback.
1180 fileChooserCallback = filePathCallback;
1182 // Create an intent to open a chooser based ont the file chooser parameters.
1183 Intent fileChooserIntent = fileChooserParams.createIntent();
1185 // Open the file chooser. Currently only one `startActivityForResult` exists in this activity, so the request code, used to differentiate them, is simply `0`.
1186 startActivityForResult(fileChooserIntent, 0);
1192 // Register `mainWebView` for a context menu. This is used to see link targets and download images.
1193 registerForContextMenu(mainWebView);
1195 // Allow the downloading of files.
1196 mainWebView.setDownloadListener((String url, String userAgent, String contentDisposition, String mimetype, long contentLength) -> {
1197 // Check if the download should be processed by an external app.
1198 if (downloadWithExternalApp) { // Download with an external app.
1199 openUrlWithExternalApp(url);
1200 } else { // Download with Android's download manager.
1201 // Check to see if the WRITE_EXTERNAL_STORAGE permission has already been granted.
1202 if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) { // The storage permission has not been granted.
1203 // The WRITE_EXTERNAL_STORAGE permission needs to be requested.
1205 // Store the variables for future use by `onRequestPermissionsResult()`.
1207 downloadContentDisposition = contentDisposition;
1208 downloadContentLength = contentLength;
1210 // Show a dialog if the user has previously denied the permission.
1211 if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) { // Show a dialog explaining the request first.
1212 // Instantiate the download location permission alert dialog and set the download type to DOWNLOAD_FILE.
1213 DialogFragment downloadLocationPermissionDialogFragment = DownloadLocationPermissionDialog.downloadType(DownloadLocationPermissionDialog.DOWNLOAD_FILE);
1215 // Show the download location permission alert dialog. The permission will be requested when the the dialog is closed.
1216 downloadLocationPermissionDialogFragment.show(getFragmentManager(), getString(R.string.download_location));
1217 } else { // Show the permission request directly.
1218 // Request the permission. The download dialog will be launched by `onRequestPermissionResult()`.
1219 ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_FILE_REQUEST_CODE);
1221 } else { // The storage permission has already been granted.
1222 // Get a handle for the download file alert dialog.
1223 AppCompatDialogFragment downloadFileDialogFragment = DownloadFileDialog.fromUrl(url, contentDisposition, contentLength);
1225 // Show the download file alert dialog.
1226 downloadFileDialogFragment.show(getSupportFragmentManager(), getString(R.string.download));
1231 // Allow pinch to zoom.
1232 mainWebView.getSettings().setBuiltInZoomControls(true);
1234 // Hide zoom controls.
1235 mainWebView.getSettings().setDisplayZoomControls(false);
1237 // Don't allow mixed content (HTTP and HTTPS) on the same website.
1238 if (Build.VERSION.SDK_INT >= 21) {
1239 mainWebView.getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_NEVER_ALLOW);
1242 // Set the WebView to use a wide viewport. Otherwise, some web pages will be scrunched and some content will render outside the screen.
1243 mainWebView.getSettings().setUseWideViewPort(true);
1245 // Set the WebView to load in overview mode (zoomed out to the maximum width).
1246 mainWebView.getSettings().setLoadWithOverviewMode(true);
1248 // Explicitly disable geolocation.
1249 mainWebView.getSettings().setGeolocationEnabled(false);
1251 // Initialize cookieManager.
1252 cookieManager = CookieManager.getInstance();
1254 // 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).
1255 customHeaders.put("X-Requested-With", "");
1257 // Initialize the default preference values the first time the program is run. `false` keeps this command from resetting any current preferences back to default.
1258 PreferenceManager.setDefaultValues(this, R.xml.preferences, false);
1260 // Get a handle for the `Runtime`.
1261 privacyBrowserRuntime = Runtime.getRuntime();
1263 // Store the application's private data directory.
1264 privateDataDirectoryString = getApplicationInfo().dataDir;
1265 // `dataDir` will vary, but will be something like `/data/user/0/com.stoutner.privacybrowser.standard`, which links to `/data/data/com.stoutner.privacybrowser.standard`.
1267 // Initialize `inFullScreenBrowsingMode`, which is always false at this point because Privacy Browser never starts in full screen browsing mode.
1268 inFullScreenBrowsingMode = false;
1270 // Initialize the privacy settings variables.
1271 javaScriptEnabled = false;
1272 firstPartyCookiesEnabled = false;
1273 thirdPartyCookiesEnabled = false;
1274 domStorageEnabled = false;
1275 saveFormDataEnabled = false; // Form data can be removed once the minimum API >= 26.
1278 // Store the default user agent.
1279 webViewDefaultUserAgent = mainWebView.getSettings().getUserAgentString();
1281 // Initialize the WebView title.
1282 webViewTitle = getString(R.string.no_title);
1284 // Initialize the favorite icon bitmap. `ContextCompat` must be used until API >= 21.
1285 Drawable favoriteIconDrawable = ContextCompat.getDrawable(getApplicationContext(), R.drawable.world);
1286 BitmapDrawable favoriteIconBitmapDrawable = (BitmapDrawable) favoriteIconDrawable;
1287 assert favoriteIconBitmapDrawable != null;
1288 favoriteIconDefaultBitmap = favoriteIconBitmapDrawable.getBitmap();
1290 // If the favorite icon is null, load the default.
1291 if (favoriteIconBitmap == null) {
1292 favoriteIconBitmap = favoriteIconDefaultBitmap;
1295 // Initialize the user agent array adapter and string array.
1296 userAgentNamesArray = ArrayAdapter.createFromResource(this, R.array.user_agent_names, R.layout.spinner_item);
1297 userAgentDataArray = resources.getStringArray(R.array.user_agent_data);
1299 // Apply the app settings from the shared preferences.
1302 // Instantiate the block list helper.
1303 BlockListHelper blockListHelper = new BlockListHelper();
1305 // Initialize the list of resource requests.
1306 resourceRequests = new ArrayList<>();
1308 // Parse the block lists.
1309 final ArrayList<List<String[]>> easyList = blockListHelper.parseBlockList(getAssets(), "blocklists/easylist.txt");
1310 final ArrayList<List<String[]>> easyPrivacy = blockListHelper.parseBlockList(getAssets(), "blocklists/easyprivacy.txt");
1311 final ArrayList<List<String[]>> fanboysAnnoyanceList = blockListHelper.parseBlockList(getAssets(), "blocklists/fanboy-annoyance.txt");
1312 final ArrayList<List<String[]>> fanboysSocialList = blockListHelper.parseBlockList(getAssets(), "blocklists/fanboy-social.txt");
1313 final ArrayList<List<String[]>> ultraPrivacy = blockListHelper.parseBlockList(getAssets(), "blocklists/ultraprivacy.txt");
1315 // Store the list versions.
1316 easyListVersion = easyList.get(0).get(0)[0];
1317 easyPrivacyVersion = easyPrivacy.get(0).get(0)[0];
1318 fanboysAnnoyanceVersion = fanboysAnnoyanceList.get(0).get(0)[0];
1319 fanboysSocialVersion = fanboysSocialList.get(0).get(0)[0];
1320 ultraPrivacyVersion = ultraPrivacy.get(0).get(0)[0];
1322 // Get a handle for the activity. This is used to update the requests counter while the navigation menu is open.
1323 Activity activity = this;
1325 mainWebView.setWebViewClient(new WebViewClient() {
1326 // `shouldOverrideUrlLoading` makes this `WebView` the default handler for URLs inside the app, so that links are not kicked out to other apps.
1327 // The deprecated `shouldOverrideUrlLoading` must be used until API >= 24.
1328 @SuppressWarnings("deprecation")
1330 public boolean shouldOverrideUrlLoading(WebView view, String url) {
1331 if (url.startsWith("http")) { // Load the URL in Privacy Browser.
1332 // Reset the formatted URL string so the page will load correctly if blocking of third-party requests is enabled.
1333 formattedUrlString = "";
1335 // Apply the domain settings for the new URL. `applyDomainSettings` doesn't do anything if the domain has not changed.
1336 boolean userAgentChanged = applyDomainSettings(url, true, false);
1338 // Check if the user agent has changed.
1339 if (userAgentChanged) {
1340 // Manually load the URL. The changing of the user agent will cause WebView to reload the previous URL.
1341 mainWebView.loadUrl(url, customHeaders);
1343 // Returning true indicates that Privacy Browser is manually handling the loading of the URL.
1346 // Returning false causes the current WebView to handle the URL and prevents it from adding redirects to the history list.
1349 } else if (url.startsWith("mailto:")) { // Load the email address in an external email program.
1350 // Use `ACTION_SENDTO` instead of `ACTION_SEND` so that only email programs are launched.
1351 Intent emailIntent = new Intent(Intent.ACTION_SENDTO);
1353 // Parse the url and set it as the data for the intent.
1354 emailIntent.setData(Uri.parse(url));
1356 // Open the email program in a new task instead of as part of Privacy Browser.
1357 emailIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
1360 startActivity(emailIntent);
1362 // Returning true indicates Privacy Browser is handling the URL by creating an intent.
1364 } else if (url.startsWith("tel:")) { // Load the phone number in the dialer.
1365 // Open the dialer and load the phone number, but wait for the user to place the call.
1366 Intent dialIntent = new Intent(Intent.ACTION_DIAL);
1368 // Add the phone number to the intent.
1369 dialIntent.setData(Uri.parse(url));
1371 // Open the dialer in a new task instead of as part of Privacy Browser.
1372 dialIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
1375 startActivity(dialIntent);
1377 // Returning true indicates Privacy Browser is handling the URL by creating an intent.
1379 } else { // Load a system chooser to select an app that can handle the URL.
1380 // Open an app that can handle the URL.
1381 Intent genericIntent = new Intent(Intent.ACTION_VIEW);
1383 // Add the URL to the intent.
1384 genericIntent.setData(Uri.parse(url));
1386 // List all apps that can handle the URL instead of just opening the first one.
1387 genericIntent.addCategory(Intent.CATEGORY_BROWSABLE);
1389 // Open the app in a new task instead of as part of Privacy Browser.
1390 genericIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
1392 // Start the app or display a snackbar if no app is available to handle the URL.
1394 startActivity(genericIntent);
1395 } catch (ActivityNotFoundException exception) {
1396 Snackbar.make(mainWebView, getString(R.string.unrecognized_url) + " " + url, Snackbar.LENGTH_SHORT).show();
1399 // Returning true indicates Privacy Browser is handling the URL by creating an intent.
1404 // Check requests against the block lists. The deprecated `shouldInterceptRequest()` must be used until minimum API >= 21.
1405 @SuppressWarnings("deprecation")
1407 public WebResourceResponse shouldInterceptRequest(WebView view, String url){
1408 // Create an empty web resource response to be used if the resource request is blocked.
1409 WebResourceResponse emptyWebResourceResponse = new WebResourceResponse("text/plain", "utf8", new ByteArrayInputStream("".getBytes()));
1411 // Reset the whitelist results tracker.
1412 whiteListResultStringArray = null;
1414 // Initialize the third party request tracker.
1415 boolean isThirdPartyRequest = false;
1417 // Initialize the current domain string.
1418 String currentDomain = "";
1420 // Nobody is happy when comparing null strings.
1421 if (!(formattedUrlString == null) && !(url == null)) {
1422 // Get the domain strings to URIs.
1423 Uri currentDomainUri = Uri.parse(formattedUrlString);
1424 Uri requestDomainUri = Uri.parse(url);
1426 // Get the domain host names.
1427 String currentBaseDomain = currentDomainUri.getHost();
1428 String requestBaseDomain = requestDomainUri.getHost();
1430 // Update the current domain variable.
1431 currentDomain = currentBaseDomain;
1433 // Only compare the current base domain and the request base domain if neither is null.
1434 if (!(currentBaseDomain == null) && !(requestBaseDomain == null)) {
1435 // Determine the current base domain.
1436 while (currentBaseDomain.indexOf(".", currentBaseDomain.indexOf(".") + 1) > 0) { // There is at least one subdomain.
1437 // Remove the first subdomain.
1438 currentBaseDomain = currentBaseDomain.substring(currentBaseDomain.indexOf(".") + 1);
1441 // Determine the request base domain.
1442 while (requestBaseDomain.indexOf(".", requestBaseDomain.indexOf(".") + 1) > 0) { // There is at least one subdomain.
1443 // Remove the first subdomain.
1444 requestBaseDomain = requestBaseDomain.substring(requestBaseDomain.indexOf(".") + 1);
1447 // Update the third party request tracker.
1448 isThirdPartyRequest = !currentBaseDomain.equals(requestBaseDomain);
1452 // Block third-party requests if enabled.
1453 if (isThirdPartyRequest && blockAllThirdPartyRequests) {
1454 // Increment the blocked requests counters.
1456 thirdPartyBlockedRequests++;
1458 // Update the titles of the blocklist menu items. This must be run from the UI thread.
1459 activity.runOnUiThread(() -> {
1460 navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
1461 blocklistsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
1462 blockAllThirdPartyRequestsMenuItem.setTitle(thirdPartyBlockedRequests + " - " + getString(R.string.block_all_third_party_requests));
1465 // Add the request to the log.
1466 resourceRequests.add(new String[]{String.valueOf(REQUEST_THIRD_PARTY), url});
1468 // Return an empty web resource response.
1469 return emptyWebResourceResponse;
1472 // Check UltraPrivacy if it is enabled.
1473 if (ultraPrivacyEnabled) {
1474 if (blockListHelper.isBlocked(currentDomain, url, isThirdPartyRequest, ultraPrivacy)) {
1475 // Increment the blocked requests counters.
1477 ultraPrivacyBlockedRequests++;
1479 // Update the titles of the blocklist menu items. This must be run from the UI thread.
1480 activity.runOnUiThread(() -> {
1481 navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
1482 blocklistsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
1483 ultraPrivacyMenuItem.setTitle(ultraPrivacyBlockedRequests + " - " + getString(R.string.ultraprivacy));
1486 // The resource request was blocked. Return an empty web resource response.
1487 return emptyWebResourceResponse;
1490 // If the whitelist result is not null, the request has been allowed by UltraPrivacy.
1491 if (whiteListResultStringArray != null) {
1492 // Add a whitelist entry to the resource requests array.
1493 resourceRequests.add(whiteListResultStringArray);
1495 // The resource request has been allowed by UltraPrivacy. `return null` loads the requested resource.
1500 // Check EasyList if it is enabled.
1501 if (easyListEnabled) {
1502 if (blockListHelper.isBlocked(currentDomain, url, isThirdPartyRequest, easyList)) {
1503 // Increment the blocked requests counters.
1505 easyListBlockedRequests++;
1507 // Update the titles of the blocklist menu items. This must be run from the UI thread.
1508 activity.runOnUiThread(() -> {
1509 navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
1510 blocklistsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
1511 easyListMenuItem.setTitle(easyListBlockedRequests + " - " + getString(R.string.easylist));
1514 // Reset the whitelist results tracker (because otherwise it will sometimes add results to the list due to a race condition).
1515 whiteListResultStringArray = null;
1517 // The resource request was blocked. Return an empty web resource response.
1518 return emptyWebResourceResponse;
1522 // Check EasyPrivacy if it is enabled.
1523 if (easyPrivacyEnabled) {
1524 if (blockListHelper.isBlocked(currentDomain, url, isThirdPartyRequest, easyPrivacy)) {
1525 // Increment the blocked requests counters.
1527 easyPrivacyBlockedRequests++;
1529 // Update the titles of the blocklist menu items. This must be run from the UI thread.
1530 activity.runOnUiThread(() -> {
1531 navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
1532 blocklistsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
1533 easyPrivacyMenuItem.setTitle(easyPrivacyBlockedRequests + " - " + getString(R.string.easyprivacy));
1536 // Reset the whitelist results tracker (because otherwise it will sometimes add results to the list due to a race condition).
1537 whiteListResultStringArray = null;
1539 // The resource request was blocked. Return an empty web resource response.
1540 return emptyWebResourceResponse;
1544 // Check Fanboy’s Annoyance List if it is enabled.
1545 if (fanboysAnnoyanceListEnabled) {
1546 if (blockListHelper.isBlocked(currentDomain, url, isThirdPartyRequest, fanboysAnnoyanceList)) {
1547 // Increment the blocked requests counters.
1549 fanboysAnnoyanceListBlockedRequests++;
1551 // Update the titles of the blocklist menu items. This must be run from the UI thread.
1552 activity.runOnUiThread(() -> {
1553 navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
1554 blocklistsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
1555 fanboysAnnoyanceListMenuItem.setTitle(fanboysAnnoyanceListBlockedRequests + " - " + getString(R.string.fanboys_annoyance_list));
1558 // Reset the whitelist results tracker (because otherwise it will sometimes add results to the list due to a race condition).
1559 whiteListResultStringArray = null;
1561 // The resource request was blocked. Return an empty web resource response.
1562 return emptyWebResourceResponse;
1564 } else if (fanboysSocialBlockingListEnabled){ // Only check Fanboy’s Social Blocking List if Fanboy’s Annoyance List is disabled.
1565 if (blockListHelper.isBlocked(currentDomain, url, isThirdPartyRequest, fanboysSocialList)) {
1566 // Increment the blocked requests counters.
1568 fanboysSocialBlockingListBlockedRequests++;
1570 // Update the titles of the blocklist menu items. This must be run from the UI thread.
1571 activity.runOnUiThread(() -> {
1572 navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
1573 blocklistsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
1574 fanboysSocialBlockingListMenuItem.setTitle(fanboysSocialBlockingListBlockedRequests + " - " + getString(R.string.fanboys_social_blocking_list));
1577 // Reset the whitelist results tracker (because otherwise it will sometimes add results to the list due to a race condition).
1578 whiteListResultStringArray = null;
1580 // The resource request was blocked. Return an empty web resource response.
1581 return emptyWebResourceResponse;
1585 // Add the request to the log because it hasn't been processed by any of the previous checks.
1586 if (whiteListResultStringArray != null ) { // The request was processed by a whitelist.
1587 resourceRequests.add(whiteListResultStringArray);
1588 } else { // The request didn't match any blocklist entry. Log it as a default request.
1589 resourceRequests.add(new String[]{String.valueOf(REQUEST_DEFAULT), url});
1592 // The resource request has not been blocked. `return null` loads the requested resource.
1596 // Handle HTTP authentication requests.
1598 public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, String host, String realm) {
1599 // Store `handler` so it can be accessed from `onHttpAuthenticationCancel()` and `onHttpAuthenticationProceed()`.
1600 httpAuthHandler = handler;
1602 // Display the HTTP authentication dialog.
1603 AppCompatDialogFragment httpAuthenticationDialogFragment = HttpAuthenticationDialog.displayDialog(host, realm);
1604 httpAuthenticationDialogFragment.show(getSupportFragmentManager(), getString(R.string.http_authentication));
1607 // Update the URL in urlTextBox when the page starts to load.
1609 public void onPageStarted(WebView view, String url, Bitmap favicon) {
1610 // Reset the list of host IP addresses.
1611 currentHostIpAddresses = "";
1613 // Reset the list of resource requests.
1614 resourceRequests.clear();
1616 // Initialize the counters for requests blocked by each blocklist.
1617 blockedRequests = 0;
1618 easyListBlockedRequests = 0;
1619 easyPrivacyBlockedRequests = 0;
1620 fanboysAnnoyanceListBlockedRequests = 0;
1621 fanboysSocialBlockingListBlockedRequests = 0;
1622 ultraPrivacyBlockedRequests = 0;
1623 thirdPartyBlockedRequests = 0;
1625 // If night mode is enabled, hide `mainWebView` until after the night mode CSS is applied.
1627 mainWebView.setVisibility(View.INVISIBLE);
1630 // Hide the keyboard. `0` indicates no additional flags.
1631 inputMethodManager.hideSoftInputFromWindow(mainWebView.getWindowToken(), 0);
1633 // Check to see if Privacy Browser is waiting on Orbot.
1634 if (!waitingForOrbot) { // We are not waiting on Orbot, so we need to process the URL.
1635 // 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.
1636 formattedUrlString = url;
1638 // Display the formatted URL text.
1639 urlTextBox.setText(formattedUrlString);
1641 // Apply text highlighting to `urlTextBox`.
1644 // Get a URI for the current URL.
1645 Uri currentUri = Uri.parse(formattedUrlString);
1647 // Get the IP addresses for the host.
1648 new GetHostIpAddresses(activity).execute(currentUri.getHost());
1650 // Apply any custom domain settings if the URL was loaded by navigating history.
1651 if (navigatingHistory) {
1652 // Apply the domain settings.
1653 boolean userAgentChanged = applyDomainSettings(url, true, false);
1655 // Reset `navigatingHistory`.
1656 navigatingHistory = false;
1658 // Manually load the URL if the user agent has changed, which will have caused the previous URL to be reloaded.
1659 if (userAgentChanged) {
1660 loadUrl(formattedUrlString);
1664 // 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.
1665 urlIsLoading = true;
1667 // Replace Refresh with Stop if the menu item has been created. (The WebView typically begins loading before the menu items are instantiated.)
1668 if (refreshMenuItem != null) {
1670 refreshMenuItem.setTitle(R.string.stop);
1672 // If the icon is displayed in the AppBar, set it according to the theme.
1673 if (displayAdditionalAppBarIcons) {
1675 refreshMenuItem.setIcon(R.drawable.close_dark);
1677 refreshMenuItem.setIcon(R.drawable.close_light);
1684 // It is necessary to update `formattedUrlString` and `urlTextBox` after the page finishes loading because the final URL can change during load.
1686 public void onPageFinished(WebView view, String url) {
1687 // Reset the wide view port if it has been turned off by the waiting for Orbot message.
1688 if (!waitingForOrbot) {
1689 // Only use a wide view port if the URL starts with `http`, not for `file://` and `content://`.
1690 mainWebView.getSettings().setUseWideViewPort(url.startsWith("http"));
1693 // Flush any cookies to persistent storage. `CookieManager` has become very lazy about flushing cookies in recent versions.
1694 if (firstPartyCookiesEnabled && Build.VERSION.SDK_INT >= 21) {
1695 cookieManager.flush();
1698 // Update the Refresh menu item if it has been created.
1699 if (refreshMenuItem != null) {
1700 // Reset the Refresh title.
1701 refreshMenuItem.setTitle(R.string.refresh);
1703 // If the icon is displayed in the AppBar, reset it according to the theme.
1704 if (displayAdditionalAppBarIcons) {
1706 refreshMenuItem.setIcon(R.drawable.refresh_enabled_dark);
1708 refreshMenuItem.setIcon(R.drawable.refresh_enabled_light);
1713 // Reset `urlIsLoading`, which is used to prevent reloads on redirect if the user agent changes.
1714 urlIsLoading = false;
1716 // Clear the cache and history if Incognito Mode is enabled.
1717 if (incognitoModeEnabled) {
1718 // Clear the cache. `true` includes disk files.
1719 mainWebView.clearCache(true);
1721 // Clear the back/forward history.
1722 mainWebView.clearHistory();
1724 // Manually delete cache folders.
1726 // Delete the main cache directory.
1727 privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/cache");
1729 // Delete the secondary `Service Worker` cache directory.
1730 // A `String[]` must be used because the directory contains a space and `Runtime.exec` will not escape the string correctly otherwise.
1731 privacyBrowserRuntime.exec(new String[] {"rm", "-rf", privateDataDirectoryString + "/app_webview/Service Worker/"});
1732 } catch (IOException e) {
1733 // Do nothing if an error is thrown.
1737 // Update the URL text box and apply domain settings if not waiting on Orbot.
1738 if (!waitingForOrbot) {
1739 // Check to see if `WebView` has set `url` to be `about:blank`.
1740 if (url.equals("about:blank")) { // `WebView` is blank, so `formattedUrlString` should be `""` and `urlTextBox` should display a hint.
1741 // Set `formattedUrlString` to `""`.
1742 formattedUrlString = "";
1744 urlTextBox.setText(formattedUrlString);
1746 // Request focus for `urlTextBox`.
1747 urlTextBox.requestFocus();
1749 // Display the keyboard.
1750 inputMethodManager.showSoftInput(urlTextBox, 0);
1752 // Apply the domain settings. This clears any settings from the previous domain.
1753 applyDomainSettings(formattedUrlString, true, false);
1754 } else { // `WebView` has loaded a webpage.
1755 // 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.
1756 formattedUrlString = mainWebView.getUrl();
1758 // Only update the URL text box if the user is not typing in it.
1759 if (!urlTextBox.hasFocus()) {
1760 // Display the formatted URL text.
1761 urlTextBox.setText(formattedUrlString);
1763 // Apply text highlighting to `urlTextBox`.
1768 // Store the SSL certificate so it can be accessed from `ViewSslCertificateDialog` and `PinnedMismatchDialog`.
1769 sslCertificate = mainWebView.getCertificate();
1771 // Check the current website information against any pinned domain information.
1772 if ((pinnedSslCertificate || pinnedIpAddresses) && !ignorePinnedDomainInformation) {
1773 // Initialize the current SSL certificate variables.
1774 String currentWebsiteIssuedToCName = "";
1775 String currentWebsiteIssuedToOName = "";
1776 String currentWebsiteIssuedToUName = "";
1777 String currentWebsiteIssuedByCName = "";
1778 String currentWebsiteIssuedByOName = "";
1779 String currentWebsiteIssuedByUName = "";
1780 Date currentWebsiteSslStartDate = null;
1781 Date currentWebsiteSslEndDate = null;
1784 // Extract the individual pieces of information from the current website SSL certificate if it is not null.
1785 if (sslCertificate != null) {
1786 currentWebsiteIssuedToCName = sslCertificate.getIssuedTo().getCName();
1787 currentWebsiteIssuedToOName = sslCertificate.getIssuedTo().getOName();
1788 currentWebsiteIssuedToUName = sslCertificate.getIssuedTo().getUName();
1789 currentWebsiteIssuedByCName = sslCertificate.getIssuedBy().getCName();
1790 currentWebsiteIssuedByOName = sslCertificate.getIssuedBy().getOName();
1791 currentWebsiteIssuedByUName = sslCertificate.getIssuedBy().getUName();
1792 currentWebsiteSslStartDate = sslCertificate.getValidNotBeforeDate();
1793 currentWebsiteSslEndDate = sslCertificate.getValidNotAfterDate();
1796 // 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`.
1797 String currentWebsiteSslStartDateString = "";
1798 String currentWebsiteSslEndDateString = "";
1799 String pinnedSslStartDateString = "";
1800 String pinnedSslEndDateString = "";
1802 // Convert the `Dates` to `Strings` if they are not `null`.
1803 if (currentWebsiteSslStartDate != null) {
1804 currentWebsiteSslStartDateString = currentWebsiteSslStartDate.toString();
1807 if (currentWebsiteSslEndDate != null) {
1808 currentWebsiteSslEndDateString = currentWebsiteSslEndDate.toString();
1811 if (pinnedSslStartDate != null) {
1812 pinnedSslStartDateString = pinnedSslStartDate.toString();
1815 if (pinnedSslEndDate != null) {
1816 pinnedSslEndDateString = pinnedSslEndDate.toString();
1819 // Check to see if the pinned information matches the current information.
1820 if ((pinnedIpAddresses && !currentHostIpAddresses.equals(pinnedHostIpAddresses)) || (pinnedSslCertificate && (!currentWebsiteIssuedToCName.equals(pinnedSslIssuedToCName) ||
1821 !currentWebsiteIssuedToOName.equals(pinnedSslIssuedToOName) || !currentWebsiteIssuedToUName.equals(pinnedSslIssuedToUName) ||
1822 !currentWebsiteIssuedByCName.equals(pinnedSslIssuedByCName) || !currentWebsiteIssuedByOName.equals(pinnedSslIssuedByOName) ||
1823 !currentWebsiteIssuedByUName.equals(pinnedSslIssuedByUName) || !currentWebsiteSslStartDateString.equals(pinnedSslStartDateString) ||
1824 !currentWebsiteSslEndDateString.equals(pinnedSslEndDateString)))) {
1826 // Get a handle for the pinned mismatch alert dialog.
1827 AppCompatDialogFragment pinnedMismatchDialogFragment = PinnedMismatchDialog.displayDialog(pinnedSslCertificate, pinnedIpAddresses);
1829 // Show the pinned mismatch alert dialog.
1830 pinnedMismatchDialogFragment.show(getSupportFragmentManager(), getString(R.string.pinned_mismatch));
1836 // Handle SSL Certificate errors.
1838 public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
1839 // Get the current website SSL certificate.
1840 SslCertificate currentWebsiteSslCertificate = error.getCertificate();
1842 // Extract the individual pieces of information from the current website SSL certificate.
1843 String currentWebsiteIssuedToCName = currentWebsiteSslCertificate.getIssuedTo().getCName();
1844 String currentWebsiteIssuedToOName = currentWebsiteSslCertificate.getIssuedTo().getOName();
1845 String currentWebsiteIssuedToUName = currentWebsiteSslCertificate.getIssuedTo().getUName();
1846 String currentWebsiteIssuedByCName = currentWebsiteSslCertificate.getIssuedBy().getCName();
1847 String currentWebsiteIssuedByOName = currentWebsiteSslCertificate.getIssuedBy().getOName();
1848 String currentWebsiteIssuedByUName = currentWebsiteSslCertificate.getIssuedBy().getUName();
1849 Date currentWebsiteSslStartDate = currentWebsiteSslCertificate.getValidNotBeforeDate();
1850 Date currentWebsiteSslEndDate = currentWebsiteSslCertificate.getValidNotAfterDate();
1852 // Proceed to the website if the current SSL website certificate matches the pinned domain certificate.
1853 if (pinnedSslCertificate &&
1854 currentWebsiteIssuedToCName.equals(pinnedSslIssuedToCName) && currentWebsiteIssuedToOName.equals(pinnedSslIssuedToOName) &&
1855 currentWebsiteIssuedToUName.equals(pinnedSslIssuedToUName) && currentWebsiteIssuedByCName.equals(pinnedSslIssuedByCName) &&
1856 currentWebsiteIssuedByOName.equals(pinnedSslIssuedByOName) && currentWebsiteIssuedByUName.equals(pinnedSslIssuedByUName) &&
1857 currentWebsiteSslStartDate.equals(pinnedSslStartDate) && currentWebsiteSslEndDate.equals(pinnedSslEndDate)) {
1859 // An SSL certificate is pinned and matches the current domain certificate. Proceed to the website without displaying an error.
1861 } else { // Either there isn't a pinned SSL certificate or it doesn't match the current website certificate.
1862 // Store `handler` so it can be accesses from `onSslErrorCancel()` and `onSslErrorProceed()`.
1863 sslErrorHandler = handler;
1865 // Display the SSL error `AlertDialog`.
1866 AppCompatDialogFragment sslCertificateErrorDialogFragment = SslCertificateErrorDialog.displayDialog(error);
1867 sslCertificateErrorDialogFragment.show(getSupportFragmentManager(), getString(R.string.ssl_certificate_error));
1872 // Get the intent that started the app.
1873 Intent launchingIntent = getIntent();
1875 // Get the information from the intent.
1876 String launchingIntentAction = launchingIntent.getAction();
1877 Uri launchingIntentUriData = launchingIntent.getData();
1879 // If the intent action is a web search, perform the search.
1880 if ((launchingIntentAction != null) && launchingIntentAction.equals(Intent.ACTION_WEB_SEARCH)) {
1881 // Create an encoded URL string.
1882 String encodedUrlString;
1884 // Sanitize the search input and convert it to a search.
1886 encodedUrlString = URLEncoder.encode(launchingIntent.getStringExtra(SearchManager.QUERY), "UTF-8");
1887 } catch (UnsupportedEncodingException exception) {
1888 encodedUrlString = "";
1891 // Add the base search URL.
1892 formattedUrlString = searchURL + encodedUrlString;
1893 } else if (launchingIntentUriData != null){ // Check to see if the intent contains a new URL.
1894 // Set the formatted URL string.
1895 formattedUrlString = launchingIntentUriData.toString();
1898 // Load the website if not waiting for Orbot to connect.
1899 if (!waitingForOrbot) {
1900 loadUrl(formattedUrlString);
1905 protected void onNewIntent(Intent intent) {
1906 // Sets the new intent as the activity intent, so that any future `getIntent()`s pick up this one instead of creating a new activity.
1909 // Get the information from the intent.
1910 String intentAction = intent.getAction();
1911 Uri intentUriData = intent.getData();
1913 // If the intent action is a web search, perform the search.
1914 if ((intentAction != null) && intentAction.equals(Intent.ACTION_WEB_SEARCH)) {
1915 // Create an encoded URL string.
1916 String encodedUrlString;
1918 // Sanitize the search input and convert it to a search.
1920 encodedUrlString = URLEncoder.encode(intent.getStringExtra(SearchManager.QUERY), "UTF-8");
1921 } catch (UnsupportedEncodingException exception) {
1922 encodedUrlString = "";
1925 // Add the base search URL.
1926 formattedUrlString = searchURL + encodedUrlString;
1927 } else if (intentUriData != null){ // Check to see if the intent contains a new URL.
1928 // Set the formatted URL string.
1929 formattedUrlString = intentUriData.toString();
1933 loadUrl(formattedUrlString);
1935 // Close the navigation drawer if it is open.
1936 if (drawerLayout.isDrawerVisible(GravityCompat.START)) {
1937 drawerLayout.closeDrawer(GravityCompat.START);
1940 // Close the bookmarks drawer if it is open.
1941 if (drawerLayout.isDrawerVisible(GravityCompat.END)) {
1942 drawerLayout.closeDrawer(GravityCompat.END);
1945 // Clear the keyboard if displayed and remove the focus on the urlTextBar if it has it.
1946 mainWebView.requestFocus();
1950 public void onRestart() {
1951 // Run the default commands.
1954 // Make sure Orbot is running if Privacy Browser is proxying through Orbot.
1955 if (proxyThroughOrbot) {
1956 // Request Orbot to start. If Orbot is already running no hard will be caused by this request.
1957 Intent orbotIntent = new Intent("org.torproject.android.intent.action.START");
1959 // Send the intent to the Orbot package.
1960 orbotIntent.setPackage("org.torproject.android");
1963 sendBroadcast(orbotIntent);
1966 // Apply the app settings if returning from the Settings activity..
1967 if (reapplyAppSettingsOnRestart) {
1968 // Apply the app settings.
1971 // Reload the webpage if displaying of images has been disabled in the Settings activity.
1972 if (reloadOnRestart) {
1973 // Reload `mainWebView`.
1974 mainWebView.reload();
1976 // Reset `reloadOnRestartBoolean`.
1977 reloadOnRestart = false;
1980 // Reset the return from settings flag.
1981 reapplyAppSettingsOnRestart = false;
1984 // Apply the domain settings if returning from the Domains activity.
1985 if (reapplyDomainSettingsOnRestart) {
1986 // Reapply the domain settings.
1987 applyDomainSettings(formattedUrlString, false, true);
1989 // Reset `reapplyDomainSettingsOnRestart`.
1990 reapplyDomainSettingsOnRestart = false;
1993 // Load the URL on restart to apply changes to night mode.
1994 if (loadUrlOnRestart) {
1995 // Load the current `formattedUrlString`.
1996 loadUrl(formattedUrlString);
1998 // Reset `loadUrlOnRestart.
1999 loadUrlOnRestart = false;
2002 // Update the bookmarks drawer if returning from the Bookmarks activity.
2003 if (restartFromBookmarksActivity) {
2004 // Close the bookmarks drawer.
2005 drawerLayout.closeDrawer(GravityCompat.END);
2007 // Reload the bookmarks drawer.
2008 loadBookmarksFolder();
2010 // Reset `restartFromBookmarksActivity`.
2011 restartFromBookmarksActivity = false;
2014 // Update the privacy icon. `true` runs `invalidateOptionsMenu` as the last step. This can be important if the screen was rotated.
2015 updatePrivacyIcons(true);
2018 // `onResume()` runs after `onStart()`, which runs after `onCreate()` and `onRestart()`.
2020 public void onResume() {
2021 // Run the default commands.
2024 // Resume JavaScript (if enabled).
2025 mainWebView.resumeTimers();
2027 // Resume `mainWebView`.
2028 mainWebView.onResume();
2030 // Resume the adView for the free flavor.
2031 if (BuildConfig.FLAVOR.contentEquals("free")) {
2033 AdHelper.resumeAd(findViewById(R.id.adview));
2036 // Display a message to the user if waiting for Orbot.
2037 if (waitingForOrbot && !orbotStatus.equals("ON")) {
2038 // Disable the wide view port so that the waiting for Orbot text is displayed correctly.
2039 mainWebView.getSettings().setUseWideViewPort(false);
2041 // Load a waiting page. `null` specifies no encoding, which defaults to ASCII.
2042 mainWebView.loadData(waitingForOrbotHtmlString, "text/html", null);
2045 if (displayingFullScreenVideo) {
2046 // Remove the translucent overlays.
2047 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
2049 // Remove the translucent status bar overlay on the `Drawer Layout`, which is special and needs its own command.
2050 drawerLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
2052 /* SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
2053 * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
2054 * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
2056 rootCoordinatorLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
2061 public void onPause() {
2062 // Run the default commands.
2065 // Pause `mainWebView`.
2066 mainWebView.onPause();
2068 // Stop all JavaScript.
2069 mainWebView.pauseTimers();
2071 // Pause the ad or it will continue to consume resources in the background on the free flavor.
2072 if (BuildConfig.FLAVOR.contentEquals("free")) {
2074 AdHelper.pauseAd(findViewById(R.id.adview));
2079 public void onDestroy() {
2080 // Unregister the Orbot status broadcast receiver.
2081 this.unregisterReceiver(orbotStatusBroadcastReceiver);
2083 // Close the bookmarks cursor and database.
2084 bookmarksCursor.close();
2085 bookmarksDatabaseHelper.close();
2087 // Run the default commands.
2092 public boolean onCreateOptionsMenu(Menu menu) {
2093 // Inflate the menu; this adds items to the action bar if it is present.
2094 getMenuInflater().inflate(R.menu.webview_options_menu, menu);
2096 // Set mainMenu so it can be used by `onOptionsItemSelected()` and `updatePrivacyIcons`.
2099 // Set the initial status of the privacy icons. `false` does not call `invalidateOptionsMenu` as the last step.
2100 updatePrivacyIcons(false);
2102 // Get handles for the menu items.
2103 MenuItem toggleFirstPartyCookiesMenuItem = menu.findItem(R.id.toggle_first_party_cookies);
2104 MenuItem toggleThirdPartyCookiesMenuItem = menu.findItem(R.id.toggle_third_party_cookies);
2105 MenuItem toggleDomStorageMenuItem = menu.findItem(R.id.toggle_dom_storage);
2106 MenuItem toggleSaveFormDataMenuItem = menu.findItem(R.id.toggle_save_form_data); // Form data can be removed once the minimum API >= 26.
2107 MenuItem clearFormDataMenuItem = menu.findItem(R.id.clear_form_data); // Form data can be removed once the minimum API >= 26.
2108 refreshMenuItem = menu.findItem(R.id.refresh);
2109 blocklistsMenuItem = menu.findItem(R.id.blocklists);
2110 easyListMenuItem = menu.findItem(R.id.easylist);
2111 easyPrivacyMenuItem = menu.findItem(R.id.easyprivacy);
2112 fanboysAnnoyanceListMenuItem = menu.findItem(R.id.fanboys_annoyance_list);
2113 fanboysSocialBlockingListMenuItem = menu.findItem(R.id.fanboys_social_blocking_list);
2114 ultraPrivacyMenuItem = menu.findItem(R.id.ultraprivacy);
2115 blockAllThirdPartyRequestsMenuItem = menu.findItem(R.id.block_all_third_party_requests);
2116 MenuItem adConsentMenuItem = menu.findItem(R.id.ad_consent);
2118 // Only display third-party cookies if API >= 21
2119 toggleThirdPartyCookiesMenuItem.setVisible(Build.VERSION.SDK_INT >= 21);
2121 // Only display the form data menu items if the API < 26.
2122 toggleSaveFormDataMenuItem.setVisible(Build.VERSION.SDK_INT < 26);
2123 clearFormDataMenuItem.setVisible(Build.VERSION.SDK_INT < 26);
2125 // Only show Ad Consent if this is the free flavor.
2126 adConsentMenuItem.setVisible(BuildConfig.FLAVOR.contentEquals("free"));
2128 // Get the shared preference values.
2129 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
2131 // Get the status of the additional AppBar icons.
2132 displayAdditionalAppBarIcons = sharedPreferences.getBoolean("display_additional_app_bar_icons", false);
2134 // 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.
2135 if (displayAdditionalAppBarIcons) {
2136 toggleFirstPartyCookiesMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
2137 toggleDomStorageMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
2138 refreshMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
2139 } else { //Do not display the additional icons.
2140 toggleFirstPartyCookiesMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
2141 toggleDomStorageMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
2142 refreshMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
2145 // Replace Refresh with Stop if a URL is already loading.
2148 refreshMenuItem.setTitle(R.string.stop);
2150 // If the icon is displayed in the AppBar, set it according to the theme.
2151 if (displayAdditionalAppBarIcons) {
2153 refreshMenuItem.setIcon(R.drawable.close_dark);
2155 refreshMenuItem.setIcon(R.drawable.close_light);
2164 public boolean onPrepareOptionsMenu(Menu menu) {
2165 // Get handles for the menu items.
2166 MenuItem addOrEditDomain = menu.findItem(R.id.add_or_edit_domain);
2167 MenuItem toggleFirstPartyCookiesMenuItem = menu.findItem(R.id.toggle_first_party_cookies);
2168 MenuItem toggleThirdPartyCookiesMenuItem = menu.findItem(R.id.toggle_third_party_cookies);
2169 MenuItem toggleDomStorageMenuItem = menu.findItem(R.id.toggle_dom_storage);
2170 MenuItem toggleSaveFormDataMenuItem = menu.findItem(R.id.toggle_save_form_data); // Form data can be removed once the minimum API >= 26.
2171 MenuItem clearDataMenuItem = menu.findItem(R.id.clear_data);
2172 MenuItem clearCookiesMenuItem = menu.findItem(R.id.clear_cookies);
2173 MenuItem clearDOMStorageMenuItem = menu.findItem(R.id.clear_dom_storage);
2174 MenuItem clearFormDataMenuItem = menu.findItem(R.id.clear_form_data); // Form data can be removed once the minimum API >= 26.
2175 MenuItem fontSizeMenuItem = menu.findItem(R.id.font_size);
2176 MenuItem swipeToRefreshMenuItem = menu.findItem(R.id.swipe_to_refresh);
2177 MenuItem displayImagesMenuItem = menu.findItem(R.id.display_images);
2178 MenuItem nightModeMenuItem = menu.findItem(R.id.night_mode);
2179 MenuItem proxyThroughOrbotMenuItem = menu.findItem(R.id.proxy_through_orbot);
2181 // Set the text for the domain menu item.
2182 if (domainSettingsApplied) {
2183 addOrEditDomain.setTitle(R.string.edit_domain_settings);
2185 addOrEditDomain.setTitle(R.string.add_domain_settings);
2188 // Set the status of the menu item checkboxes.
2189 toggleFirstPartyCookiesMenuItem.setChecked(firstPartyCookiesEnabled);
2190 toggleThirdPartyCookiesMenuItem.setChecked(thirdPartyCookiesEnabled);
2191 toggleDomStorageMenuItem.setChecked(domStorageEnabled);
2192 toggleSaveFormDataMenuItem.setChecked(saveFormDataEnabled); // Form data can be removed once the minimum API >= 26.
2193 easyListMenuItem.setChecked(easyListEnabled);
2194 easyPrivacyMenuItem.setChecked(easyPrivacyEnabled);
2195 fanboysAnnoyanceListMenuItem.setChecked(fanboysAnnoyanceListEnabled);
2196 fanboysSocialBlockingListMenuItem.setChecked(fanboysSocialBlockingListEnabled);
2197 ultraPrivacyMenuItem.setChecked(ultraPrivacyEnabled);
2198 blockAllThirdPartyRequestsMenuItem.setChecked(blockAllThirdPartyRequests);
2199 swipeToRefreshMenuItem.setChecked(swipeRefreshLayout.isEnabled());
2200 displayImagesMenuItem.setChecked(mainWebView.getSettings().getLoadsImagesAutomatically());
2201 nightModeMenuItem.setChecked(nightMode);
2202 proxyThroughOrbotMenuItem.setChecked(proxyThroughOrbot);
2204 // Enable third-party cookies if first-party cookies are enabled.
2205 toggleThirdPartyCookiesMenuItem.setEnabled(firstPartyCookiesEnabled);
2207 // Enable DOM Storage if JavaScript is enabled.
2208 toggleDomStorageMenuItem.setEnabled(javaScriptEnabled);
2210 // Enable Clear Cookies if there are any.
2211 clearCookiesMenuItem.setEnabled(cookieManager.hasCookies());
2213 // Get a count of the number of files in the Local Storage directory.
2214 File localStorageDirectory = new File (privateDataDirectoryString + "/app_webview/Local Storage/");
2215 int localStorageDirectoryNumberOfFiles = 0;
2216 if (localStorageDirectory.exists()) {
2217 localStorageDirectoryNumberOfFiles = localStorageDirectory.list().length;
2220 // Get a count of the number of files in the IndexedDB directory.
2221 File indexedDBDirectory = new File (privateDataDirectoryString + "/app_webview/IndexedDB");
2222 int indexedDBDirectoryNumberOfFiles = 0;
2223 if (indexedDBDirectory.exists()) {
2224 indexedDBDirectoryNumberOfFiles = indexedDBDirectory.list().length;
2227 // Enable Clear DOM Storage if there is any.
2228 clearDOMStorageMenuItem.setEnabled(localStorageDirectoryNumberOfFiles > 0 || indexedDBDirectoryNumberOfFiles > 0);
2230 // Enable Clear Form Data is there is any. This can be removed once the minimum API >= 26.
2231 if (Build.VERSION.SDK_INT < 26) {
2232 WebViewDatabase mainWebViewDatabase = WebViewDatabase.getInstance(this);
2233 clearFormDataMenuItem.setEnabled(mainWebViewDatabase.hasFormData());
2235 // Disable clear form data because it is not supported on current version of Android.
2236 clearFormDataMenuItem.setEnabled(false);
2239 // Enable Clear Data if any of the submenu items are enabled.
2240 clearDataMenuItem.setEnabled(clearCookiesMenuItem.isEnabled() || clearDOMStorageMenuItem.isEnabled() || clearFormDataMenuItem.isEnabled());
2242 // Disable Fanboy's Social Blocking List if Fanboy's Annoyance List is checked.
2243 fanboysSocialBlockingListMenuItem.setEnabled(!fanboysAnnoyanceListEnabled);
2245 // Initialize the display names for the blocklists with the number of blocked requests.
2246 blocklistsMenuItem.setTitle(getString(R.string.blocklists) + " - " + blockedRequests);
2247 easyListMenuItem.setTitle(easyListBlockedRequests + " - " + getString(R.string.easylist));
2248 easyPrivacyMenuItem.setTitle(easyPrivacyBlockedRequests + " - " + getString(R.string.easyprivacy));
2249 fanboysAnnoyanceListMenuItem.setTitle(fanboysAnnoyanceListBlockedRequests + " - " + getString(R.string.fanboys_annoyance_list));
2250 fanboysSocialBlockingListMenuItem.setTitle(fanboysSocialBlockingListBlockedRequests + " - " + getString(R.string.fanboys_social_blocking_list));
2251 ultraPrivacyMenuItem.setTitle(ultraPrivacyBlockedRequests + " - " + getString(R.string.ultraprivacy));
2252 blockAllThirdPartyRequestsMenuItem.setTitle(thirdPartyBlockedRequests + " - " + getString(R.string.block_all_third_party_requests));
2254 // Get the current user agent.
2255 String currentUserAgent = mainWebView.getSettings().getUserAgentString();
2257 // Select the current user agent menu item. A switch statement cannot be used because the user agents are not compile time constants.
2258 if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[0])) { // Privacy Browser.
2259 menu.findItem(R.id.user_agent_privacy_browser).setChecked(true);
2260 } else if (currentUserAgent.equals(webViewDefaultUserAgent)) { // WebView Default.
2261 menu.findItem(R.id.user_agent_webview_default).setChecked(true);
2262 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[2])) { // Firefox on Android.
2263 menu.findItem(R.id.user_agent_firefox_on_android).setChecked(true);
2264 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[3])) { // Chrome on Android.
2265 menu.findItem(R.id.user_agent_chrome_on_android).setChecked(true);
2266 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[4])) { // Safari on iOS.
2267 menu.findItem(R.id.user_agent_safari_on_ios).setChecked(true);
2268 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[5])) { // Firefox on Linux.
2269 menu.findItem(R.id.user_agent_firefox_on_linux).setChecked(true);
2270 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[6])) { // Chromium on Linux.
2271 menu.findItem(R.id.user_agent_chromium_on_linux).setChecked(true);
2272 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[7])) { // Firefox on Windows.
2273 menu.findItem(R.id.user_agent_firefox_on_windows).setChecked(true);
2274 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[8])) { // Chrome on Windows.
2275 menu.findItem(R.id.user_agent_chrome_on_windows).setChecked(true);
2276 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[9])) { // Edge on Windows.
2277 menu.findItem(R.id.user_agent_edge_on_windows).setChecked(true);
2278 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[10])) { // Internet Explorer on Windows.
2279 menu.findItem(R.id.user_agent_internet_explorer_on_windows).setChecked(true);
2280 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[11])) { // Safari on macOS.
2281 menu.findItem(R.id.user_agent_safari_on_macos).setChecked(true);
2282 } else { // Custom user agent.
2283 menu.findItem(R.id.user_agent_custom).setChecked(true);
2286 // Initialize font size variables.
2287 int fontSize = mainWebView.getSettings().getTextZoom();
2288 String fontSizeTitle;
2289 MenuItem selectedFontSizeMenuItem;
2291 // Prepare the font size title and current size menu item.
2294 fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.twenty_five_percent);
2295 selectedFontSizeMenuItem = menu.findItem(R.id.font_size_twenty_five_percent);
2299 fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.fifty_percent);
2300 selectedFontSizeMenuItem = menu.findItem(R.id.font_size_fifty_percent);
2304 fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.seventy_five_percent);
2305 selectedFontSizeMenuItem = menu.findItem(R.id.font_size_seventy_five_percent);
2309 fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.one_hundred_percent);
2310 selectedFontSizeMenuItem = menu.findItem(R.id.font_size_one_hundred_percent);
2314 fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.one_hundred_twenty_five_percent);
2315 selectedFontSizeMenuItem = menu.findItem(R.id.font_size_one_hundred_twenty_five_percent);
2319 fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.one_hundred_fifty_percent);
2320 selectedFontSizeMenuItem = menu.findItem(R.id.font_size_one_hundred_fifty_percent);
2324 fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.one_hundred_seventy_five_percent);
2325 selectedFontSizeMenuItem = menu.findItem(R.id.font_size_one_hundred_seventy_five_percent);
2329 fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.two_hundred_percent);
2330 selectedFontSizeMenuItem = menu.findItem(R.id.font_size_two_hundred_percent);
2334 fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.one_hundred_percent);
2335 selectedFontSizeMenuItem = menu.findItem(R.id.font_size_one_hundred_percent);
2339 // Set the font size title and select the current size menu item.
2340 fontSizeMenuItem.setTitle(fontSizeTitle);
2341 selectedFontSizeMenuItem.setChecked(true);
2343 // Run all the other default commands.
2344 super.onPrepareOptionsMenu(menu);
2346 // Display the menu.
2351 // Remove Android Studio's warning about the dangers of using SetJavaScriptEnabled.
2352 @SuppressLint("SetJavaScriptEnabled")
2353 // removeAllCookies is deprecated, but it is required for API < 21.
2354 @SuppressWarnings("deprecation")
2355 public boolean onOptionsItemSelected(MenuItem menuItem) {
2356 // Get the selected menu item ID.
2357 int menuItemId = menuItem.getItemId();
2359 // Set the commands that relate to the menu entries.
2360 switch (menuItemId) {
2361 case R.id.toggle_javascript:
2362 // Switch the status of javaScriptEnabled.
2363 javaScriptEnabled = !javaScriptEnabled;
2365 // Apply the new JavaScript status.
2366 mainWebView.getSettings().setJavaScriptEnabled(javaScriptEnabled);
2368 // Update the privacy icon. `true` runs `invalidateOptionsMenu` as the last step.
2369 updatePrivacyIcons(true);
2371 // Display a `Snackbar`.
2372 if (javaScriptEnabled) { // JavaScrip is enabled.
2373 Snackbar.make(findViewById(R.id.main_webview), R.string.javascript_enabled, Snackbar.LENGTH_SHORT).show();
2374 } else if (firstPartyCookiesEnabled) { // JavaScript is disabled, but first-party cookies are enabled.
2375 Snackbar.make(findViewById(R.id.main_webview), R.string.javascript_disabled, Snackbar.LENGTH_SHORT).show();
2376 } else { // Privacy mode.
2377 Snackbar.make(findViewById(R.id.main_webview), R.string.privacy_mode, Snackbar.LENGTH_SHORT).show();
2380 // Reload the WebView.
2381 mainWebView.reload();
2384 case R.id.add_or_edit_domain:
2385 if (domainSettingsApplied) { // Edit the current domain settings.
2386 // Reapply the domain settings on returning to `MainWebViewActivity`.
2387 reapplyDomainSettingsOnRestart = true;
2388 currentDomainName = "";
2390 // Create an intent to launch the domains activity.
2391 Intent domainsIntent = new Intent(this, DomainsActivity.class);
2393 // Put extra information instructing the domains activity to directly load the current domain and close on back instead of returning to the domains list.
2394 domainsIntent.putExtra("loadDomain", domainSettingsDatabaseId);
2395 domainsIntent.putExtra("closeOnBack", true);
2398 startActivity(domainsIntent);
2399 } else { // Add a new domain.
2400 // Apply the new domain settings on returning to `MainWebViewActivity`.
2401 reapplyDomainSettingsOnRestart = true;
2402 currentDomainName = "";
2404 // Get the current domain
2405 Uri currentUri = Uri.parse(formattedUrlString);
2406 String currentDomain = currentUri.getHost();
2408 // Initialize the database handler. The `0` specifies the database version, but that is ignored and set instead using a constant in `DomainsDatabaseHelper`.
2409 DomainsDatabaseHelper domainsDatabaseHelper = new DomainsDatabaseHelper(this, null, null, 0);
2411 // Create the domain and store the database ID.
2412 int newDomainDatabaseId = domainsDatabaseHelper.addDomain(currentDomain);
2414 // Create an intent to launch the domains activity.
2415 Intent domainsIntent = new Intent(this, DomainsActivity.class);
2417 // Put extra information instructing the domains activity to directly load the new domain and close on back instead of returning to the domains list.
2418 domainsIntent.putExtra("loadDomain", newDomainDatabaseId);
2419 domainsIntent.putExtra("closeOnBack", true);
2422 startActivity(domainsIntent);
2426 case R.id.toggle_first_party_cookies:
2427 // Switch the status of firstPartyCookiesEnabled.
2428 firstPartyCookiesEnabled = !firstPartyCookiesEnabled;
2430 // Update the menu checkbox.
2431 menuItem.setChecked(firstPartyCookiesEnabled);
2433 // Apply the new cookie status.
2434 cookieManager.setAcceptCookie(firstPartyCookiesEnabled);
2436 // Update the privacy icon. `true` runs `invalidateOptionsMenu` as the last step.
2437 updatePrivacyIcons(true);
2439 // Display a `Snackbar`.
2440 if (firstPartyCookiesEnabled) { // First-party cookies are enabled.
2441 Snackbar.make(findViewById(R.id.main_webview), R.string.first_party_cookies_enabled, Snackbar.LENGTH_SHORT).show();
2442 } else if (javaScriptEnabled) { // JavaScript is still enabled.
2443 Snackbar.make(findViewById(R.id.main_webview), R.string.first_party_cookies_disabled, Snackbar.LENGTH_SHORT).show();
2444 } else { // Privacy mode.
2445 Snackbar.make(findViewById(R.id.main_webview), R.string.privacy_mode, Snackbar.LENGTH_SHORT).show();
2448 // Reload the WebView.
2449 mainWebView.reload();
2452 case R.id.toggle_third_party_cookies:
2453 if (Build.VERSION.SDK_INT >= 21) {
2454 // Switch the status of thirdPartyCookiesEnabled.
2455 thirdPartyCookiesEnabled = !thirdPartyCookiesEnabled;
2457 // Update the menu checkbox.
2458 menuItem.setChecked(thirdPartyCookiesEnabled);
2460 // Apply the new cookie status.
2461 cookieManager.setAcceptThirdPartyCookies(mainWebView, thirdPartyCookiesEnabled);
2463 // Display a `Snackbar`.
2464 if (thirdPartyCookiesEnabled) {
2465 Snackbar.make(findViewById(R.id.main_webview), R.string.third_party_cookies_enabled, Snackbar.LENGTH_SHORT).show();
2467 Snackbar.make(findViewById(R.id.main_webview), R.string.third_party_cookies_disabled, Snackbar.LENGTH_SHORT).show();
2470 // Reload the WebView.
2471 mainWebView.reload();
2472 } // Else do nothing because SDK < 21.
2475 case R.id.toggle_dom_storage:
2476 // Switch the status of domStorageEnabled.
2477 domStorageEnabled = !domStorageEnabled;
2479 // Update the menu checkbox.
2480 menuItem.setChecked(domStorageEnabled);
2482 // Apply the new DOM Storage status.
2483 mainWebView.getSettings().setDomStorageEnabled(domStorageEnabled);
2485 // Update the privacy icon. `true` runs `invalidateOptionsMenu` as the last step.
2486 updatePrivacyIcons(true);
2488 // Display a `Snackbar`.
2489 if (domStorageEnabled) {
2490 Snackbar.make(findViewById(R.id.main_webview), R.string.dom_storage_enabled, Snackbar.LENGTH_SHORT).show();
2492 Snackbar.make(findViewById(R.id.main_webview), R.string.dom_storage_disabled, Snackbar.LENGTH_SHORT).show();
2495 // Reload the WebView.
2496 mainWebView.reload();
2499 // Form data can be removed once the minimum API >= 26.
2500 case R.id.toggle_save_form_data:
2501 // Switch the status of saveFormDataEnabled.
2502 saveFormDataEnabled = !saveFormDataEnabled;
2504 // Update the menu checkbox.
2505 menuItem.setChecked(saveFormDataEnabled);
2507 // Apply the new form data status.
2508 mainWebView.getSettings().setSaveFormData(saveFormDataEnabled);
2510 // Display a `Snackbar`.
2511 if (saveFormDataEnabled) {
2512 Snackbar.make(findViewById(R.id.main_webview), R.string.form_data_enabled, Snackbar.LENGTH_SHORT).show();
2514 Snackbar.make(findViewById(R.id.main_webview), R.string.form_data_disabled, Snackbar.LENGTH_SHORT).show();
2517 // Update the privacy icon. `true` runs `invalidateOptionsMenu` as the last step.
2518 updatePrivacyIcons(true);
2520 // Reload the WebView.
2521 mainWebView.reload();
2524 case R.id.clear_cookies:
2525 Snackbar.make(findViewById(R.id.main_webview), R.string.cookies_deleted, Snackbar.LENGTH_LONG)
2526 .setAction(R.string.undo, v -> {
2527 // Do nothing because everything will be handled by `onDismissed()` below.
2529 .addCallback(new Snackbar.Callback() {
2531 public void onDismissed(Snackbar snackbar, int event) {
2533 // The user pushed the `Undo` button.
2534 case Snackbar.Callback.DISMISS_EVENT_ACTION:
2538 // The `Snackbar` was dismissed without the `Undo` button being pushed.
2540 // `cookieManager.removeAllCookie()` varies by SDK.
2541 if (Build.VERSION.SDK_INT < 21) {
2542 cookieManager.removeAllCookie();
2544 // `null` indicates no callback.
2545 cookieManager.removeAllCookies(null);
2553 case R.id.clear_dom_storage:
2554 Snackbar.make(findViewById(R.id.main_webview), R.string.dom_storage_deleted, Snackbar.LENGTH_LONG)
2555 .setAction(R.string.undo, v -> {
2556 // Do nothing because everything will be handled by `onDismissed()` below.
2558 .addCallback(new Snackbar.Callback() {
2560 public void onDismissed(Snackbar snackbar, int event) {
2562 // The user pushed the `Undo` button.
2563 case Snackbar.Callback.DISMISS_EVENT_ACTION:
2567 // The `Snackbar` was dismissed without the `Undo` button being pushed.
2569 // Delete the DOM Storage.
2570 WebStorage webStorage = WebStorage.getInstance();
2571 webStorage.deleteAllData();
2573 // Initialize a handler to manually delete the DOM storage files and directories.
2574 Handler deleteDomStorageHandler = new Handler();
2576 // Setup a runnable to manually delete the DOM storage files and directories.
2577 Runnable deleteDomStorageRunnable = () -> {
2579 // A `String[]` must be used because the directory contains a space and `Runtime.exec` will otherwise not escape the string correctly.
2580 privacyBrowserRuntime.exec(new String[]{"rm", "-rf", privateDataDirectoryString + "/app_webview/Local Storage/"});
2582 // Multiple commands must be used because `Runtime.exec()` does not like `*`.
2583 privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/IndexedDB");
2584 privacyBrowserRuntime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager");
2585 privacyBrowserRuntime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager-journal");
2586 privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/databases");
2587 } catch (IOException e) {
2588 // Do nothing if an error is thrown.
2592 // Manually delete the DOM storage files after 200 milliseconds.
2593 deleteDomStorageHandler.postDelayed(deleteDomStorageRunnable, 200);
2600 // Form data can be remove once the minimum API >= 26.
2601 case R.id.clear_form_data:
2602 Snackbar.make(findViewById(R.id.main_webview), R.string.form_data_deleted, Snackbar.LENGTH_LONG)
2603 .setAction(R.string.undo, v -> {
2604 // Do nothing because everything will be handled by `onDismissed()` below.
2606 .addCallback(new Snackbar.Callback() {
2608 public void onDismissed(Snackbar snackbar, int event) {
2610 // The user pushed the `Undo` button.
2611 case Snackbar.Callback.DISMISS_EVENT_ACTION:
2615 // The `Snackbar` was dismissed without the `Undo` button being pushed.
2617 // Delete the form data.
2618 WebViewDatabase mainWebViewDatabase = WebViewDatabase.getInstance(getApplicationContext());
2619 mainWebViewDatabase.clearFormData();
2627 // Toggle the EasyList status.
2628 easyListEnabled = !easyListEnabled;
2630 // Update the menu checkbox.
2631 menuItem.setChecked(easyListEnabled);
2633 // Reload the main WebView.
2634 mainWebView.reload();
2637 case R.id.easyprivacy:
2638 // Toggle the EasyPrivacy status.
2639 easyPrivacyEnabled = !easyPrivacyEnabled;
2641 // Update the menu checkbox.
2642 menuItem.setChecked(easyPrivacyEnabled);
2644 // Reload the main WebView.
2645 mainWebView.reload();
2648 case R.id.fanboys_annoyance_list:
2649 // Toggle Fanboy's Annoyance List status.
2650 fanboysAnnoyanceListEnabled = !fanboysAnnoyanceListEnabled;
2652 // Update the menu checkbox.
2653 menuItem.setChecked(fanboysAnnoyanceListEnabled);
2655 // Update the staus of Fanboy's Social Blocking List.
2656 MenuItem fanboysSocialBlockingListMenuItem = mainMenu.findItem(R.id.fanboys_social_blocking_list);
2657 fanboysSocialBlockingListMenuItem.setEnabled(!fanboysAnnoyanceListEnabled);
2659 // Reload the main WebView.
2660 mainWebView.reload();
2663 case R.id.fanboys_social_blocking_list:
2664 // Toggle Fanboy's Social Blocking List status.
2665 fanboysSocialBlockingListEnabled = !fanboysSocialBlockingListEnabled;
2667 // Update the menu checkbox.
2668 menuItem.setChecked(fanboysSocialBlockingListEnabled);
2670 // Reload the main WebView.
2671 mainWebView.reload();
2674 case R.id.ultraprivacy:
2675 // Toggle the UltraPrivacy status.
2676 ultraPrivacyEnabled = !ultraPrivacyEnabled;
2678 // Update the menu checkbox.
2679 menuItem.setChecked(ultraPrivacyEnabled);
2681 // Reload the main WebView.
2682 mainWebView.reload();
2685 case R.id.block_all_third_party_requests:
2686 //Toggle the third-party requests blocker status.
2687 blockAllThirdPartyRequests = !blockAllThirdPartyRequests;
2689 // Update the menu checkbox.
2690 menuItem.setChecked(blockAllThirdPartyRequests);
2692 // Reload the main WebView.
2693 mainWebView.reload();
2696 case R.id.user_agent_privacy_browser:
2697 // Update the user agent.
2698 mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[0]);
2700 // Reload the WebView.
2701 mainWebView.reload();
2704 case R.id.user_agent_webview_default:
2705 // Update the user agent.
2706 mainWebView.getSettings().setUserAgentString("");
2708 // Reload the WebView.
2709 mainWebView.reload();
2712 case R.id.user_agent_firefox_on_android:
2713 // Update the user agent.
2714 mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[2]);
2716 // Reload the WebView.
2717 mainWebView.reload();
2720 case R.id.user_agent_chrome_on_android:
2721 // Update the user agent.
2722 mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[3]);
2724 // Reload the WebView.
2725 mainWebView.reload();
2728 case R.id.user_agent_safari_on_ios:
2729 // Update the user agent.
2730 mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[4]);
2732 // Reload the WebView.
2733 mainWebView.reload();
2736 case R.id.user_agent_firefox_on_linux:
2737 // Update the user agent.
2738 mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[5]);
2740 // Reload the WebView.
2741 mainWebView.reload();
2744 case R.id.user_agent_chromium_on_linux:
2745 // Update the user agent.
2746 mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[6]);
2748 // Reload the WebView.
2749 mainWebView.reload();
2752 case R.id.user_agent_firefox_on_windows:
2753 // Update the user agent.
2754 mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[7]);
2756 // Reload the WebView.
2757 mainWebView.reload();
2760 case R.id.user_agent_chrome_on_windows:
2761 // Update the user agent.
2762 mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[8]);
2764 // Reload the WebView.
2765 mainWebView.reload();
2768 case R.id.user_agent_edge_on_windows:
2769 // Update the user agent.
2770 mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[9]);
2772 // Reload the WebView.
2773 mainWebView.reload();
2776 case R.id.user_agent_internet_explorer_on_windows:
2777 // Update the user agent.
2778 mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[10]);
2780 // Reload the WebView.
2781 mainWebView.reload();
2784 case R.id.user_agent_safari_on_macos:
2785 // Update the user agent.
2786 mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[11]);
2788 // Reload the WebView.
2789 mainWebView.reload();
2792 case R.id.user_agent_custom:
2793 // Update the user agent.
2794 mainWebView.getSettings().setUserAgentString(defaultCustomUserAgentString);
2796 // Reload the WebView.
2797 mainWebView.reload();
2800 case R.id.font_size_twenty_five_percent:
2801 mainWebView.getSettings().setTextZoom(25);
2804 case R.id.font_size_fifty_percent:
2805 mainWebView.getSettings().setTextZoom(50);
2808 case R.id.font_size_seventy_five_percent:
2809 mainWebView.getSettings().setTextZoom(75);
2812 case R.id.font_size_one_hundred_percent:
2813 mainWebView.getSettings().setTextZoom(100);
2816 case R.id.font_size_one_hundred_twenty_five_percent:
2817 mainWebView.getSettings().setTextZoom(125);
2820 case R.id.font_size_one_hundred_fifty_percent:
2821 mainWebView.getSettings().setTextZoom(150);
2824 case R.id.font_size_one_hundred_seventy_five_percent:
2825 mainWebView.getSettings().setTextZoom(175);
2828 case R.id.font_size_two_hundred_percent:
2829 mainWebView.getSettings().setTextZoom(200);
2832 case R.id.swipe_to_refresh:
2833 // Toggle swipe to refresh.
2834 swipeRefreshLayout.setEnabled(!swipeRefreshLayout.isEnabled());
2837 case R.id.display_images:
2838 if (mainWebView.getSettings().getLoadsImagesAutomatically()) { // Images are currently loaded automatically.
2839 mainWebView.getSettings().setLoadsImagesAutomatically(false);
2840 mainWebView.reload();
2841 } else { // Images are not currently loaded automatically.
2842 mainWebView.getSettings().setLoadsImagesAutomatically(true);
2846 case R.id.night_mode:
2847 // Toggle night mode.
2848 nightMode = !nightMode;
2850 // Enable or disable JavaScript according to night mode, the global preference, and any domain settings.
2851 if (nightMode) { // Night mode is enabled. Enable JavaScript.
2852 // Update the global variable.
2853 javaScriptEnabled = true;
2854 } else if (domainSettingsApplied) { // Night mode is disabled and domain settings are applied. Set JavaScript according to the domain settings.
2855 // Get the JavaScript preference that was stored the last time domain settings were loaded.
2856 javaScriptEnabled = domainSettingsJavaScriptEnabled;
2857 } else { // Night mode is disabled and domain settings are not applied. Set JavaScript according to the global preference.
2858 // Get a handle for the shared preference.
2859 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
2861 // Get the JavaScript preference.
2862 javaScriptEnabled = sharedPreferences.getBoolean("javascript", false);
2865 // Apply the JavaScript setting to the WebView.
2866 mainWebView.getSettings().setJavaScriptEnabled(javaScriptEnabled);
2868 // Update the privacy icons.
2869 updatePrivacyIcons(false);
2871 // Reload the website.
2872 mainWebView.reload();
2875 case R.id.find_on_page:
2876 // Hide the URL app bar.
2877 supportAppBar.setVisibility(View.GONE);
2879 // Show the Find on Page `RelativeLayout`.
2880 findOnPageLinearLayout.setVisibility(View.VISIBLE);
2882 // Display the keyboard. We have to wait 200 ms before running the command to work around a bug in Android.
2883 // http://stackoverflow.com/questions/5520085/android-show-softkeyboard-with-showsoftinput-is-not-working
2884 findOnPageEditText.postDelayed(() -> {
2885 // Set the focus on `findOnPageEditText`.
2886 findOnPageEditText.requestFocus();
2888 // Display the keyboard. `0` sets no input flags.
2889 inputMethodManager.showSoftInput(findOnPageEditText, 0);
2893 case R.id.view_source:
2894 // Launch the View Source activity.
2895 Intent viewSourceIntent = new Intent(this, ViewSourceActivity.class);
2896 startActivity(viewSourceIntent);
2899 case R.id.share_url:
2900 // Setup the share string.
2901 String shareString = webViewTitle + " – " + urlTextBox.getText().toString();
2903 // Create the share intent.
2904 Intent shareIntent = new Intent(Intent.ACTION_SEND);
2905 shareIntent.putExtra(Intent.EXTRA_TEXT, shareString);
2906 shareIntent.setType("text/plain");
2909 startActivity(Intent.createChooser(shareIntent, getString(R.string.share_url)));
2913 // Get a `PrintManager` instance.
2914 PrintManager printManager = (PrintManager) getSystemService(Context.PRINT_SERVICE);
2916 // Convert `mainWebView` to `printDocumentAdapter`.
2917 PrintDocumentAdapter printDocumentAdapter = mainWebView.createPrintDocumentAdapter();
2919 // Remove the lint error below that `printManager` might be `null`.
2920 assert printManager != null;
2922 // Print the document. The print attributes are `null`.
2923 printManager.print(getString(R.string.privacy_browser_web_page), printDocumentAdapter, null);
2926 case R.id.open_with_app:
2927 openWithApp(formattedUrlString);
2930 case R.id.open_with_browser:
2931 openWithBrowser(formattedUrlString);
2934 case R.id.add_to_homescreen:
2935 // Show the alert dialog.
2936 AppCompatDialogFragment createHomeScreenShortcutDialogFragment = new CreateHomeScreenShortcutDialog();
2937 createHomeScreenShortcutDialogFragment.show(getSupportFragmentManager(), getString(R.string.create_shortcut));
2939 //Everything else will be handled by the alert dialog and the associated listener below.
2942 case R.id.proxy_through_orbot:
2943 // Toggle the proxy through Orbot variable.
2944 proxyThroughOrbot = !proxyThroughOrbot;
2946 // Apply the proxy through Orbot settings.
2947 applyProxyThroughOrbot(true);
2951 if (menuItem.getTitle().equals(getString(R.string.refresh))) { // The refresh button was pushed.
2952 // Reload the WebView.
2953 mainWebView.reload();
2954 } else { // The stop button was pushed.
2955 // Stop the loading of the WebView.
2956 mainWebView.stopLoading();
2960 case R.id.ad_consent:
2961 // Display the ad consent dialog.
2962 DialogFragment adConsentDialogFragment = new AdConsentDialog();
2963 adConsentDialogFragment.show(getFragmentManager(), getString(R.string.ad_consent));
2967 // Don't consume the event.
2968 return super.onOptionsItemSelected(menuItem);
2972 // removeAllCookies is deprecated, but it is required for API < 21.
2973 @SuppressWarnings("deprecation")
2975 public boolean onNavigationItemSelected(@NonNull MenuItem menuItem) {
2976 int menuItemId = menuItem.getItemId();
2978 switch (menuItemId) {
2984 if (mainWebView.canGoBack()) {
2985 // Reset the formatted URL string so the page will load correctly if blocking of third-party requests is enabled.
2986 formattedUrlString = "";
2988 // Set `navigatingHistory` so that the domain settings are applied when the new URL is loaded.
2989 navigatingHistory = true;
2991 // Load the previous website in the history.
2992 mainWebView.goBack();
2997 if (mainWebView.canGoForward()) {
2998 // Reset the formatted URL string so the page will load correctly if blocking of third-party requests is enabled.
2999 formattedUrlString = "";
3001 // Set `navigatingHistory` so that the domain settings are applied when the new URL is loaded.
3002 navigatingHistory = true;
3004 // Load the next website in the history.
3005 mainWebView.goForward();
3010 // Get the `WebBackForwardList`.
3011 WebBackForwardList webBackForwardList = mainWebView.copyBackForwardList();
3013 // Show the `UrlHistoryDialog` `AlertDialog` and name this instance `R.string.history`. `this` is the `Context`.
3014 AppCompatDialogFragment urlHistoryDialogFragment = UrlHistoryDialog.loadBackForwardList(this, webBackForwardList);
3015 urlHistoryDialogFragment.show(getSupportFragmentManager(), getString(R.string.history));
3019 // Launch the requests activity.
3020 Intent requestsIntent = new Intent(this, RequestsActivity.class);
3021 startActivity(requestsIntent);
3024 case R.id.downloads:
3025 // Launch the system Download Manager.
3026 Intent downloadManagerIntent = new Intent(DownloadManager.ACTION_VIEW_DOWNLOADS);
3028 // Launch as a new task so that Download Manager and Privacy Browser show as separate windows in the recent tasks list.
3029 downloadManagerIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
3031 startActivity(downloadManagerIntent);
3035 // Set the flag to reapply the domain settings on restart when returning from Domain Settings.
3036 reapplyDomainSettingsOnRestart = true;
3037 currentDomainName = "";
3039 // Launch the domains activity.
3040 Intent domainsIntent = new Intent(this, DomainsActivity.class);
3041 startActivity(domainsIntent);
3045 // Set the flag to reapply app settings on restart when returning from Settings.
3046 reapplyAppSettingsOnRestart = true;
3048 // Set the flag to reapply the domain settings on restart when returning from Settings.
3049 reapplyDomainSettingsOnRestart = true;
3050 currentDomainName = "";
3052 // Launch the settings activity.
3053 Intent settingsIntent = new Intent(this, SettingsActivity.class);
3054 startActivity(settingsIntent);
3057 case R.id.import_export:
3058 // Launch the import/export activity.
3059 Intent importExportIntent = new Intent (this, ImportExportActivity.class);
3060 startActivity(importExportIntent);
3064 // Launch `GuideActivity`.
3065 Intent guideIntent = new Intent(this, GuideActivity.class);
3066 startActivity(guideIntent);
3070 // Launch `AboutActivity`.
3071 Intent aboutIntent = new Intent(this, AboutActivity.class);
3072 startActivity(aboutIntent);
3075 case R.id.clearAndExit:
3076 // Close the bookmarks cursor and database.
3077 bookmarksCursor.close();
3078 bookmarksDatabaseHelper.close();
3080 // Get a handle for `sharedPreferences`. `this` references the current context.
3081 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
3083 boolean clearEverything = sharedPreferences.getBoolean("clear_everything", true);
3086 if (clearEverything || sharedPreferences.getBoolean("clear_cookies", true)) {
3087 // The command to remove cookies changed slightly in API 21.
3088 if (Build.VERSION.SDK_INT >= 21) {
3089 cookieManager.removeAllCookies(null);
3091 cookieManager.removeAllCookie();
3094 // Manually delete the cookies database, as `CookieManager` sometimes will not flush its changes to disk before `System.exit(0)` is run.
3096 // We have to use two commands because `Runtime.exec()` does not like `*`.
3097 privacyBrowserRuntime.exec("rm -f " + privateDataDirectoryString + "/app_webview/Cookies");
3098 privacyBrowserRuntime.exec("rm -f " + privateDataDirectoryString + "/app_webview/Cookies-journal");
3099 } catch (IOException e) {
3100 // Do nothing if an error is thrown.
3104 // Clear DOM storage.
3105 if (clearEverything || sharedPreferences.getBoolean("clear_dom_storage", true)) {
3106 // Ask `WebStorage` to clear the DOM storage.
3107 WebStorage webStorage = WebStorage.getInstance();
3108 webStorage.deleteAllData();
3110 // Manually delete the DOM storage files and directories, as `WebStorage` sometimes will not flush its changes to disk before `System.exit(0)` is run.
3112 // A `String[]` must be used because the directory contains a space and `Runtime.exec` will otherwise not escape the string correctly.
3113 privacyBrowserRuntime.exec(new String[] {"rm", "-rf", privateDataDirectoryString + "/app_webview/Local Storage/"});
3115 // Multiple commands must be used because `Runtime.exec()` does not like `*`.
3116 privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/IndexedDB");
3117 privacyBrowserRuntime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager");
3118 privacyBrowserRuntime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager-journal");
3119 privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/databases");
3120 } catch (IOException e) {
3121 // Do nothing if an error is thrown.
3125 // Clear form data if the API < 26.
3126 if ((Build.VERSION.SDK_INT < 26) && (clearEverything || sharedPreferences.getBoolean("clear_form_data", true))) {
3127 WebViewDatabase webViewDatabase = WebViewDatabase.getInstance(this);
3128 webViewDatabase.clearFormData();
3130 // Manually delete the form data database, as `WebViewDatabase` sometimes will not flush its changes to disk before `System.exit(0)` is run.
3132 // We have to use a `String[]` because the database contains a space and `Runtime.exec` will not escape the string correctly otherwise.
3133 privacyBrowserRuntime.exec(new String[] {"rm", "-f", privateDataDirectoryString + "/app_webview/Web Data"});
3134 privacyBrowserRuntime.exec(new String[] {"rm", "-f", privateDataDirectoryString + "/app_webview/Web Data-journal"});
3135 } catch (IOException e) {
3136 // Do nothing if an error is thrown.
3141 if (clearEverything || sharedPreferences.getBoolean("clear_cache", true)) {
3142 // `true` includes disk files.
3143 mainWebView.clearCache(true);
3145 // Manually delete the cache directories.
3147 // Delete the main cache directory.
3148 privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/cache");
3150 // Delete the secondary `Service Worker` cache directory.
3151 // A `String[]` must be used because the directory contains a space and `Runtime.exec` will not escape the string correctly otherwise.
3152 privacyBrowserRuntime.exec(new String[] {"rm", "-rf", privateDataDirectoryString + "/app_webview/Service Worker/"});
3153 } catch (IOException e) {
3154 // Do nothing if an error is thrown.
3158 // Clear SSL certificate preferences.
3159 mainWebView.clearSslPreferences();
3161 // Clear the back/forward history.
3162 mainWebView.clearHistory();
3164 // Clear `formattedUrlString`.
3165 formattedUrlString = null;
3167 // Clear `customHeaders`.
3168 customHeaders.clear();
3170 // Detach all views from `mainWebViewRelativeLayout`.
3171 mainWebViewRelativeLayout.removeAllViews();
3173 // Destroy the internal state of `mainWebView`.
3174 mainWebView.destroy();
3176 // Manually delete the `app_webview` folder, which contains the cookies, DOM storage, form data, and `Service Worker` cache.
3177 // See `https://code.google.com/p/android/issues/detail?id=233826&thanks=233826&ts=1486670530`.
3178 if (clearEverything) {
3180 privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/app_webview");
3181 } catch (IOException e) {
3182 // Do nothing if an error is thrown.
3186 // Close Privacy Browser. `finishAndRemoveTask` also removes Privacy Browser from the recent app list.
3187 if (Build.VERSION.SDK_INT >= 21) {
3188 finishAndRemoveTask();
3193 // Remove the terminated program from RAM. The status code is `0`.
3198 // Close the navigation drawer.
3199 drawerLayout.closeDrawer(GravityCompat.START);
3204 public void onPostCreate(Bundle savedInstanceState) {
3205 super.onPostCreate(savedInstanceState);
3207 // Sync the state of the DrawerToggle after onRestoreInstanceState has finished.
3208 drawerToggle.syncState();
3212 public void onConfigurationChanged(Configuration newConfig) {
3213 super.onConfigurationChanged(newConfig);
3215 // Get the status bar pixel size.
3216 int statusBarResourceId = getResources().getIdentifier("status_bar_height", "dimen", "android");
3217 int statusBarPixelSize = getResources().getDimensionPixelSize(statusBarResourceId);
3219 // Get the resource density.
3220 float screenDensity = getResources().getDisplayMetrics().density;
3222 // Recalculate the drawer header padding.
3223 drawerHeaderPaddingLeftAndRight = (int) (15 * screenDensity);
3224 drawerHeaderPaddingTop = statusBarPixelSize + (int) (4 * screenDensity);
3225 drawerHeaderPaddingBottom = (int) (8 * screenDensity);
3227 // Reload the ad for the free flavor if not in full screen mode.
3228 if (BuildConfig.FLAVOR.contentEquals("free") && !inFullScreenBrowsingMode) {
3229 // Reload the ad. The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations.
3230 AdHelper.loadAd(findViewById(R.id.adview), getApplicationContext(), getString(R.string.ad_unit_id));
3233 // `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:
3234 // https://code.google.com/p/android/issues/detail?id=20493#c8
3235 // ActivityCompat.invalidateOptionsMenu(this);
3239 public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.ContextMenuInfo menuInfo) {
3240 // Store the `HitTestResult`.
3241 final WebView.HitTestResult hitTestResult = mainWebView.getHitTestResult();
3244 final String imageUrl;
3245 final String linkUrl;
3247 // Get a handle for the `ClipboardManager`.
3248 final ClipboardManager clipboardManager = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
3250 // Remove the lint errors below that `clipboardManager` might be `null`.
3251 assert clipboardManager != null;
3253 switch (hitTestResult.getType()) {
3254 // `SRC_ANCHOR_TYPE` is a link.
3255 case WebView.HitTestResult.SRC_ANCHOR_TYPE:
3256 // Get the target URL.
3257 linkUrl = hitTestResult.getExtra();
3259 // Set the target URL as the title of the `ContextMenu`.
3260 menu.setHeaderTitle(linkUrl);
3262 // Add a Load URL entry.
3263 menu.add(R.string.load_url).setOnMenuItemClickListener((MenuItem item) -> {
3268 // Add a Copy URL entry.
3269 menu.add(R.string.copy_url).setOnMenuItemClickListener((MenuItem item) -> {
3270 // Save the link URL in a `ClipData`.
3271 ClipData srcAnchorTypeClipData = ClipData.newPlainText(getString(R.string.url), linkUrl);
3273 // Set the `ClipData` as the clipboard's primary clip.
3274 clipboardManager.setPrimaryClip(srcAnchorTypeClipData);
3278 // Add a Download URL entry.
3279 menu.add(R.string.download_url).setOnMenuItemClickListener((MenuItem item) -> {
3280 // Check if the download should be processed by an external app.
3281 if (downloadWithExternalApp) { // Download with an external app.
3282 openUrlWithExternalApp(linkUrl);
3283 } else { // Download with Android's download manager.
3284 // Check to see if the storage permission has already been granted.
3285 if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) { // The storage permission needs to be requested.
3286 // Store the variables for future use by `onRequestPermissionsResult()`.
3287 downloadUrl = linkUrl;
3288 downloadContentDisposition = "none";
3289 downloadContentLength = -1;
3291 // Show a dialog if the user has previously denied the permission.
3292 if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) { // Show a dialog explaining the request first.
3293 // Instantiate the download location permission alert dialog and set the download type to DOWNLOAD_FILE.
3294 DialogFragment downloadLocationPermissionDialogFragment = DownloadLocationPermissionDialog.downloadType(DownloadLocationPermissionDialog.DOWNLOAD_FILE);
3296 // Show the download location permission alert dialog. The permission will be requested when the the dialog is closed.
3297 downloadLocationPermissionDialogFragment.show(getFragmentManager(), getString(R.string.download_location));
3298 } else { // Show the permission request directly.
3299 // Request the permission. The download dialog will be launched by `onRequestPermissionResult()`.
3300 ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_FILE_REQUEST_CODE);
3302 } else { // The storage permission has already been granted.
3303 // Get a handle for the download file alert dialog.
3304 AppCompatDialogFragment downloadFileDialogFragment = DownloadFileDialog.fromUrl(linkUrl, "none", -1);
3306 // Show the download file alert dialog.
3307 downloadFileDialogFragment.show(getSupportFragmentManager(), getString(R.string.download));
3313 // Add an Open with App entry.
3314 menu.add(R.string.open_with_app).setOnMenuItemClickListener((MenuItem item) -> {
3315 openWithApp(linkUrl);
3319 // Add an Open with Browser entry.
3320 menu.add(R.string.open_with_browser).setOnMenuItemClickListener((MenuItem item) -> {
3321 openWithBrowser(linkUrl);
3325 // Add a Cancel entry, which by default closes the context menu.
3326 menu.add(R.string.cancel);
3329 case WebView.HitTestResult.EMAIL_TYPE:
3330 // Get the target URL.
3331 linkUrl = hitTestResult.getExtra();
3333 // Set the target URL as the title of the `ContextMenu`.
3334 menu.setHeaderTitle(linkUrl);
3336 // Add a Write Email entry.
3337 menu.add(R.string.write_email).setOnMenuItemClickListener(item -> {
3338 // Use `ACTION_SENDTO` instead of `ACTION_SEND` so that only email programs are launched.
3339 Intent emailIntent = new Intent(Intent.ACTION_SENDTO);
3341 // Parse the url and set it as the data for the `Intent`.
3342 emailIntent.setData(Uri.parse("mailto:" + linkUrl));
3344 // `FLAG_ACTIVITY_NEW_TASK` opens the email program in a new task instead as part of Privacy Browser.
3345 emailIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
3348 startActivity(emailIntent);
3352 // Add a Copy Email Address entry.
3353 menu.add(R.string.copy_email_address).setOnMenuItemClickListener(item -> {
3354 // Save the email address in a `ClipData`.
3355 ClipData srcEmailTypeClipData = ClipData.newPlainText(getString(R.string.email_address), linkUrl);
3357 // Set the `ClipData` as the clipboard's primary clip.
3358 clipboardManager.setPrimaryClip(srcEmailTypeClipData);
3362 // Add a `Cancel` entry, which by default closes the `ContextMenu`.
3363 menu.add(R.string.cancel);
3366 // `IMAGE_TYPE` is an image.
3367 case WebView.HitTestResult.IMAGE_TYPE:
3368 // Get the image URL.
3369 imageUrl = hitTestResult.getExtra();
3371 // Set the image URL as the title of the `ContextMenu`.
3372 menu.setHeaderTitle(imageUrl);
3374 // Add a View Image entry.
3375 menu.add(R.string.view_image).setOnMenuItemClickListener(item -> {
3380 // Add a Download Image entry.
3381 menu.add(R.string.download_image).setOnMenuItemClickListener((MenuItem item) -> {
3382 // Check if the download should be processed by an external app.
3383 if (downloadWithExternalApp) { // Download with an external app.
3384 openUrlWithExternalApp(imageUrl);
3385 } else { // Download with Android's download manager.
3386 // Check to see if the storage permission has already been granted.
3387 if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) { // The storage permission needs to be requested.
3388 // Store the image URL for use by `onRequestPermissionResult()`.
3389 downloadImageUrl = imageUrl;
3391 // Show a dialog if the user has previously denied the permission.
3392 if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) { // Show a dialog explaining the request first.
3393 // Instantiate the download location permission alert dialog and set the download type to DOWNLOAD_IMAGE.
3394 DialogFragment downloadLocationPermissionDialogFragment = DownloadLocationPermissionDialog.downloadType(DownloadLocationPermissionDialog.DOWNLOAD_IMAGE);
3396 // Show the download location permission alert dialog. The permission will be requested when the dialog is closed.
3397 downloadLocationPermissionDialogFragment.show(getFragmentManager(), getString(R.string.download_location));
3398 } else { // Show the permission request directly.
3399 // Request the permission. The download dialog will be launched by `onRequestPermissionResult().
3400 ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_IMAGE_REQUEST_CODE);
3402 } else { // The storage permission has already been granted.
3403 // Get a handle for the download image alert dialog.
3404 AppCompatDialogFragment downloadImageDialogFragment = DownloadImageDialog.imageUrl(imageUrl);
3406 // Show the download image alert dialog.
3407 downloadImageDialogFragment.show(getSupportFragmentManager(), getString(R.string.download));
3413 // Add a Copy URL entry.
3414 menu.add(R.string.copy_url).setOnMenuItemClickListener(item -> {
3415 // Save the image URL in a `ClipData`.
3416 ClipData srcImageTypeClipData = ClipData.newPlainText(getString(R.string.url), imageUrl);
3418 // Set the `ClipData` as the clipboard's primary clip.
3419 clipboardManager.setPrimaryClip(srcImageTypeClipData);
3423 // Add an Open with App entry.
3424 menu.add(R.string.open_with_app).setOnMenuItemClickListener((MenuItem item) -> {
3425 openWithApp(imageUrl);
3429 // Add an Open with Browser entry.
3430 menu.add(R.string.open_with_browser).setOnMenuItemClickListener((MenuItem item) -> {
3431 openWithBrowser(imageUrl);
3435 // Add a `Cancel` entry, which by default closes the `ContextMenu`.
3436 menu.add(R.string.cancel);
3440 // `SRC_IMAGE_ANCHOR_TYPE` is an image that is also a link.
3441 case WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE:
3442 // Get the image URL.
3443 imageUrl = hitTestResult.getExtra();
3445 // Set the image URL as the title of the `ContextMenu`.
3446 menu.setHeaderTitle(imageUrl);
3448 // Add a `View Image` entry.
3449 menu.add(R.string.view_image).setOnMenuItemClickListener(item -> {
3454 // Add a `Download Image` entry.
3455 menu.add(R.string.download_image).setOnMenuItemClickListener((MenuItem item) -> {
3456 // Check if the download should be processed by an external app.
3457 if (downloadWithExternalApp) { // Download with an external app.
3458 openUrlWithExternalApp(imageUrl);
3459 } else { // Download with Android's download manager.
3460 // Check to see if the storage permission has already been granted.
3461 if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) { // The storage permission needs to be requested.
3462 // Store the image URL for use by `onRequestPermissionResult()`.
3463 downloadImageUrl = imageUrl;
3465 // Show a dialog if the user has previously denied the permission.
3466 if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) { // Show a dialog explaining the request first.
3467 // Instantiate the download location permission alert dialog and set the download type to DOWNLOAD_IMAGE.
3468 DialogFragment downloadLocationPermissionDialogFragment = DownloadLocationPermissionDialog.downloadType(DownloadLocationPermissionDialog.DOWNLOAD_IMAGE);
3470 // Show the download location permission alert dialog. The permission will be requested when the dialog is closed.
3471 downloadLocationPermissionDialogFragment.show(getFragmentManager(), getString(R.string.download_location));
3472 } else { // Show the permission request directly.
3473 // Request the permission. The download dialog will be launched by `onRequestPermissionResult().
3474 ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_IMAGE_REQUEST_CODE);
3476 } else { // The storage permission has already been granted.
3477 // Get a handle for the download image alert dialog.
3478 AppCompatDialogFragment downloadImageDialogFragment = DownloadImageDialog.imageUrl(imageUrl);
3480 // Show the download image alert dialog.
3481 downloadImageDialogFragment.show(getSupportFragmentManager(), getString(R.string.download));
3487 // Add a `Copy URL` entry.
3488 menu.add(R.string.copy_url).setOnMenuItemClickListener(item -> {
3489 // Save the image URL in a `ClipData`.
3490 ClipData srcImageAnchorTypeClipData = ClipData.newPlainText(getString(R.string.url), imageUrl);
3492 // Set the `ClipData` as the clipboard's primary clip.
3493 clipboardManager.setPrimaryClip(srcImageAnchorTypeClipData);
3497 // Add an Open with App entry.
3498 menu.add(R.string.open_with_app).setOnMenuItemClickListener((MenuItem item) -> {
3499 openWithApp(imageUrl);
3503 // Add an Open with Browser entry.
3504 menu.add(R.string.open_with_browser).setOnMenuItemClickListener((MenuItem item) -> {
3505 openWithBrowser(imageUrl);
3509 // Add a `Cancel` entry, which by default closes the `ContextMenu`.
3510 menu.add(R.string.cancel);
3516 public void onCreateBookmark(AppCompatDialogFragment dialogFragment) {
3517 // Get the `EditTexts` from the `dialogFragment`.
3518 EditText createBookmarkNameEditText = dialogFragment.getDialog().findViewById(R.id.create_bookmark_name_edittext);
3519 EditText createBookmarkUrlEditText = dialogFragment.getDialog().findViewById(R.id.create_bookmark_url_edittext);
3521 // Extract the strings from the `EditTexts`.
3522 String bookmarkNameString = createBookmarkNameEditText.getText().toString();
3523 String bookmarkUrlString = createBookmarkUrlEditText.getText().toString();
3525 // Convert the favoriteIcon Bitmap to a byte array. `0` is for lossless compression (the only option for a PNG).
3526 ByteArrayOutputStream favoriteIconByteArrayOutputStream = new ByteArrayOutputStream();
3527 favoriteIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, favoriteIconByteArrayOutputStream);
3528 byte[] favoriteIconByteArray = favoriteIconByteArrayOutputStream.toByteArray();
3530 // Display the new bookmark below the current items in the (0 indexed) list.
3531 int newBookmarkDisplayOrder = bookmarksListView.getCount();
3533 // Create the bookmark.
3534 bookmarksDatabaseHelper.createBookmark(bookmarkNameString, bookmarkUrlString, currentBookmarksFolder, newBookmarkDisplayOrder, favoriteIconByteArray);
3536 // Update the bookmarks cursor with the current contents of this folder.
3537 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder);
3539 // Update the `ListView`.
3540 bookmarksCursorAdapter.changeCursor(bookmarksCursor);
3542 // Scroll to the new bookmark.
3543 bookmarksListView.setSelection(newBookmarkDisplayOrder);
3547 public void onCreateBookmarkFolder(AppCompatDialogFragment dialogFragment) {
3548 // Get handles for the views in `dialogFragment`.
3549 EditText createFolderNameEditText = dialogFragment.getDialog().findViewById(R.id.create_folder_name_edittext);
3550 RadioButton defaultFolderIconRadioButton = dialogFragment.getDialog().findViewById(R.id.create_folder_default_icon_radiobutton);
3551 ImageView folderIconImageView = dialogFragment.getDialog().findViewById(R.id.create_folder_default_icon);
3553 // Get new folder name string.
3554 String folderNameString = createFolderNameEditText.getText().toString();
3556 // Get the new folder icon `Bitmap`.
3557 Bitmap folderIconBitmap;
3558 if (defaultFolderIconRadioButton.isChecked()) { // Use the default folder icon.
3559 // Get the default folder icon and convert it to a `Bitmap`.
3560 Drawable folderIconDrawable = folderIconImageView.getDrawable();
3561 BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable;
3562 folderIconBitmap = folderIconBitmapDrawable.getBitmap();
3563 } else { // Use the `WebView` favorite icon.
3564 folderIconBitmap = favoriteIconBitmap;
3567 // Convert `folderIconBitmap` to a byte array. `0` is for lossless compression (the only option for a PNG).
3568 ByteArrayOutputStream folderIconByteArrayOutputStream = new ByteArrayOutputStream();
3569 folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, folderIconByteArrayOutputStream);
3570 byte[] folderIconByteArray = folderIconByteArrayOutputStream.toByteArray();
3572 // Move all the bookmarks down one in the display order.
3573 for (int i = 0; i < bookmarksListView.getCount(); i++) {
3574 int databaseId = (int) bookmarksListView.getItemIdAtPosition(i);
3575 bookmarksDatabaseHelper.updateDisplayOrder(databaseId, i + 1);
3578 // Create the folder, which will be placed at the top of the `ListView`.
3579 bookmarksDatabaseHelper.createFolder(folderNameString, currentBookmarksFolder, folderIconByteArray);
3581 // Update the bookmarks cursor with the current contents of this folder.
3582 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder);
3584 // Update the `ListView`.
3585 bookmarksCursorAdapter.changeCursor(bookmarksCursor);
3587 // Scroll to the new folder.
3588 bookmarksListView.setSelection(0);
3592 public void onCreateHomeScreenShortcut(AppCompatDialogFragment dialogFragment) {
3593 // Get the shortcut name.
3594 EditText shortcutNameEditText = dialogFragment.getDialog().findViewById(R.id.shortcut_name_edittext);
3595 String shortcutNameString = shortcutNameEditText.getText().toString();
3597 // Convert the favorite icon bitmap to an `Icon`. `IconCompat` is required until API >= 26.
3598 IconCompat favoriteIcon = IconCompat.createWithBitmap(favoriteIconBitmap);
3600 // Setup the shortcut intent.
3601 Intent shortcutIntent = new Intent(Intent.ACTION_VIEW);
3602 shortcutIntent.setData(Uri.parse(formattedUrlString));
3604 // Create a shortcut info builder. The shortcut name becomes the shortcut ID.
3605 ShortcutInfoCompat.Builder shortcutInfoBuilder = new ShortcutInfoCompat.Builder(this, shortcutNameString);
3607 // Add the required fields to the shortcut info builder.
3608 shortcutInfoBuilder.setIcon(favoriteIcon);
3609 shortcutInfoBuilder.setIntent(shortcutIntent);
3610 shortcutInfoBuilder.setShortLabel(shortcutNameString);
3612 // Request the pin. `ShortcutManagerCompat` can be switched to `ShortcutManager` once API >= 26.
3613 ShortcutManagerCompat.requestPinShortcut(this, shortcutInfoBuilder.build(), null);
3617 public void onCloseDownloadLocationPermissionDialog(int downloadType) {
3618 switch (downloadType) {
3619 case DownloadLocationPermissionDialog.DOWNLOAD_FILE:
3620 // Request the WRITE_EXTERNAL_STORAGE permission with a file request code.
3621 ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_FILE_REQUEST_CODE);
3624 case DownloadLocationPermissionDialog.DOWNLOAD_IMAGE:
3625 // Request the WRITE_EXTERNAL_STORAGE permission with an image request code.
3626 ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_IMAGE_REQUEST_CODE);
3632 public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
3633 switch (requestCode) {
3634 case DOWNLOAD_FILE_REQUEST_CODE:
3635 // Show the download file alert dialog. When the dialog closes, the correct command will be used based on the permission status.
3636 AppCompatDialogFragment downloadFileDialogFragment = DownloadFileDialog.fromUrl(downloadUrl, downloadContentDisposition, downloadContentLength);
3638 // On API 23, displaying the fragment must be delayed or the app will crash.
3639 if (Build.VERSION.SDK_INT == 23) {
3640 new Handler().postDelayed(() -> downloadFileDialogFragment.show(getSupportFragmentManager(), getString(R.string.download)), 500);
3642 downloadFileDialogFragment.show(getSupportFragmentManager(), getString(R.string.download));
3645 // Reset the download variables.
3647 downloadContentDisposition = "";
3648 downloadContentLength = 0;
3651 case DOWNLOAD_IMAGE_REQUEST_CODE:
3652 // Show the download image alert dialog. When the dialog closes, the correct command will be used based on the permission status.
3653 AppCompatDialogFragment downloadImageDialogFragment = DownloadImageDialog.imageUrl(downloadImageUrl);
3655 // On API 23, displaying the fragment must be delayed or the app will crash.
3656 if (Build.VERSION.SDK_INT == 23) {
3657 new Handler().postDelayed(() -> downloadImageDialogFragment.show(getSupportFragmentManager(), getString(R.string.download)), 500);
3659 downloadImageDialogFragment.show(getSupportFragmentManager(), getString(R.string.download));
3662 // Reset the image URL variable.
3663 downloadImageUrl = "";
3669 public void onDownloadImage(AppCompatDialogFragment dialogFragment, String imageUrl) {
3670 // Download the image if it has an HTTP or HTTPS URI.
3671 if (imageUrl.startsWith("http")) {
3672 // Get a handle for the system `DOWNLOAD_SERVICE`.
3673 DownloadManager downloadManager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
3675 // Parse `imageUrl`.
3676 DownloadManager.Request downloadRequest = new DownloadManager.Request(Uri.parse(imageUrl));
3678 // Pass cookies to download manager if cookies are enabled. This is required to download images from websites that require a login.
3679 // Code contributed 2017 Hendrik Knackstedt. Copyright assigned to Soren Stoutner <soren@stoutner.com>.
3680 if (firstPartyCookiesEnabled) {
3681 // Get the cookies for `imageUrl`.
3682 String cookies = cookieManager.getCookie(imageUrl);
3684 // Add the cookies to `downloadRequest`. In the HTTP request header, cookies are named `Cookie`.
3685 downloadRequest.addRequestHeader("Cookie", cookies);
3688 // Get the file name from the dialog fragment.
3689 EditText downloadImageNameEditText = dialogFragment.getDialog().findViewById(R.id.download_image_name);
3690 String imageName = downloadImageNameEditText.getText().toString();
3692 // Specify the download location.
3693 if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { // External write permission granted.
3694 // Download to the public download directory.
3695 downloadRequest.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, imageName);
3696 } else { // External write permission denied.
3697 // Download to the app's external download directory.
3698 downloadRequest.setDestinationInExternalFilesDir(this, Environment.DIRECTORY_DOWNLOADS, imageName);
3701 // Allow `MediaScanner` to index the download if it is a media file.
3702 downloadRequest.allowScanningByMediaScanner();
3704 // Add the URL as the description for the download.
3705 downloadRequest.setDescription(imageUrl);
3707 // Show the download notification after the download is completed.
3708 downloadRequest.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
3710 // Remove the lint warning below that `downloadManager` might be `null`.
3711 assert downloadManager != null;
3713 // Initiate the download.
3714 downloadManager.enqueue(downloadRequest);
3715 } else { // The image is not an HTTP or HTTPS URI.
3716 Snackbar.make(mainWebView, R.string.cannot_download_image, Snackbar.LENGTH_INDEFINITE).show();
3721 public void onDownloadFile(AppCompatDialogFragment dialogFragment, String downloadUrl) {
3722 // Download the file if it has an HTTP or HTTPS URI.
3723 if (downloadUrl.startsWith("http")) {
3724 // Get a handle for the system `DOWNLOAD_SERVICE`.
3725 DownloadManager downloadManager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
3727 // Parse `downloadUrl`.
3728 DownloadManager.Request downloadRequest = new DownloadManager.Request(Uri.parse(downloadUrl));
3730 // Pass cookies to download manager if cookies are enabled. This is required to download files from websites that require a login.
3731 // Code contributed 2017 Hendrik Knackstedt. Copyright assigned to Soren Stoutner <soren@stoutner.com>.
3732 if (firstPartyCookiesEnabled) {
3733 // Get the cookies for `downloadUrl`.
3734 String cookies = cookieManager.getCookie(downloadUrl);
3736 // Add the cookies to `downloadRequest`. In the HTTP request header, cookies are named `Cookie`.
3737 downloadRequest.addRequestHeader("Cookie", cookies);
3740 // Get the file name from the dialog fragment.
3741 EditText downloadFileNameEditText = dialogFragment.getDialog().findViewById(R.id.download_file_name);
3742 String fileName = downloadFileNameEditText.getText().toString();
3744 // Specify the download location.
3745 if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { // External write permission granted.
3746 // Download to the public download directory.
3747 downloadRequest.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, fileName);
3748 } else { // External write permission denied.
3749 // Download to the app's external download directory.
3750 downloadRequest.setDestinationInExternalFilesDir(this, Environment.DIRECTORY_DOWNLOADS, fileName);
3753 // Allow `MediaScanner` to index the download if it is a media file.
3754 downloadRequest.allowScanningByMediaScanner();
3756 // Add the URL as the description for the download.
3757 downloadRequest.setDescription(downloadUrl);
3759 // Show the download notification after the download is completed.
3760 downloadRequest.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
3762 // Remove the lint warning below that `downloadManager` might be `null`.
3763 assert downloadManager != null;
3765 // Initiate the download.
3766 downloadManager.enqueue(downloadRequest);
3767 } else { // The download is not an HTTP or HTTPS URI.
3768 Snackbar.make(mainWebView, R.string.cannot_download_file, Snackbar.LENGTH_INDEFINITE).show();
3773 public void onSaveBookmark(AppCompatDialogFragment dialogFragment, int selectedBookmarkDatabaseId) {
3774 // Get handles for the views from `dialogFragment`.
3775 EditText editBookmarkNameEditText = dialogFragment.getDialog().findViewById(R.id.edit_bookmark_name_edittext);
3776 EditText editBookmarkUrlEditText = dialogFragment.getDialog().findViewById(R.id.edit_bookmark_url_edittext);
3777 RadioButton currentBookmarkIconRadioButton = dialogFragment.getDialog().findViewById(R.id.edit_bookmark_current_icon_radiobutton);
3779 // Store the bookmark strings.
3780 String bookmarkNameString = editBookmarkNameEditText.getText().toString();
3781 String bookmarkUrlString = editBookmarkUrlEditText.getText().toString();
3783 // Update the bookmark.
3784 if (currentBookmarkIconRadioButton.isChecked()) { // Update the bookmark without changing the favorite icon.
3785 bookmarksDatabaseHelper.updateBookmark(selectedBookmarkDatabaseId, bookmarkNameString, bookmarkUrlString);
3786 } else { // Update the bookmark using the `WebView` favorite icon.
3787 // Convert the favorite icon to a byte array. `0` is for lossless compression (the only option for a PNG).
3788 ByteArrayOutputStream newFavoriteIconByteArrayOutputStream = new ByteArrayOutputStream();
3789 favoriteIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, newFavoriteIconByteArrayOutputStream);
3790 byte[] newFavoriteIconByteArray = newFavoriteIconByteArrayOutputStream.toByteArray();
3792 // Update the bookmark and the favorite icon.
3793 bookmarksDatabaseHelper.updateBookmark(selectedBookmarkDatabaseId, bookmarkNameString, bookmarkUrlString, newFavoriteIconByteArray);
3796 // Update the bookmarks cursor with the current contents of this folder.
3797 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder);
3799 // Update the `ListView`.
3800 bookmarksCursorAdapter.changeCursor(bookmarksCursor);
3804 public void onSaveBookmarkFolder(AppCompatDialogFragment dialogFragment, int selectedFolderDatabaseId) {
3805 // Get handles for the views from `dialogFragment`.
3806 EditText editFolderNameEditText = dialogFragment.getDialog().findViewById(R.id.edit_folder_name_edittext);
3807 RadioButton currentFolderIconRadioButton = dialogFragment.getDialog().findViewById(R.id.edit_folder_current_icon_radiobutton);
3808 RadioButton defaultFolderIconRadioButton = dialogFragment.getDialog().findViewById(R.id.edit_folder_default_icon_radiobutton);
3809 ImageView folderIconImageView = dialogFragment.getDialog().findViewById(R.id.edit_folder_default_icon_imageview);
3811 // Get the new folder name.
3812 String newFolderNameString = editFolderNameEditText.getText().toString();
3814 // Check if the favorite icon has changed.
3815 if (currentFolderIconRadioButton.isChecked()) { // Only the name has changed.
3816 // Update the name in the database.
3817 bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, oldFolderNameString, newFolderNameString);
3818 } else if (!currentFolderIconRadioButton.isChecked() && newFolderNameString.equals(oldFolderNameString)) { // Only the icon has changed.
3819 // Get the new folder icon `Bitmap`.
3820 Bitmap folderIconBitmap;
3821 if (defaultFolderIconRadioButton.isChecked()) {
3822 // Get the default folder icon and convert it to a `Bitmap`.
3823 Drawable folderIconDrawable = folderIconImageView.getDrawable();
3824 BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable;
3825 folderIconBitmap = folderIconBitmapDrawable.getBitmap();
3826 } else { // Use the `WebView` favorite icon.
3827 folderIconBitmap = favoriteIconBitmap;
3830 // Convert the folder `Bitmap` to a byte array. `0` is for lossless compression (the only option for a PNG).
3831 ByteArrayOutputStream folderIconByteArrayOutputStream = new ByteArrayOutputStream();
3832 folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, folderIconByteArrayOutputStream);
3833 byte[] folderIconByteArray = folderIconByteArrayOutputStream.toByteArray();
3835 // Update the folder icon in the database.
3836 bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, folderIconByteArray);
3837 } else { // The folder icon and the name have changed.
3838 // Get the new folder icon `Bitmap`.
3839 Bitmap folderIconBitmap;
3840 if (defaultFolderIconRadioButton.isChecked()) {
3841 // Get the default folder icon and convert it to a `Bitmap`.
3842 Drawable folderIconDrawable = folderIconImageView.getDrawable();
3843 BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable;
3844 folderIconBitmap = folderIconBitmapDrawable.getBitmap();
3845 } else { // Use the `WebView` favorite icon.
3846 folderIconBitmap = MainWebViewActivity.favoriteIconBitmap;
3849 // Convert the folder `Bitmap` to a byte array. `0` is for lossless compression (the only option for a PNG).
3850 ByteArrayOutputStream folderIconByteArrayOutputStream = new ByteArrayOutputStream();
3851 folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, folderIconByteArrayOutputStream);
3852 byte[] folderIconByteArray = folderIconByteArrayOutputStream.toByteArray();
3854 // Update the folder name and icon in the database.
3855 bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, oldFolderNameString, newFolderNameString, folderIconByteArray);
3858 // Update the bookmarks cursor with the current contents of this folder.
3859 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder);
3861 // Update the `ListView`.
3862 bookmarksCursorAdapter.changeCursor(bookmarksCursor);
3866 public void onHttpAuthenticationCancel() {
3867 // Cancel the `HttpAuthHandler`.
3868 httpAuthHandler.cancel();
3872 public void onHttpAuthenticationProceed(AppCompatDialogFragment dialogFragment) {
3873 // Get handles for the `EditTexts`.
3874 EditText usernameEditText = dialogFragment.getDialog().findViewById(R.id.http_authentication_username);
3875 EditText passwordEditText = dialogFragment.getDialog().findViewById(R.id.http_authentication_password);
3877 // Proceed with the HTTP authentication.
3878 httpAuthHandler.proceed(usernameEditText.getText().toString(), passwordEditText.getText().toString());
3881 public void viewSslCertificate(View view) {
3882 // Show the `ViewSslCertificateDialog` `AlertDialog` and name this instance `@string/view_ssl_certificate`.
3883 DialogFragment viewSslCertificateDialogFragment = new ViewSslCertificateDialog();
3884 viewSslCertificateDialogFragment.show(getFragmentManager(), getString(R.string.view_ssl_certificate));
3888 public void onSslErrorCancel() {
3889 sslErrorHandler.cancel();
3893 public void onSslErrorProceed() {
3894 sslErrorHandler.proceed();
3898 public void onPinnedMismatchBack() {
3899 if (mainWebView.canGoBack()) { // There is a back page in the history.
3900 // Reset the formatted URL string so the page will load correctly if blocking of third-party requests is enabled.
3901 formattedUrlString = "";
3903 // Set `navigatingHistory` so that the domain settings are applied when the new URL is loaded.
3904 navigatingHistory = true;
3907 mainWebView.goBack();
3908 } else { // There are no pages to go back to.
3909 // Load a blank page
3915 public void onPinnedMismatchProceed() {
3916 // Do not check the pinned information for this domain again until the domain changes.
3917 ignorePinnedDomainInformation = true;
3921 public void onUrlHistoryEntrySelected(int moveBackOrForwardSteps) {
3922 // Reset the formatted URL string so the page will load correctly if blocking of third-party requests is enabled.
3923 formattedUrlString = "";
3925 // Set `navigatingHistory` so that the domain settings are applied when the new URL is loaded.
3926 navigatingHistory = true;
3928 // Load the history entry.
3929 mainWebView.goBackOrForward(moveBackOrForwardSteps);
3933 public void onClearHistory() {
3934 // Clear the history.
3935 mainWebView.clearHistory();
3938 // Override `onBackPressed` to handle the navigation drawer and `mainWebView`.
3940 public void onBackPressed() {
3941 if (drawerLayout.isDrawerVisible(GravityCompat.START)) { // The navigation drawer is open.
3942 // Close the navigation drawer.
3943 drawerLayout.closeDrawer(GravityCompat.START);
3944 } else if (drawerLayout.isDrawerVisible(GravityCompat.END)){ // The bookmarks drawer is open.
3945 if (currentBookmarksFolder.isEmpty()) { // The home folder is displayed.
3946 // close the bookmarks drawer.
3947 drawerLayout.closeDrawer(GravityCompat.END);
3948 } else { // A subfolder is displayed.
3949 // Place the former parent folder in `currentFolder`.
3950 currentBookmarksFolder = bookmarksDatabaseHelper.getParentFolderName(currentBookmarksFolder);
3952 // Load the new folder.
3953 loadBookmarksFolder();
3956 } else if (mainWebView.canGoBack()) { // There is at least one item in the `WebView` history.
3957 // Reset the formatted URL string so the page will load correctly if blocking of third-party requests is enabled.
3958 formattedUrlString = "";
3960 // Set `navigatingHistory` so that the domain settings are applied when the new URL is loaded.
3961 navigatingHistory = true;
3964 mainWebView.goBack();
3965 } else { // There isn't anything to do in Privacy Browser.
3966 // Pass `onBackPressed()` to the system.
3967 super.onBackPressed();
3971 // 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.
3973 public void onActivityResult(int requestCode, int resultCode, Intent data) {
3974 // File uploads only work on API >= 21.
3975 if (Build.VERSION.SDK_INT >= 21) {
3976 // Pass the file to the WebView.
3977 fileChooserCallback.onReceiveValue(WebChromeClient.FileChooserParams.parseResult(resultCode, data));
3981 private void loadUrlFromTextBox() {
3982 // Get the text from urlTextBox and convert it to a string. trim() removes white spaces from the beginning and end of the string.
3983 String unformattedUrlString = urlTextBox.getText().toString().trim();
3985 // Check to see if `unformattedUrlString` is a valid URL. Otherwise, convert it into a search.
3986 if (unformattedUrlString.startsWith("content://")) {
3987 // Load the entire content URL.
3988 formattedUrlString = unformattedUrlString;
3989 } else if (Patterns.WEB_URL.matcher(unformattedUrlString).matches() || unformattedUrlString.startsWith("http://") || unformattedUrlString.startsWith("https://")
3990 || unformattedUrlString.startsWith("file://")) {
3991 // Add `https://` at the beginning if there is no protocol. Otherwise the app will segfault.
3992 if (!unformattedUrlString.startsWith("http") && !unformattedUrlString.startsWith("file://") && !unformattedUrlString.startsWith("content://")) {
3993 unformattedUrlString = "https://" + unformattedUrlString;
3996 // Initialize `unformattedUrl`.
3997 URL unformattedUrl = null;
3999 // 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.
4001 unformattedUrl = new URL(unformattedUrlString);
4002 } catch (MalformedURLException e) {
4003 e.printStackTrace();
4006 // The ternary operator (? :) makes sure that a null pointer exception is not thrown, which would happen if `.get` was called on a `null` value.
4007 String scheme = unformattedUrl != null ? unformattedUrl.getProtocol() : null;
4008 String authority = unformattedUrl != null ? unformattedUrl.getAuthority() : null;
4009 String path = unformattedUrl != null ? unformattedUrl.getPath() : null;
4010 String query = unformattedUrl != null ? unformattedUrl.getQuery() : null;
4011 String fragment = unformattedUrl != null ? unformattedUrl.getRef() : null;
4014 Uri.Builder formattedUri = new Uri.Builder();
4015 formattedUri.scheme(scheme).authority(authority).path(path).query(query).fragment(fragment);
4017 // Decode `formattedUri` as a `String` in `UTF-8`.
4019 formattedUrlString = URLDecoder.decode(formattedUri.build().toString(), "UTF-8");
4020 } catch (UnsupportedEncodingException exception) {
4021 // Load a blank string.
4022 formattedUrlString = "";
4024 } else if (unformattedUrlString.isEmpty()){ // Load a blank web site.
4025 // Load a blank string.
4026 formattedUrlString = "";
4027 } else { // Search for the contents of the URL box.
4028 // Create an encoded URL String.
4029 String encodedUrlString;
4031 // Sanitize the search input.
4033 encodedUrlString = URLEncoder.encode(unformattedUrlString, "UTF-8");
4034 } catch (UnsupportedEncodingException exception) {
4035 encodedUrlString = "";
4038 // Add the base search URL.
4039 formattedUrlString = searchURL + encodedUrlString;
4042 // 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.
4043 urlTextBox.clearFocus();
4046 loadUrl(formattedUrlString);
4049 private void loadUrl(String url) {// Apply any custom domain settings.
4050 // Set the URL as the formatted URL string so that checking third-party requests works correctly.
4051 formattedUrlString = url;
4053 // Apply the domain settings.
4054 applyDomainSettings(url, true, false);
4056 // If loading a website, set `urlIsLoading` to prevent changes in the user agent on websites with redirects from reloading the current website.
4057 urlIsLoading = !url.equals("");
4060 mainWebView.loadUrl(url, customHeaders);
4063 public void findPreviousOnPage(View view) {
4064 // Go to the previous highlighted phrase on the page. `false` goes backwards instead of forwards.
4065 mainWebView.findNext(false);
4068 public void findNextOnPage(View view) {
4069 // Go to the next highlighted phrase on the page. `true` goes forwards instead of backwards.
4070 mainWebView.findNext(true);
4073 public void closeFindOnPage(View view) {
4074 // Delete the contents of `find_on_page_edittext`.
4075 findOnPageEditText.setText(null);
4077 // Clear the highlighted phrases.
4078 mainWebView.clearMatches();
4080 // Hide the Find on Page `RelativeLayout`.
4081 findOnPageLinearLayout.setVisibility(View.GONE);
4083 // Show the URL app bar.
4084 supportAppBar.setVisibility(View.VISIBLE);
4086 // Hide the keyboard so we can see the webpage. `0` indicates no additional flags.
4087 inputMethodManager.hideSoftInputFromWindow(mainWebView.getWindowToken(), 0);
4090 private void applyAppSettings() {
4091 // Get a handle for the shared preferences.
4092 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
4094 // Store the values from the shared preferences in variables.
4095 incognitoModeEnabled = sharedPreferences.getBoolean("incognito_mode", false);
4096 boolean doNotTrackEnabled = sharedPreferences.getBoolean("do_not_track", false);
4097 proxyThroughOrbot = sharedPreferences.getBoolean("proxy_through_orbot", false);
4098 fullScreenBrowsingModeEnabled = sharedPreferences.getBoolean("full_screen_browsing_mode", false);
4099 hideSystemBarsOnFullscreen = sharedPreferences.getBoolean("hide_system_bars", false);
4100 translucentNavigationBarOnFullscreen = sharedPreferences.getBoolean("translucent_navigation_bar", true);
4101 downloadWithExternalApp = sharedPreferences.getBoolean("download_with_external_app", false);
4103 // Apply the proxy through Orbot settings.
4104 applyProxyThroughOrbot(false);
4106 // Set Do Not Track status.
4107 if (doNotTrackEnabled) {
4108 customHeaders.put("DNT", "1");
4110 customHeaders.remove("DNT");
4113 // Apply the appropriate full screen mode the `SYSTEM_UI` flags.
4114 if (fullScreenBrowsingModeEnabled && inFullScreenBrowsingMode) { // Privacy Browser is currently in full screen browsing mode.
4115 if (hideSystemBarsOnFullscreen) { // Hide everything.
4116 // Remove the translucent navigation setting if it is currently flagged.
4117 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
4119 // Remove the translucent status bar overlay.
4120 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
4122 // Remove the translucent status bar overlay on the `Drawer Layout`, which is special and needs its own command.
4123 drawerLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
4125 /* SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
4126 * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
4127 * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
4129 rootCoordinatorLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
4130 } else { // Hide everything except the status and navigation bars.
4131 // Remove any `SYSTEM_UI` flags from `rootCoordinatorLayout`.
4132 rootCoordinatorLayout.setSystemUiVisibility(0);
4134 // Add the translucent status flag if it is unset.
4135 getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
4137 if (translucentNavigationBarOnFullscreen) {
4138 // Set the navigation bar to be translucent.
4139 getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
4141 // Set the navigation bar to be black.
4142 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
4145 } else { // Privacy Browser is not in full screen browsing mode.
4146 // 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.
4147 inFullScreenBrowsingMode = false;
4149 // Show the `appBar` if `findOnPageLinearLayout` is not visible.
4150 if (findOnPageLinearLayout.getVisibility() == View.GONE) {
4154 // Show the `BannerAd` in the free flavor.
4155 if (BuildConfig.FLAVOR.contentEquals("free")) {
4156 // Initialize the ad. The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations.
4157 AdHelper.initializeAds(findViewById(R.id.adview), getApplicationContext(), getFragmentManager(), getString(R.string.google_app_id), getString(R.string.ad_unit_id));
4160 // Remove any `SYSTEM_UI` flags from `rootCoordinatorLayout`.
4161 rootCoordinatorLayout.setSystemUiVisibility(0);
4163 // Remove the translucent navigation bar flag if it is set.
4164 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
4166 // Add the translucent status flag if it is unset. This also resets `drawerLayout's` `View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN`.
4167 getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
4169 // Constrain `rootCoordinatorLayout` inside the status and navigation bars.
4170 rootCoordinatorLayout.setFitsSystemWindows(true);
4174 // `reloadWebsite` is used if returning from the Domains activity. Otherwise JavaScript might not function correctly if it is newly enabled.
4175 // The deprecated `.getDrawable()` must be used until the minimum API >= 21.
4176 @SuppressWarnings("deprecation")
4177 private boolean applyDomainSettings(String url, boolean resetFavoriteIcon, boolean reloadWebsite) {
4178 // Get the current user agent.
4179 String initialUserAgent = mainWebView.getSettings().getUserAgentString();
4181 // Initialize a variable to track if the user agent changes.
4182 boolean userAgentChanged = false;
4184 // Parse the URL into a URI.
4185 Uri uri = Uri.parse(url);
4187 // Extract the domain from `uri`.
4188 String hostName = uri.getHost();
4190 // Initialize `loadingNewDomainName`.
4191 boolean loadingNewDomainName;
4193 // If either `hostName` or `currentDomainName` are `null`, run the options for loading a new domain name.
4194 // The lint suggestion to simplify the `if` statement is incorrect, because `hostName.equals(currentDomainName)` can produce a `null object reference.`
4195 //noinspection SimplifiableIfStatement
4196 if ((hostName == null) || (currentDomainName == null)) {
4197 loadingNewDomainName = true;
4198 } else { // Determine if `hostName` equals `currentDomainName`.
4199 loadingNewDomainName = !hostName.equals(currentDomainName);
4202 // Strings don't like to be null.
4203 if (hostName == null) {
4207 // 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.
4208 if (loadingNewDomainName) {
4209 // Set the new `hostname` as the `currentDomainName`.
4210 currentDomainName = hostName;
4212 // Reset the ignoring of pinned domain information.
4213 ignorePinnedDomainInformation = false;
4215 // Reset the favorite icon if specified.
4216 if (resetFavoriteIcon) {
4217 favoriteIconBitmap = favoriteIconDefaultBitmap;
4218 favoriteIconImageView.setImageBitmap(Bitmap.createScaledBitmap(favoriteIconBitmap, 64, 64, true));
4221 // Initialize the database handler. The `0` specifies the database version, but that is ignored and set instead using a constant in `DomainsDatabaseHelper`.
4222 DomainsDatabaseHelper domainsDatabaseHelper = new DomainsDatabaseHelper(this, null, null, 0);
4224 // Get a full cursor from `domainsDatabaseHelper`.
4225 Cursor domainNameCursor = domainsDatabaseHelper.getDomainNameCursorOrderedByDomain();
4227 // Initialize `domainSettingsSet`.
4228 Set<String> domainSettingsSet = new HashSet<>();
4230 // Get the domain name column index.
4231 int domainNameColumnIndex = domainNameCursor.getColumnIndex(DomainsDatabaseHelper.DOMAIN_NAME);
4233 // Populate `domainSettingsSet`.
4234 for (int i = 0; i < domainNameCursor.getCount(); i++) {
4235 // Move `domainsCursor` to the current row.
4236 domainNameCursor.moveToPosition(i);
4238 // Store the domain name in `domainSettingsSet`.
4239 domainSettingsSet.add(domainNameCursor.getString(domainNameColumnIndex));
4242 // Close `domainNameCursor.
4243 domainNameCursor.close();
4245 // Initialize variables to track if domain settings will be applied and, if so, under which name.
4246 domainSettingsApplied = false;
4247 String domainNameInDatabase = null;
4249 // Check the hostname.
4250 if (domainSettingsSet.contains(hostName)) {
4251 domainSettingsApplied = true;
4252 domainNameInDatabase = hostName;
4255 // Check all the subdomains of the host name against wildcard domains in the domain cursor.
4256 while (!domainSettingsApplied && hostName.contains(".")) { // Stop checking if domain settings are already applied or there are no more `.` in the host name.
4257 if (domainSettingsSet.contains("*." + hostName)) { // Check the host name prepended by `*.`.
4258 // Apply the domain settings.
4259 domainSettingsApplied = true;
4261 // Store the applied domain names as it appears in the database.
4262 domainNameInDatabase = "*." + hostName;
4265 // Strip out the lowest subdomain of of the host name.
4266 hostName = hostName.substring(hostName.indexOf(".") + 1);
4270 // Get a handle for the shared preference.
4271 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
4273 // Store the general preference information.
4274 String defaultFontSizeString = sharedPreferences.getString("font_size", getString(R.string.font_size_default_value));
4275 String defaultUserAgentName = sharedPreferences.getString("user_agent", getString(R.string.user_agent_default_value));
4276 defaultCustomUserAgentString = sharedPreferences.getString("custom_user_agent", getString(R.string.custom_user_agent_default_value));
4277 boolean defaultSwipeToRefresh = sharedPreferences.getBoolean("swipe_to_refresh", true);
4278 nightMode = sharedPreferences.getBoolean("night_mode", false);
4279 boolean displayWebpageImages = sharedPreferences.getBoolean("display_webpage_images", true);
4281 if (domainSettingsApplied) { // The url has custom domain settings.
4282 // Get a cursor for the current host and move it to the first position.
4283 Cursor currentHostDomainSettingsCursor = domainsDatabaseHelper.getCursorForDomainName(domainNameInDatabase);
4284 currentHostDomainSettingsCursor.moveToFirst();
4286 // Get the settings from the cursor.
4287 domainSettingsDatabaseId = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper._ID)));
4288 javaScriptEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_JAVASCRIPT)) == 1);
4289 firstPartyCookiesEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FIRST_PARTY_COOKIES)) == 1);
4290 thirdPartyCookiesEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_THIRD_PARTY_COOKIES)) == 1);
4291 domStorageEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_DOM_STORAGE)) == 1);
4292 // Form data can be removed once the minimum API >= 26.
4293 saveFormDataEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FORM_DATA)) == 1);
4294 easyListEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_EASYLIST)) == 1);
4295 easyPrivacyEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_EASYPRIVACY)) == 1);
4296 fanboysAnnoyanceListEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FANBOYS_ANNOYANCE_LIST)) == 1);
4297 fanboysSocialBlockingListEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FANBOYS_SOCIAL_BLOCKING_LIST)) == 1);
4298 ultraPrivacyEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_ULTRAPRIVACY)) == 1);
4299 blockAllThirdPartyRequests = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.BLOCK_ALL_THIRD_PARTY_REQUESTS)) == 1);
4300 String userAgentName = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.USER_AGENT));
4301 int fontSize = currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.FONT_SIZE));
4302 int swipeToRefreshInt = currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SWIPE_TO_REFRESH));
4303 int nightModeInt = currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.NIGHT_MODE));
4304 int displayWebpageImagesInt = currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.DISPLAY_IMAGES));
4305 pinnedSslCertificate = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.PINNED_SSL_CERTIFICATE)) == 1);
4306 pinnedSslIssuedToCName = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_TO_COMMON_NAME));
4307 pinnedSslIssuedToOName = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_TO_ORGANIZATION));
4308 pinnedSslIssuedToUName = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_TO_ORGANIZATIONAL_UNIT));
4309 pinnedSslIssuedByCName = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_BY_COMMON_NAME));
4310 pinnedSslIssuedByOName = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_BY_ORGANIZATION));
4311 pinnedSslIssuedByUName = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_BY_ORGANIZATIONAL_UNIT));
4312 pinnedIpAddresses = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.PINNED_IP_ADDRESSES)) == 1);
4313 pinnedHostIpAddresses = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.IP_ADDRESSES));
4315 // Set `nightMode` according to `nightModeInt`. If `nightModeInt` is `DomainsDatabaseHelper.NIGHT_MODE_SYSTEM_DEFAULT` the current setting from `sharedPreferences` will be used.
4316 switch (nightModeInt) {
4317 case DomainsDatabaseHelper.NIGHT_MODE_ENABLED:
4321 case DomainsDatabaseHelper.NIGHT_MODE_DISABLED:
4326 // Store the domain JavaScript status. This is used by the options menu night mode toggle.
4327 domainSettingsJavaScriptEnabled = javaScriptEnabled;
4329 // Enable JavaScript if night mode is enabled.
4331 javaScriptEnabled = true;
4334 // Set the pinned SSL certificate start date to `null` if the saved date `long` is 0.
4335 if (currentHostDomainSettingsCursor.getLong(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_START_DATE)) == 0) {
4336 pinnedSslStartDate = null;
4338 pinnedSslStartDate = new Date(currentHostDomainSettingsCursor.getLong(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_START_DATE)));
4341 // Set the pinned SSL certificate end date to `null` if the saved date `long` is 0.
4342 if (currentHostDomainSettingsCursor.getLong(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_END_DATE)) == 0) {
4343 pinnedSslEndDate = null;
4345 pinnedSslEndDate = new Date(currentHostDomainSettingsCursor.getLong(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_END_DATE)));
4348 // Close `currentHostDomainSettingsCursor`.
4349 currentHostDomainSettingsCursor.close();
4351 // Apply the domain settings.
4352 mainWebView.getSettings().setJavaScriptEnabled(javaScriptEnabled);
4353 cookieManager.setAcceptCookie(firstPartyCookiesEnabled);
4354 mainWebView.getSettings().setDomStorageEnabled(domStorageEnabled);
4356 // Apply the form data setting if the API < 26.
4357 if (Build.VERSION.SDK_INT < 26) {
4358 mainWebView.getSettings().setSaveFormData(saveFormDataEnabled);
4361 // Apply the font size.
4362 if (fontSize == 0) { // Apply the default font size.
4363 mainWebView.getSettings().setTextZoom(Integer.valueOf(defaultFontSizeString));
4364 } else { // Apply the specified font size.
4365 mainWebView.getSettings().setTextZoom(fontSize);
4368 // Set third-party cookies status if API >= 21.
4369 if (Build.VERSION.SDK_INT >= 21) {
4370 cookieManager.setAcceptThirdPartyCookies(mainWebView, thirdPartyCookiesEnabled);
4373 // 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.
4374 // <https://redmine.stoutner.com/issues/160>
4375 if (!urlIsLoading) {
4376 // Set the user agent.
4377 if (userAgentName.equals(getString(R.string.system_default_user_agent))) { // Use the system default user agent.
4378 // Get the array position of the default user agent name.
4379 int defaultUserAgentArrayPosition = userAgentNamesArray.getPosition(defaultUserAgentName);
4381 // Set the user agent according to the system default.
4382 switch (defaultUserAgentArrayPosition) {
4383 case UNRECOGNIZED_USER_AGENT: // The default user agent name is not on the canonical list.
4384 // This is probably because it was set in an older version of Privacy Browser before the switch to persistent user agent names.
4385 mainWebView.getSettings().setUserAgentString(defaultUserAgentName);
4388 case SETTINGS_WEBVIEW_DEFAULT_USER_AGENT:
4389 // Set the user agent to `""`, which uses the default value.
4390 mainWebView.getSettings().setUserAgentString("");
4393 case SETTINGS_CUSTOM_USER_AGENT:
4394 // Set the custom user agent.
4395 mainWebView.getSettings().setUserAgentString(defaultCustomUserAgentString);
4399 // Get the user agent string from the user agent data array
4400 mainWebView.getSettings().setUserAgentString(userAgentDataArray[defaultUserAgentArrayPosition]);
4402 } else { // Set the user agent according to the stored name.
4403 // Get the array position of the user agent name.
4404 int userAgentArrayPosition = userAgentNamesArray.getPosition(userAgentName);
4406 switch (userAgentArrayPosition) {
4407 case UNRECOGNIZED_USER_AGENT: // The user agent name contains a custom user agent.
4408 mainWebView.getSettings().setUserAgentString(userAgentName);
4411 case SETTINGS_WEBVIEW_DEFAULT_USER_AGENT:
4412 // Set the user agent to `""`, which uses the default value.
4413 mainWebView.getSettings().setUserAgentString("");
4417 // Get the user agent string from the user agent data array.
4418 mainWebView.getSettings().setUserAgentString(userAgentDataArray[userAgentArrayPosition]);
4422 // Store the applied user agent string, which is used in the View Source activity.
4423 appliedUserAgentString = mainWebView.getSettings().getUserAgentString();
4425 // Update the user agent change tracker.
4426 userAgentChanged = !appliedUserAgentString.equals(initialUserAgent);
4429 // Set swipe to refresh.
4430 switch (swipeToRefreshInt) {
4431 case DomainsDatabaseHelper.SWIPE_TO_REFRESH_SYSTEM_DEFAULT:
4432 // Set swipe to refresh according to the default.
4433 swipeRefreshLayout.setEnabled(defaultSwipeToRefresh);
4436 case DomainsDatabaseHelper.SWIPE_TO_REFRESH_ENABLED:
4437 // Enable swipe to refresh.
4438 swipeRefreshLayout.setEnabled(true);
4441 case DomainsDatabaseHelper.SWIPE_TO_REFRESH_DISABLED:
4442 // Disable swipe to refresh.
4443 swipeRefreshLayout.setEnabled(false);
4446 // Set the loading of webpage images.
4447 switch (displayWebpageImagesInt) {
4448 case DomainsDatabaseHelper.DISPLAY_WEBPAGE_IMAGES_SYSTEM_DEFAULT:
4449 mainWebView.getSettings().setLoadsImagesAutomatically(displayWebpageImages);
4452 case DomainsDatabaseHelper.DISPLAY_WEBPAGE_IMAGES_ENABLED:
4453 mainWebView.getSettings().setLoadsImagesAutomatically(true);
4456 case DomainsDatabaseHelper.DISPLAY_WEBPAGE_IMAGES_DISABLED:
4457 mainWebView.getSettings().setLoadsImagesAutomatically(false);
4461 // 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.
4463 urlAppBarRelativeLayout.setBackground(getResources().getDrawable(R.drawable.url_bar_background_dark_blue));
4465 urlAppBarRelativeLayout.setBackground(getResources().getDrawable(R.drawable.url_bar_background_light_green));
4467 } else { // The new URL does not have custom domain settings. Load the defaults.
4468 // Store the values from `sharedPreferences` in variables.
4469 javaScriptEnabled = sharedPreferences.getBoolean("javascript", false);
4470 firstPartyCookiesEnabled = sharedPreferences.getBoolean("first_party_cookies", false);
4471 thirdPartyCookiesEnabled = sharedPreferences.getBoolean("third_party_cookies", false);
4472 domStorageEnabled = sharedPreferences.getBoolean("dom_storage", false);
4473 saveFormDataEnabled = sharedPreferences.getBoolean("save_form_data", false); // Form data can be removed once the minimum API >= 26.
4474 easyListEnabled = sharedPreferences.getBoolean("easylist", true);
4475 easyPrivacyEnabled = sharedPreferences.getBoolean("easyprivacy", true);
4476 fanboysAnnoyanceListEnabled = sharedPreferences.getBoolean("fanboys_annoyance_list", true);
4477 fanboysSocialBlockingListEnabled = sharedPreferences.getBoolean("fanboys_social_blocking_list", true);
4478 ultraPrivacyEnabled = sharedPreferences.getBoolean("ultraprivacy", true);
4479 blockAllThirdPartyRequests = sharedPreferences.getBoolean("block_all_third_party_requests", false);
4481 // Set `javaScriptEnabled` to be `true` if `night_mode` is `true`.
4483 javaScriptEnabled = true;
4486 // Apply the default settings.
4487 mainWebView.getSettings().setJavaScriptEnabled(javaScriptEnabled);
4488 cookieManager.setAcceptCookie(firstPartyCookiesEnabled);
4489 mainWebView.getSettings().setDomStorageEnabled(domStorageEnabled);
4490 mainWebView.getSettings().setTextZoom(Integer.valueOf(defaultFontSizeString));
4491 swipeRefreshLayout.setEnabled(defaultSwipeToRefresh);
4493 // Apply the form data setting if the API < 26.
4494 if (Build.VERSION.SDK_INT < 26) {
4495 mainWebView.getSettings().setSaveFormData(saveFormDataEnabled);
4498 // Reset the pinned variables.
4499 domainSettingsDatabaseId = -1;
4500 pinnedSslCertificate = false;
4501 pinnedSslIssuedToCName = "";
4502 pinnedSslIssuedToOName = "";
4503 pinnedSslIssuedToUName = "";
4504 pinnedSslIssuedByCName = "";
4505 pinnedSslIssuedByOName = "";
4506 pinnedSslIssuedByUName = "";
4507 pinnedSslStartDate = null;
4508 pinnedSslEndDate = null;
4509 pinnedIpAddresses = false;
4510 pinnedHostIpAddresses = "";
4512 // Set third-party cookies status if API >= 21.
4513 if (Build.VERSION.SDK_INT >= 21) {
4514 cookieManager.setAcceptThirdPartyCookies(mainWebView, thirdPartyCookiesEnabled);
4517 // 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.
4518 // <https://redmine.stoutner.com/issues/160>
4519 if (!urlIsLoading) {
4520 // Get the array position of the user agent name.
4521 int userAgentArrayPosition = userAgentNamesArray.getPosition(defaultUserAgentName);
4523 // Set the user agent.
4524 switch (userAgentArrayPosition) {
4525 case UNRECOGNIZED_USER_AGENT: // The default user agent name is not on the canonical list.
4526 // This is probably because it was set in an older version of Privacy Browser before the switch to persistent user agent names.
4527 mainWebView.getSettings().setUserAgentString(defaultUserAgentName);
4530 case SETTINGS_WEBVIEW_DEFAULT_USER_AGENT:
4531 // Set the user agent to `""`, which uses the default value.
4532 mainWebView.getSettings().setUserAgentString("");
4535 case SETTINGS_CUSTOM_USER_AGENT:
4536 // Set the custom user agent.
4537 mainWebView.getSettings().setUserAgentString(defaultCustomUserAgentString);
4541 // Get the user agent string from the user agent data array
4542 mainWebView.getSettings().setUserAgentString(userAgentDataArray[userAgentArrayPosition]);
4545 // Store the applied user agent string, which is used in the View Source activity.
4546 appliedUserAgentString = mainWebView.getSettings().getUserAgentString();
4548 // Update the user agent change tracker.
4549 userAgentChanged = !appliedUserAgentString.equals(initialUserAgent);
4552 // Set the loading of webpage images.
4553 mainWebView.getSettings().setLoadsImagesAutomatically(displayWebpageImages);
4555 // Set a transparent background on `urlTextBox`. The deprecated `.getDrawable()` must be used until the minimum API >= 21.
4556 urlAppBarRelativeLayout.setBackgroundDrawable(getResources().getDrawable(R.color.transparent));
4559 // Close the domains database helper.
4560 domainsDatabaseHelper.close();
4562 // Update the privacy icons, but only if `mainMenu` has already been populated.
4563 if (mainMenu != null) {
4564 updatePrivacyIcons(true);
4568 // Reload the website if returning from the Domains activity.
4569 if (reloadWebsite) {
4570 mainWebView.reload();
4573 // Return the user agent changed status.
4574 return userAgentChanged;
4577 private void applyProxyThroughOrbot(boolean reloadWebsite) {
4578 // Get a handle for the shared preferences.
4579 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
4581 // Get the search preferences.
4582 String homepageString = sharedPreferences.getString("homepage", getString(R.string.homepage_default_value));
4583 String torHomepageString = sharedPreferences.getString("tor_homepage", getString(R.string.tor_homepage_default_value));
4584 String torSearchString = sharedPreferences.getString("tor_search", getString(R.string.tor_search_default_value));
4585 String torSearchCustomUrlString = sharedPreferences.getString("tor_search_custom_url", getString(R.string.tor_search_custom_url_default_value));
4586 String searchString = sharedPreferences.getString("search", getString(R.string.search_default_value));
4587 String searchCustomUrlString = sharedPreferences.getString("search_custom_url", getString(R.string.search_custom_url_default_value));
4589 // Set the homepage, search, and proxy options.
4590 if (proxyThroughOrbot) { // Set the Tor options.
4591 // Set `torHomepageString` as `homepage`.
4592 homepage = torHomepageString;
4594 // If formattedUrlString is null assign the homepage to it.
4595 if (formattedUrlString == null) {
4596 formattedUrlString = homepage;
4599 // Set the search URL.
4600 if (torSearchString.equals("Custom URL")) { // Get the custom URL string.
4601 searchURL = torSearchCustomUrlString;
4602 } else { // Use the string from the pre-built list.
4603 searchURL = torSearchString;
4606 // Set the proxy. `this` refers to the current activity where an `AlertDialog` might be displayed.
4607 OrbotProxyHelper.setProxy(getApplicationContext(), this, "localhost", "8118");
4609 // Set the `appBar` background to indicate proxying through Orbot is enabled. `this` refers to the context.
4611 appBar.setBackgroundDrawable(ContextCompat.getDrawable(this, R.color.dark_blue_30));
4613 appBar.setBackgroundDrawable(ContextCompat.getDrawable(this, R.color.blue_50));
4616 // Check to see if Orbot is ready.
4617 if (!orbotStatus.equals("ON")) { // Orbot is not ready.
4618 // Set `waitingForOrbot`.
4619 waitingForOrbot = true;
4621 // Disable the wide view port so that the waiting for Orbot text is displayed correctly.
4622 mainWebView.getSettings().setUseWideViewPort(false);
4624 // Load a waiting page. `null` specifies no encoding, which defaults to ASCII.
4625 mainWebView.loadData(waitingForOrbotHtmlString, "text/html", null);
4626 } else if (reloadWebsite) { // Orbot is ready and the website should be reloaded.
4627 // Reload the website.
4628 mainWebView.reload();
4630 } else { // Set the non-Tor options.
4631 // Set `homepageString` as `homepage`.
4632 homepage = homepageString;
4634 // If formattedUrlString is null assign the homepage to it.
4635 if (formattedUrlString == null) {
4636 formattedUrlString = homepage;
4639 // Set the search URL.
4640 if (searchString.equals("Custom URL")) { // Get the custom URL string.
4641 searchURL = searchCustomUrlString;
4642 } else { // Use the string from the pre-built list.
4643 searchURL = searchString;
4646 // Reset the proxy to default. The host is `""` and the port is `"0"`.
4647 OrbotProxyHelper.setProxy(getApplicationContext(), this, "", "0");
4649 // Set the default `appBar` background. `this` refers to the context.
4651 appBar.setBackgroundDrawable(ContextCompat.getDrawable(this, R.color.gray_900));
4653 appBar.setBackgroundDrawable(ContextCompat.getDrawable(this, R.color.gray_100));
4656 // Reset `waitingForOrbot.
4657 waitingForOrbot = false;
4659 // Reload the website if requested.
4660 if (reloadWebsite) {
4661 mainWebView.reload();
4666 private void updatePrivacyIcons(boolean runInvalidateOptionsMenu) {
4667 // Get handles for the menu items.
4668 MenuItem privacyMenuItem = mainMenu.findItem(R.id.toggle_javascript);
4669 MenuItem firstPartyCookiesMenuItem = mainMenu.findItem(R.id.toggle_first_party_cookies);
4670 MenuItem domStorageMenuItem = mainMenu.findItem(R.id.toggle_dom_storage);
4671 MenuItem refreshMenuItem = mainMenu.findItem(R.id.refresh);
4673 // Update the privacy icon.
4674 if (javaScriptEnabled) { // JavaScript is enabled.
4675 privacyMenuItem.setIcon(R.drawable.javascript_enabled);
4676 } else if (firstPartyCookiesEnabled) { // JavaScript is disabled but cookies are enabled.
4677 privacyMenuItem.setIcon(R.drawable.warning);
4678 } else { // All the dangerous features are disabled.
4679 privacyMenuItem.setIcon(R.drawable.privacy_mode);
4682 // Update the first-party cookies icon.
4683 if (firstPartyCookiesEnabled) { // First-party cookies are enabled.
4684 firstPartyCookiesMenuItem.setIcon(R.drawable.cookies_enabled);
4685 } else { // First-party cookies are disabled.
4687 firstPartyCookiesMenuItem.setIcon(R.drawable.cookies_disabled_dark);
4689 firstPartyCookiesMenuItem.setIcon(R.drawable.cookies_disabled_light);
4693 // Update the DOM storage icon.
4694 if (javaScriptEnabled && domStorageEnabled) { // Both JavaScript and DOM storage are enabled.
4695 domStorageMenuItem.setIcon(R.drawable.dom_storage_enabled);
4696 } else if (javaScriptEnabled) { // JavaScript is enabled but DOM storage is disabled.
4698 domStorageMenuItem.setIcon(R.drawable.dom_storage_disabled_dark);
4700 domStorageMenuItem.setIcon(R.drawable.dom_storage_disabled_light);
4702 } else { // JavaScript is disabled, so DOM storage is ghosted.
4704 domStorageMenuItem.setIcon(R.drawable.dom_storage_ghosted_dark);
4706 domStorageMenuItem.setIcon(R.drawable.dom_storage_ghosted_light);
4710 // Update the refresh icon.
4712 refreshMenuItem.setIcon(R.drawable.refresh_enabled_dark);
4714 refreshMenuItem.setIcon(R.drawable.refresh_enabled_light);
4717 // `invalidateOptionsMenu` calls `onPrepareOptionsMenu()` and redraws the icons in the `AppBar`.
4718 if (runInvalidateOptionsMenu) {
4719 invalidateOptionsMenu();
4723 private void openUrlWithExternalApp(String url) {
4724 // Create a download intent. Not specifying the action type will display the maximum number of options.
4725 Intent downloadIntent = new Intent();
4727 // Set the URI and the MIME type. Specifying `text/html` displays a good number of options.
4728 downloadIntent.setDataAndType(Uri.parse(url), "text/html");
4730 // Flag the intent to open in a new task.
4731 downloadIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
4733 // Show the chooser.
4734 startActivity(Intent.createChooser(downloadIntent, getString(R.string.open_with)));
4737 private void highlightUrlText() {
4738 // Only highlight the URL text if the box is not currently selected.
4739 if (!urlTextBox.hasFocus()) {
4740 // Get the URL string.
4741 String urlString = urlTextBox.getText().toString();
4743 // Highlight the URL according to the protocol.
4744 if (urlString.startsWith("file://")) { // This is a file URL.
4745 // De-emphasize only the protocol.
4746 urlTextBox.getText().setSpan(initialGrayColorSpan, 0, 7, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
4747 } else if (urlString.startsWith("content://")) {
4748 // De-emphasize only the protocol.
4749 urlTextBox.getText().setSpan(initialGrayColorSpan, 0, 10, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
4750 } else { // This is a web URL.
4751 // Get the index of the `/` immediately after the domain name.
4752 int endOfDomainName = urlString.indexOf("/", (urlString.indexOf("//") + 2));
4754 // Create a base URL string.
4757 // Get the base URL.
4758 if (endOfDomainName > 0) { // There is at least one character after the base URL.
4759 // Get the base URL.
4760 baseUrl = urlString.substring(0, endOfDomainName);
4761 } else { // There are no characters after the base URL.
4762 // Set the base URL to be the entire URL string.
4763 baseUrl = urlString;
4766 // Get the index of the last `.` in the domain.
4767 int lastDotIndex = baseUrl.lastIndexOf(".");
4769 // Get the index of the penultimate `.` in the domain.
4770 int penultimateDotIndex = baseUrl.lastIndexOf(".", lastDotIndex - 1);
4772 // Markup the beginning of the URL.
4773 if (urlString.startsWith("http://")) { // Highlight the protocol of connections that are not encrypted.
4774 urlTextBox.getText().setSpan(redColorSpan, 0, 7, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
4776 // De-emphasize subdomains.
4777 if (penultimateDotIndex > 0) { // There is more than one subdomain in the domain name.
4778 urlTextBox.getText().setSpan(initialGrayColorSpan, 7, penultimateDotIndex + 1, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
4780 } else if (urlString.startsWith("https://")) { // De-emphasize the protocol of connections that are encrypted.
4781 if (penultimateDotIndex > 0) { // There is more than one subdomain in the domain name.
4782 // De-emphasize the protocol and the additional subdomains.
4783 urlTextBox.getText().setSpan(initialGrayColorSpan, 0, penultimateDotIndex + 1, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
4784 } else { // There is only one subdomain in the domain name.
4785 // De-emphasize only the protocol.
4786 urlTextBox.getText().setSpan(initialGrayColorSpan, 0, 8, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
4790 // De-emphasize the text after the domain name.
4791 if (endOfDomainName > 0) {
4792 urlTextBox.getText().setSpan(finalGrayColorSpan, endOfDomainName, urlString.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
4798 private void loadBookmarksFolder() {
4799 // Update the bookmarks cursor with the contents of the bookmarks database for the current folder.
4800 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder);
4802 // Populate the bookmarks cursor adapter. `this` specifies the `Context`. `false` disables `autoRequery`.
4803 bookmarksCursorAdapter = new CursorAdapter(this, bookmarksCursor, false) {
4805 public View newView(Context context, Cursor cursor, ViewGroup parent) {
4806 // Inflate the individual item layout. `false` does not attach it to the root.
4807 return getLayoutInflater().inflate(R.layout.bookmarks_drawer_item_linearlayout, parent, false);
4811 public void bindView(View view, Context context, Cursor cursor) {
4812 // Get handles for the views.
4813 ImageView bookmarkFavoriteIcon = view.findViewById(R.id.bookmark_favorite_icon);
4814 TextView bookmarkNameTextView = view.findViewById(R.id.bookmark_name);
4816 // Get the favorite icon byte array from the cursor.
4817 byte[] favoriteIconByteArray = cursor.getBlob(cursor.getColumnIndex(BookmarksDatabaseHelper.FAVORITE_ICON));
4819 // Convert the byte array to a `Bitmap` beginning at the first byte and ending at the last.
4820 Bitmap favoriteIconBitmap = BitmapFactory.decodeByteArray(favoriteIconByteArray, 0, favoriteIconByteArray.length);
4822 // Display the bitmap in `bookmarkFavoriteIcon`.
4823 bookmarkFavoriteIcon.setImageBitmap(favoriteIconBitmap);
4825 // Get the bookmark name from the cursor and display it in `bookmarkNameTextView`.
4826 String bookmarkNameString = cursor.getString(cursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME));
4827 bookmarkNameTextView.setText(bookmarkNameString);
4829 // Make the font bold for folders.
4830 if (cursor.getInt(cursor.getColumnIndex(BookmarksDatabaseHelper.IS_FOLDER)) == 1) {
4831 bookmarkNameTextView.setTypeface(Typeface.DEFAULT_BOLD);
4832 } else { // Reset the font to default for normal bookmarks.
4833 bookmarkNameTextView.setTypeface(Typeface.DEFAULT);
4838 // Populate the `ListView` with the adapter.
4839 bookmarksListView.setAdapter(bookmarksCursorAdapter);
4841 // Set the bookmarks drawer title.
4842 if (currentBookmarksFolder.isEmpty()) {
4843 bookmarksTitleTextView.setText(R.string.bookmarks);
4845 bookmarksTitleTextView.setText(currentBookmarksFolder);
4849 private void openWithApp(String url) {
4850 // Create the open with intent with `ACTION_VIEW`.
4851 Intent openWithAppIntent = new Intent(Intent.ACTION_VIEW);
4853 // Set the URI but not the MIME type. This should open all available apps.
4854 openWithAppIntent.setData(Uri.parse(url));
4856 // Flag the intent to open in a new task.
4857 openWithAppIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
4859 // Show the chooser.
4860 startActivity(openWithAppIntent);
4863 private void openWithBrowser(String url) {
4864 // Create the open with intent with `ACTION_VIEW`.
4865 Intent openWithBrowserIntent = new Intent(Intent.ACTION_VIEW);
4867 // Set the URI and the MIME type. `"text/html"` should load browser options.
4868 openWithBrowserIntent.setDataAndType(Uri.parse(url), "text/html");
4870 // Flag the intent to open in a new task.
4871 openWithBrowserIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
4873 // Show the chooser.
4874 startActivity(openWithBrowserIntent);
4877 // This must run asynchronously because it involves a network request. `String` declares the parameters. `Void` does not declare progress units. `String` contains the results.
4878 private static class GetHostIpAddresses extends AsyncTask<String, Void, String> {
4879 // The weak references are used to determine if the activity have disappeared while the AsyncTask is running.
4880 private final WeakReference<Activity> activityWeakReference;
4882 GetHostIpAddresses(Activity activity) {
4883 // Populate the weak references.
4884 activityWeakReference = new WeakReference<>(activity);
4888 protected String doInBackground(String... domainName) {
4889 // Get handles for the activity and the alert dialog.
4890 Activity activity = activityWeakReference.get();
4892 // Abort if the activity or the dialog is gone.
4893 if ((activity == null) || activity.isFinishing()) {
4894 // Return an empty spannable string builder.
4898 // Initialize an IP address string builder.
4899 StringBuilder ipAddresses = new StringBuilder();
4901 // Get an array with the IP addresses for the host.
4903 // Get an array with all the IP addresses for the domain.
4904 InetAddress[] inetAddressesArray = InetAddress.getAllByName(domainName[0]);
4906 // Add each IP address to the string builder.
4907 for (InetAddress inetAddress : inetAddressesArray) {
4908 if (ipAddresses.length() == 0) { // This is the first IP address.
4909 // Add the IP address to the string builder.
4910 ipAddresses.append(inetAddress.getHostAddress());
4911 } else { // This is not the first IP address.
4912 // Add a line break to the string builder first.
4913 ipAddresses.append("\n");
4915 // Add the IP address to the string builder.
4916 ipAddresses.append(inetAddress.getHostAddress());
4919 } catch (UnknownHostException exception) {
4923 // Return the string.
4924 return ipAddresses.toString();
4927 // `onPostExecute()` operates on the UI thread.
4929 protected void onPostExecute(String ipAddresses) {
4930 // Get handles for the activity and the alert dialog.
4931 Activity activity = activityWeakReference.get();
4933 // Abort if the activity or the alert dialog is gone.
4934 if ((activity == null) || activity.isFinishing()) {
4938 // Store the IP addresses.
4939 currentHostIpAddresses = ipAddresses;