2 * Copyright © 2015-2018 Soren Stoutner <soren@stoutner.com>.
4 * Download cookie code contributed 2017 Hendrik Knackstedt. Copyright assigned to Soren Stoutner <soren@stoutner.com>.
6 * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
8 * Privacy Browser is free software: you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation, either version 3 of the License, or
11 * (at your option) any later version.
13 * Privacy Browser is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with Privacy Browser. If not, see <http://www.gnu.org/licenses/>.
22 package com.stoutner.privacybrowser.activities;
24 import android.Manifest;
25 import android.annotation.SuppressLint;
26 import android.app.Activity;
27 import android.app.DialogFragment;
28 import android.app.DownloadManager;
29 import android.app.SearchManager;
30 import android.content.ActivityNotFoundException;
31 import android.content.BroadcastReceiver;
32 import android.content.ClipData;
33 import android.content.ClipboardManager;
34 import android.content.Context;
35 import android.content.Intent;
36 import android.content.IntentFilter;
37 import android.content.SharedPreferences;
38 import android.content.pm.PackageManager;
39 import android.content.res.Configuration;
40 import android.content.res.Resources;
41 import android.database.Cursor;
42 import android.graphics.Bitmap;
43 import android.graphics.BitmapFactory;
44 import android.graphics.Typeface;
45 import android.graphics.drawable.BitmapDrawable;
46 import android.graphics.drawable.Drawable;
47 import android.net.Uri;
48 import android.net.http.SslCertificate;
49 import android.net.http.SslError;
50 import android.os.Build;
51 import android.os.Bundle;
52 import android.os.Environment;
53 import android.os.Handler;
54 import android.preference.PreferenceManager;
55 import android.print.PrintDocumentAdapter;
56 import android.print.PrintManager;
57 import android.support.annotation.NonNull;
58 import android.support.design.widget.CoordinatorLayout;
59 import android.support.design.widget.FloatingActionButton;
60 import android.support.design.widget.NavigationView;
61 import android.support.design.widget.Snackbar;
62 import android.support.v4.app.ActivityCompat;
63 import android.support.v4.content.ContextCompat;
64 // `ShortcutInfoCompat`, `ShortcutManagerCompat`, and `IconCompat` can be switched to the non-compat version once API >= 26.
65 import android.support.v4.content.pm.ShortcutInfoCompat;
66 import android.support.v4.content.pm.ShortcutManagerCompat;
67 import android.support.v4.graphics.drawable.IconCompat;
68 import android.support.v4.view.GravityCompat;
69 import android.support.v4.widget.DrawerLayout;
70 import android.support.v4.widget.SwipeRefreshLayout;
71 import android.support.v7.app.ActionBar;
72 import android.support.v7.app.ActionBarDrawerToggle;
73 import android.support.v7.app.AppCompatActivity;
74 import android.support.v7.app.AppCompatDialogFragment;
75 import android.support.v7.widget.Toolbar;
76 import android.text.Editable;
77 import android.text.Spanned;
78 import android.text.TextWatcher;
79 import android.text.style.ForegroundColorSpan;
80 import android.util.Patterns;
81 import android.view.ContextMenu;
82 import android.view.GestureDetector;
83 import android.view.KeyEvent;
84 import android.view.Menu;
85 import android.view.MenuItem;
86 import android.view.MotionEvent;
87 import android.view.View;
88 import android.view.ViewGroup;
89 import android.view.WindowManager;
90 import android.view.inputmethod.InputMethodManager;
91 import android.webkit.CookieManager;
92 import android.webkit.HttpAuthHandler;
93 import android.webkit.SslErrorHandler;
94 import android.webkit.ValueCallback;
95 import android.webkit.WebBackForwardList;
96 import android.webkit.WebChromeClient;
97 import android.webkit.WebResourceResponse;
98 import android.webkit.WebStorage;
99 import android.webkit.WebView;
100 import android.webkit.WebViewClient;
101 import android.webkit.WebViewDatabase;
102 import android.widget.ArrayAdapter;
103 import android.widget.CursorAdapter;
104 import android.widget.EditText;
105 import android.widget.FrameLayout;
106 import android.widget.ImageView;
107 import android.widget.LinearLayout;
108 import android.widget.ListView;
109 import android.widget.ProgressBar;
110 import android.widget.RadioButton;
111 import android.widget.RelativeLayout;
112 import android.widget.TextView;
114 import com.stoutner.privacybrowser.BuildConfig;
115 import com.stoutner.privacybrowser.R;
116 import com.stoutner.privacybrowser.dialogs.AdConsentDialog;
117 import com.stoutner.privacybrowser.dialogs.CreateBookmarkDialog;
118 import com.stoutner.privacybrowser.dialogs.CreateBookmarkFolderDialog;
119 import com.stoutner.privacybrowser.dialogs.CreateHomeScreenShortcutDialog;
120 import com.stoutner.privacybrowser.dialogs.DownloadImageDialog;
121 import com.stoutner.privacybrowser.dialogs.DownloadLocationPermissionDialog;
122 import com.stoutner.privacybrowser.dialogs.EditBookmarkDialog;
123 import com.stoutner.privacybrowser.dialogs.EditBookmarkFolderDialog;
124 import com.stoutner.privacybrowser.dialogs.HttpAuthenticationDialog;
125 import com.stoutner.privacybrowser.dialogs.PinnedSslCertificateMismatchDialog;
126 import com.stoutner.privacybrowser.dialogs.UrlHistoryDialog;
127 import com.stoutner.privacybrowser.dialogs.ViewSslCertificateDialog;
128 import com.stoutner.privacybrowser.helpers.AdHelper;
129 import com.stoutner.privacybrowser.helpers.BlockListHelper;
130 import com.stoutner.privacybrowser.helpers.BookmarksDatabaseHelper;
131 import com.stoutner.privacybrowser.helpers.DomainsDatabaseHelper;
132 import com.stoutner.privacybrowser.helpers.OrbotProxyHelper;
133 import com.stoutner.privacybrowser.dialogs.DownloadFileDialog;
134 import com.stoutner.privacybrowser.dialogs.SslCertificateErrorDialog;
136 import java.io.ByteArrayInputStream;
137 import java.io.ByteArrayOutputStream;
139 import java.io.IOException;
140 import java.io.UnsupportedEncodingException;
141 import java.net.MalformedURLException;
143 import java.net.URLDecoder;
144 import java.net.URLEncoder;
145 import java.util.ArrayList;
146 import java.util.Date;
147 import java.util.HashMap;
148 import java.util.HashSet;
149 import java.util.List;
150 import java.util.Map;
151 import java.util.Set;
153 // AppCompatActivity from android.support.v7.app.AppCompatActivity must be used to have access to the SupportActionBar until the minimum API is >= 21.
154 public class MainWebViewActivity extends AppCompatActivity implements CreateBookmarkDialog.CreateBookmarkListener, CreateBookmarkFolderDialog.CreateBookmarkFolderListener,
155 CreateHomeScreenShortcutDialog.CreateHomeScreenShortcutListener, DownloadFileDialog.DownloadFileListener, DownloadImageDialog.DownloadImageListener,
156 DownloadLocationPermissionDialog.DownloadLocationPermissionDialogListener, EditBookmarkDialog.EditBookmarkListener, EditBookmarkFolderDialog.EditBookmarkFolderListener,
157 HttpAuthenticationDialog.HttpAuthenticationListener, NavigationView.OnNavigationItemSelectedListener, PinnedSslCertificateMismatchDialog.PinnedSslCertificateMismatchListener,
158 SslCertificateErrorDialog.SslCertificateErrorListener, UrlHistoryDialog.UrlHistoryListener {
160 // `darkTheme` is public static so it can be accessed from everywhere.
161 public static boolean darkTheme;
163 // `allowScreenshots` is public static so it can be accessed from everywhere. It is also used in `onCreate()`.
164 public static boolean allowScreenshots;
166 // `favoriteIconBitmap` is public static so it can be accessed from `CreateHomeScreenShortcutDialog`, `BookmarksActivity`, `BookmarksDatabaseViewActivity`, `CreateBookmarkDialog`,
167 // `CreateBookmarkFolderDialog`, `EditBookmarkDialog`, `EditBookmarkFolderDialog`, `EditBookmarkDatabaseViewDialog`, and `ViewSslCertificateDialog`. It is also used in `onCreate()`,
168 // `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onCreateHomeScreenShortcutCreate()`, `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and `applyDomainSettings()`.
169 public static Bitmap favoriteIconBitmap;
171 // `formattedUrlString` is public static so it can be accessed from `BookmarksActivity`, `CreateBookmarkDialog`, and `AddDomainDialog`.
172 // It is also used in `onCreate()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onCreateHomeScreenShortcutCreate()`, `loadUrlFromTextBox()`, and `applyProxyThroughOrbot()`.
173 public static String formattedUrlString;
175 // `sslCertificate` is public static so it can be accessed from `DomainsActivity`, `DomainsListFragment`, `DomainSettingsFragment`, `PinnedSslCertificateMismatchDialog`,
176 // and `ViewSslCertificateDialog`. It is also used in `onCreate()`.
177 public static SslCertificate sslCertificate;
179 // `orbotStatus` is public static so it can be accessed from `OrbotProxyHelper`. It is also used in `onCreate()`, `onResume()`, and `applyProxyThroughOrbot()`.
180 public static String orbotStatus;
182 // `webViewTitle` is public static so it can be accessed from `CreateBookmarkDialog` and `CreateHomeScreenShortcutDialog`. It is also used in `onCreate()`.
183 public static String webViewTitle;
185 // `appliedUserAgentString` is public static so it can be accessed from `ViewSourceActivity`. It is also used in `applyDomainSettings()`.
186 public static String appliedUserAgentString;
188 // `reloadOnRestart` is public static so it can be accessed from `SettingsFragment`. It is also used in `onRestart()`
189 public static boolean reloadOnRestart;
191 // `reloadUrlOnRestart` is public static so it can be accessed from `SettingsFragment` and `BookmarksActivity`. It is also used in `onRestart()`.
192 public static boolean loadUrlOnRestart;
194 // `restartFromBookmarksActivity` is public static so it can be accessed from `BookmarksActivity`. It is also used in `onRestart()`.
195 public static boolean restartFromBookmarksActivity;
197 // The block list versions are public static so they can be accessed from `AboutTabFragment`. They are also used in `onCreate()`.
198 public static String easyListVersion;
199 public static String easyPrivacyVersion;
200 public static String fanboysAnnoyanceVersion;
201 public static String fanboysSocialVersion;
202 public static String ultraPrivacyVersion;
204 // The request items are public static so they can be accessed by `BlockListHelper`, `RequestsArrayAdapter`, and `ViewRequestsDialog`. They are also used in `onCreate()` and `onPrepareOptionsMenu()`.
205 public static List<String[]> resourceRequests;
206 public static String[] whiteListResultStringArray;
207 private int blockedRequests;
208 private int easyListBlockedRequests;
209 private int easyPrivacyBlockedRequests;
210 private int fanboysAnnoyanceListBlockedRequests;
211 private int fanboysSocialBlockingListBlockedRequests;
212 private int ultraPrivacyBlockedRequests;
213 private int thirdPartyBlockedRequests;
215 public final static int REQUEST_DISPOSITION = 0;
216 public final static int REQUEST_URL = 1;
217 public final static int REQUEST_BLOCKLIST = 2;
218 public final static int REQUEST_SUBLIST = 3;
219 public final static int REQUEST_BLOCKLIST_ENTRIES = 4;
220 public final static int REQUEST_BLOCKLIST_ORIGINAL_ENTRY = 5;
222 public final static int REQUEST_DEFAULT = 0;
223 public final static int REQUEST_ALLOWED = 1;
224 public final static int REQUEST_THIRD_PARTY = 2;
225 public final static int REQUEST_BLOCKED = 3;
227 public final static int MAIN_WHITELIST = 1;
228 public final static int FINAL_WHITELIST = 2;
229 public final static int DOMAIN_WHITELIST = 3;
230 public final static int DOMAIN_INITIAL_WHITELIST = 4;
231 public final static int DOMAIN_FINAL_WHITELIST = 5;
232 public final static int THIRD_PARTY_WHITELIST = 6;
233 public final static int THIRD_PARTY_DOMAIN_WHITELIST = 7;
234 public final static int THIRD_PARTY_DOMAIN_INITIAL_WHITELIST = 8;
236 public final static int MAIN_BLACKLIST = 9;
237 public final static int INITIAL_BLACKLIST = 10;
238 public final static int FINAL_BLACKLIST = 11;
239 public final static int DOMAIN_BLACKLIST = 12;
240 public final static int DOMAIN_INITIAL_BLACKLIST = 13;
241 public final static int DOMAIN_FINAL_BLACKLIST = 14;
242 public final static int DOMAIN_REGULAR_EXPRESSION_BLACKLIST = 15;
243 public final static int THIRD_PARTY_BLACKLIST = 16;
244 public final static int THIRD_PARTY_INITIAL_BLACKLIST = 17;
245 public final static int THIRD_PARTY_DOMAIN_BLACKLIST = 18;
246 public final static int THIRD_PARTY_DOMAIN_INITIAL_BLACKLIST = 19;
247 public final static int THIRD_PARTY_REGULAR_EXPRESSION_BLACKLIST = 20;
248 public final static int THIRD_PARTY_DOMAIN_REGULAR_EXPRESSION_BLACKLIST = 21;
249 public final static int REGULAR_EXPRESSION_BLACKLIST = 22;
251 // `blockAllThirdPartyRequests` is public static so it can be accessed from `RequestsActivity`.
252 // It is also used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, and `applyAppSettings()`
253 public static boolean blockAllThirdPartyRequests;
255 // `currentBookmarksFolder` is public static so it can be accessed from `BookmarksActivity`. It is also used in `onCreate()`, `onBackPressed()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`,
256 // `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and `loadBookmarksFolder()`.
257 public static String currentBookmarksFolder;
259 // `domainSettingsDatabaseId` is public static so it can be accessed from `PinnedSslCertificateMismatchDialog`. It is also used in `onCreate()`, `onOptionsItemSelected()`, and `applyDomainSettings()`.
260 public static int domainSettingsDatabaseId;
262 // The pinned domain SSL Certificate variables are public static so they can be accessed from `PinnedSslCertificateMismatchDialog`. They are also used in `onCreate()` and `applyDomainSettings()`.
263 public static String pinnedDomainSslIssuedToCNameString;
264 public static String pinnedDomainSslIssuedToONameString;
265 public static String pinnedDomainSslIssuedToUNameString;
266 public static String pinnedDomainSslIssuedByCNameString;
267 public static String pinnedDomainSslIssuedByONameString;
268 public static String pinnedDomainSslIssuedByUNameString;
269 public static Date pinnedDomainSslStartDate;
270 public static Date pinnedDomainSslEndDate;
272 // The user agent constants are public static so they can be accessed from `SettingsFragment`, `DomainsActivity`, and `DomainSettingsFragment`.
273 public final static int UNRECOGNIZED_USER_AGENT = -1;
274 public final static int SETTINGS_WEBVIEW_DEFAULT_USER_AGENT = 1;
275 public final static int SETTINGS_CUSTOM_USER_AGENT = 12;
276 public final static int DOMAINS_SYSTEM_DEFAULT_USER_AGENT = 0;
277 public final static int DOMAINS_WEBVIEW_DEFAULT_USER_AGENT = 2;
278 public final static int DOMAINS_CUSTOM_USER_AGENT = 13;
281 // `appBar` is used in `onCreate()`, `onOptionsItemSelected()`, `closeFindOnPage()`, `applyAppSettings()`, and `applyProxyThroughOrbot()`.
282 private ActionBar appBar;
284 // `navigatingHistory` is used in `onCreate()`, `onNavigationItemSelected()`, `onSslMismatchBack()`, and `applyDomainSettings()`.
285 private boolean navigatingHistory;
287 // `favoriteIconDefaultBitmap` is used in `onCreate()` and `applyDomainSettings`.
288 private Bitmap favoriteIconDefaultBitmap;
290 // `drawerLayout` is used in `onCreate()`, `onNewIntent()`, `onBackPressed()`, and `onRestart()`.
291 private DrawerLayout drawerLayout;
293 // `rootCoordinatorLayout` is used in `onCreate()` and `applyAppSettings()`.
294 private CoordinatorLayout rootCoordinatorLayout;
296 // `mainWebView` is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onRestart()`, `onCreateContextMenu()`, `findPreviousOnPage()`,
297 // `findNextOnPage()`, `closeFindOnPage()`, `loadUrlFromTextBox()`, `onSslMismatchBack()`, and `applyProxyThroughOrbot()`.
298 private WebView mainWebView;
300 // `fullScreenVideoFrameLayout` is used in `onCreate()` and `onConfigurationChanged()`.
301 private FrameLayout fullScreenVideoFrameLayout;
303 // `swipeRefreshLayout` is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, and `onRestart()`.
304 private SwipeRefreshLayout swipeRefreshLayout;
306 // `urlAppBarRelativeLayout` is used in `onCreate()` and `applyDomainSettings()`.
307 private RelativeLayout urlAppBarRelativeLayout;
309 // `favoriteIconImageView` is used in `onCreate()` and `applyDomainSettings()`
310 private ImageView favoriteIconImageView;
312 // `cookieManager` is used in `onCreate()`, `onOptionsItemSelected()`, and `onNavigationItemSelected()`, `loadUrlFromTextBox()`, `onDownloadImage()`, `onDownloadFile()`, and `onRestart()`.
313 private CookieManager cookieManager;
315 // `customHeader` is used in `onCreate()`, `onOptionsItemSelected()`, `onCreateContextMenu()`, and `loadUrl()`.
316 private final Map<String, String> customHeaders = new HashMap<>();
318 // `javaScriptEnabled` is also used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, `applyDomainSettings()`, and `updatePrivacyIcons()`.
319 private boolean javaScriptEnabled;
321 // `firstPartyCookiesEnabled` is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, `onDownloadImage()`, `onDownloadFile()`, and `applyDomainSettings()`.
322 private boolean firstPartyCookiesEnabled;
324 // `thirdPartyCookiesEnabled` used in `onCreate()`, `onPrepareOptionsMenu()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, and `applyDomainSettings()`.
325 private boolean thirdPartyCookiesEnabled;
327 // `domStorageEnabled` is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, and `applyDomainSettings()`.
328 private boolean domStorageEnabled;
330 // `saveFormDataEnabled` is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, and `applyDomainSettings()`. It can be removed once the minimum API >= 26.
331 private boolean saveFormDataEnabled;
333 // `nightMode` is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, and `applyDomainSettings()`.
334 private boolean nightMode;
336 // 'homepage' is used in `onCreate()`, `onNavigationItemSelected()`, and `applyProxyThroughOrbot()`.
337 private String homepage;
339 // `searchURL` is used in `loadURLFromTextBox()` and `applyProxyThroughOrbot()`.
340 private String searchURL;
342 // `mainMenu` is used in `onCreateOptionsMenu()` and `updatePrivacyIcons()`.
343 private Menu mainMenu;
345 // `refreshMenuItem` is used in `onCreate()` and `onCreateOptionsMenu()`.
346 private MenuItem refreshMenuItem;
348 // The blocklist menu items are used in `onCreate()`, `onCreateOptionsMenu()`, and `onPrepareOptionsMenu()`.
349 private MenuItem blocklistsMenuItem;
350 private MenuItem easyListMenuItem;
351 private MenuItem easyPrivacyMenuItem;
352 private MenuItem fanboysAnnoyanceListMenuItem;
353 private MenuItem fanboysSocialBlockingListMenuItem;
354 private MenuItem ultraPrivacyMenuItem;
355 private MenuItem blockAllThirdPartyRequestsMenuItem;
357 // The blocklist variables are used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, and `applyAppSettings()`.
358 private boolean easyListEnabled;
359 private boolean easyPrivacyEnabled;
360 private boolean fanboysAnnoyanceListEnabled;
361 private boolean fanboysSocialBlockingListEnabled;
362 private boolean ultraPrivacyEnabled;
364 // `webViewDefaultUserAgent` is used in `onCreate()` and `onPrepareOptionsMenu()`.
365 private String webViewDefaultUserAgent;
367 // `defaultCustomUserAgentString` is used in `onPrepareOptionsMenu()` and `applyDomainSettings()`.
368 private String defaultCustomUserAgentString;
370 // `privacyBrowserRuntime` is used in `onCreate()`, `onOptionsItemSelected()`, and `applyAppSettings()`.
371 private Runtime privacyBrowserRuntime;
373 // `proxyThroughOrbot` is used in `onRestart()`, `onOptionsItemSelected()`, `applyAppSettings()`, and `applyProxyThroughOrbot()`.
374 private boolean proxyThroughOrbot;
376 // `incognitoModeEnabled` is used in `onCreate()` and `applyAppSettings()`.
377 private boolean incognitoModeEnabled;
379 // `fullScreenBrowsingModeEnabled` is used in `onCreate()` and `applyAppSettings()`.
380 private boolean fullScreenBrowsingModeEnabled;
382 // `inFullScreenBrowsingMode` is used in `onCreate()`, `onConfigurationChanged()`, and `applyAppSettings()`.
383 private boolean inFullScreenBrowsingMode;
385 // `hideSystemBarsOnFullscreen` is used in `onCreate()` and `applyAppSettings()`.
386 private boolean hideSystemBarsOnFullscreen;
388 // `translucentNavigationBarOnFullscreen` is used in `onCreate()` and `applyAppSettings()`.
389 private boolean translucentNavigationBarOnFullscreen;
391 // `reapplyDomainSettingsOnRestart` is used in `onCreate()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onRestart()`, and `onAddDomain()`, .
392 private boolean reapplyDomainSettingsOnRestart;
394 // `reapplyAppSettingsOnRestart` is used in `onNavigationItemSelected()` and `onRestart()`.
395 private boolean reapplyAppSettingsOnRestart;
397 // `displayingFullScreenVideo` is used in `onCreate()` and `onResume()`.
398 private boolean displayingFullScreenVideo;
400 // `downloadWithExternalApp` is used in `onCreate()`, `onCreateContextMenu()`, and `applyDomainSettings()`.
401 private boolean downloadWithExternalApp;
403 // `currentDomainName` is used in `onCreate()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onAddDomain()`, and `applyDomainSettings()`.
404 private String currentDomainName;
406 // `ignorePinnedSslCertificateForDomain` is used in `onCreate()`, `onSslMismatchProceed()`, and `applyDomainSettings()`.
407 private boolean ignorePinnedSslCertificate;
409 // `orbotStatusBroadcastReceiver` is used in `onCreate()` and `onDestroy()`.
410 private BroadcastReceiver orbotStatusBroadcastReceiver;
412 // `waitingForOrbot` is used in `onCreate()`, `onResume()`, and `applyProxyThroughOrbot()`.
413 private boolean waitingForOrbot;
415 // `domainSettingsApplied` is used in `prepareOptionsMenu()` and `applyDomainSettings()`.
416 private boolean domainSettingsApplied;
418 // `domainSettingsJavaScriptEnabled` is used in `onOptionsItemSelected()` and `applyDomainSettings()`.
419 private Boolean domainSettingsJavaScriptEnabled;
421 // `waitingForOrbotHtmlString` is used in `onCreate()` and `applyProxyThroughOrbot()`.
422 private String waitingForOrbotHtmlString;
424 // `privateDataDirectoryString` is used in `onCreate()`, `onOptionsItemSelected()`, and `onNavigationItemSelected()`.
425 private String privateDataDirectoryString;
427 // `findOnPageLinearLayout` is used in `onCreate()`, `onOptionsItemSelected()`, and `closeFindOnPage()`.
428 private LinearLayout findOnPageLinearLayout;
430 // `findOnPageEditText` is used in `onCreate()`, `onOptionsItemSelected()`, and `closeFindOnPage()`.
431 private EditText findOnPageEditText;
433 // `displayAdditionalAppBarIcons` is used in `onCreate()` and `onCreateOptionsMenu()`.
434 private boolean displayAdditionalAppBarIcons;
436 // `drawerToggle` is used in `onCreate()`, `onPostCreate()`, `onConfigurationChanged()`, `onNewIntent()`, and `onNavigationItemSelected()`.
437 private ActionBarDrawerToggle drawerToggle;
439 // `supportAppBar` is used in `onCreate()`, `onOptionsItemSelected()`, and `closeFindOnPage()`.
440 private Toolbar supportAppBar;
442 // `urlTextBox` is used in `onCreate()`, `onOptionsItemSelected()`, `loadUrlFromTextBox()`, `loadUrl()`, and `highlightUrlText()`.
443 private EditText urlTextBox;
445 // The color spans are used in `onCreate()` and `highlightUrlText()`.
446 private ForegroundColorSpan redColorSpan;
447 private ForegroundColorSpan initialGrayColorSpan;
448 private ForegroundColorSpan finalGrayColorSpan;
450 // `sslErrorHandler` is used in `onCreate()`, `onSslErrorCancel()`, and `onSslErrorProceed`.
451 private SslErrorHandler sslErrorHandler;
453 // `httpAuthHandler` is used in `onCreate()`, `onHttpAuthenticationCancel()`, and `onHttpAuthenticationProceed()`.
454 private static HttpAuthHandler httpAuthHandler;
456 // `inputMethodManager` is used in `onOptionsItemSelected()`, `loadUrlFromTextBox()`, and `closeFindOnPage()`.
457 private InputMethodManager inputMethodManager;
459 // `mainWebViewRelativeLayout` is used in `onCreate()` and `onNavigationItemSelected()`.
460 private RelativeLayout mainWebViewRelativeLayout;
462 // `urlIsLoading` is used in `onCreate()`, `onCreateOptionsMenu()`, `loadUrl()`, and `applyDomainSettings()`.
463 private boolean urlIsLoading;
465 // `pinnedDomainSslCertificate` is used in `onCreate()` and `applyDomainSettings()`.
466 private boolean pinnedDomainSslCertificate;
468 // `bookmarksDatabaseHelper` is used in `onCreate()`, `onDestroy`, `onOptionsItemSelected()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`,
469 // and `loadBookmarksFolder()`.
470 private BookmarksDatabaseHelper bookmarksDatabaseHelper;
472 // `bookmarksListView` is used in `onCreate()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, and `loadBookmarksFolder()`.
473 private ListView bookmarksListView;
475 // `bookmarksTitleTextView` is used in `onCreate()` and `loadBookmarksFolder()`.
476 private TextView bookmarksTitleTextView;
478 // `bookmarksCursor` is used in `onDestroy()`, `onOptionsItemSelected()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and `loadBookmarksFolder()`.
479 private Cursor bookmarksCursor;
481 // `bookmarksCursorAdapter` is used in `onCreateBookmark()`, `onCreateBookmarkFolder()` `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and `loadBookmarksFolder()`.
482 private CursorAdapter bookmarksCursorAdapter;
484 // `oldFolderNameString` is used in `onCreate()` and `onSaveEditBookmarkFolder()`.
485 private String oldFolderNameString;
487 // `fileChooserCallback` is used in `onCreate()` and `onActivityResult()`.
488 private ValueCallback<Uri[]> fileChooserCallback;
490 // The download strings are used in `onCreate()` and `onRequestPermissionResult()`.
491 private String downloadUrl;
492 private String downloadContentDisposition;
493 private long downloadContentLength;
495 // `downloadImageUrl` is used in `onCreateContextMenu()` and `onRequestPermissionResult()`.
496 private String downloadImageUrl;
498 // The user agent variables are used in `onCreate()` and `applyDomainSettings()`.
499 private ArrayAdapter<CharSequence> userAgentNamesArray;
500 private String[] userAgentDataArray;
502 // The request codes are used in `onCreate()`, `onCreateContextMenu()`, `onCloseDownloadLocationPermissionDialog()`, and `onRequestPermissionResult()`.
503 private final int DOWNLOAD_FILE_REQUEST_CODE = 1;
504 private final int DOWNLOAD_IMAGE_REQUEST_CODE = 2;
507 // 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.
508 // Also, remove the warning about needing to override `performClick()` when using an `OnTouchListener` with `WebView`.
509 @SuppressLint({"SetJavaScriptEnabled", "ClickableViewAccessibility"})
510 // Remove Android Studio's warning about deprecations. The deprecated `getColor()` must be used until API >= 23.
511 @SuppressWarnings("deprecation")
512 protected void onCreate(Bundle savedInstanceState) {
513 // Get a handle for the shared preferences.
514 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
516 // Get the theme and screenshot preferences.
517 darkTheme = sharedPreferences.getBoolean("dark_theme", false);
518 allowScreenshots = sharedPreferences.getBoolean("allow_screenshots", false);
520 // Disable screenshots if not allowed.
521 if (!allowScreenshots) {
522 getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE);
525 // Set the activity theme.
527 setTheme(R.style.PrivacyBrowserDark);
529 setTheme(R.style.PrivacyBrowserLight);
532 // Run the default commands.
533 super.onCreate(savedInstanceState);
535 // Set the content view.
536 setContentView(R.layout.main_drawerlayout);
538 // Get a handle for the resources.
539 Resources resources = getResources();
541 // Get a handle for `inputMethodManager`.
542 inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
544 // `SupportActionBar` from `android.support.v7.app.ActionBar` must be used until the minimum API is >= 21.
545 supportAppBar = findViewById(R.id.app_bar);
546 setSupportActionBar(supportAppBar);
547 appBar = getSupportActionBar();
549 // This is needed to get rid of the Android Studio warning that `appBar` might be null.
550 assert appBar != null;
552 // Add the custom `url_app_bar` layout, which shows the favorite icon and the URL text bar.
553 appBar.setCustomView(R.layout.url_app_bar);
554 appBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM);
556 // Initialize the foreground color spans for highlighting the URLs. We have to use the deprecated `getColor()` until API >= 23.
557 redColorSpan = new ForegroundColorSpan(resources.getColor(R.color.red_a700));
558 initialGrayColorSpan = new ForegroundColorSpan(resources.getColor(R.color.gray_500));
559 finalGrayColorSpan = new ForegroundColorSpan(resources.getColor(R.color.gray_500));
561 // Get a handle for `urlTextBox`.
562 urlTextBox = findViewById(R.id.url_edittext);
564 // Remove the formatting from `urlTextBar` when the user is editing the text.
565 urlTextBox.setOnFocusChangeListener((View v, boolean hasFocus) -> {
566 if (hasFocus) { // The user is editing the URL text box.
567 // Remove the highlighting.
568 urlTextBox.getText().removeSpan(redColorSpan);
569 urlTextBox.getText().removeSpan(initialGrayColorSpan);
570 urlTextBox.getText().removeSpan(finalGrayColorSpan);
571 } else { // The user has stopped editing the URL text box.
572 // Move to the beginning of the string.
573 urlTextBox.setSelection(0);
575 // Reapply the highlighting.
580 // Set the go button on the keyboard to load the URL in `urlTextBox`.
581 urlTextBox.setOnKeyListener((View v, int keyCode, KeyEvent event) -> {
582 // If the event is a key-down event on the `enter` button, load the URL.
583 if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) {
584 // Load the URL into the mainWebView and consume the event.
585 loadUrlFromTextBox();
587 // If the enter key was pressed, consume the event.
590 // If any other key was pressed, do not consume the event.
595 // Set `waitingForOrbotHTMLString`.
596 waitingForOrbotHtmlString = "<html><body><br/><center><h1>" + getString(R.string.waiting_for_orbot) + "</h1></center></body></html>";
598 // Initialize `currentDomainName`, `orbotStatus`, and `waitingForOrbot`.
599 currentDomainName = "";
600 orbotStatus = "unknown";
601 waitingForOrbot = false;
603 // Create an Orbot status `BroadcastReceiver`.
604 orbotStatusBroadcastReceiver = new BroadcastReceiver() {
606 public void onReceive(Context context, Intent intent) {
607 // Store the content of the status message in `orbotStatus`.
608 orbotStatus = intent.getStringExtra("org.torproject.android.intent.extra.STATUS");
610 // If Privacy Browser is waiting on Orbot, load the website now that Orbot is connected.
611 if (orbotStatus.equals("ON") && waitingForOrbot) {
612 // Reset `waitingForOrbot`.
613 waitingForOrbot = false;
615 // Load `formattedUrlString
616 loadUrl(formattedUrlString);
621 // Register `orbotStatusBroadcastReceiver` on `this` context.
622 this.registerReceiver(orbotStatusBroadcastReceiver, new IntentFilter("org.torproject.android.intent.action.STATUS"));
624 // Get handles for views that need to be accessed.
625 drawerLayout = findViewById(R.id.drawerlayout);
626 rootCoordinatorLayout = findViewById(R.id.root_coordinatorlayout);
627 bookmarksListView = findViewById(R.id.bookmarks_drawer_listview);
628 bookmarksTitleTextView = findViewById(R.id.bookmarks_title_textview);
629 FloatingActionButton launchBookmarksActivityFab = findViewById(R.id.launch_bookmarks_activity_fab);
630 FloatingActionButton createBookmarkFolderFab = findViewById(R.id.create_bookmark_folder_fab);
631 FloatingActionButton createBookmarkFab = findViewById(R.id.create_bookmark_fab);
632 mainWebViewRelativeLayout = findViewById(R.id.main_webview_relativelayout);
633 mainWebView = findViewById(R.id.main_webview);
634 findOnPageLinearLayout = findViewById(R.id.find_on_page_linearlayout);
635 findOnPageEditText = findViewById(R.id.find_on_page_edittext);
636 fullScreenVideoFrameLayout = findViewById(R.id.full_screen_video_framelayout);
637 urlAppBarRelativeLayout = findViewById(R.id.url_app_bar_relativelayout);
638 favoriteIconImageView = findViewById(R.id.favorite_icon);
640 // 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.
642 launchBookmarksActivityFab.setImageDrawable(resources.getDrawable(R.drawable.bookmarks_dark));
643 createBookmarkFolderFab.setImageDrawable(resources.getDrawable(R.drawable.create_folder_dark));
644 createBookmarkFab.setImageDrawable(resources.getDrawable(R.drawable.create_bookmark_dark));
645 bookmarksListView.setBackgroundColor(resources.getColor(R.color.gray_850));
647 launchBookmarksActivityFab.setImageDrawable(resources.getDrawable(R.drawable.bookmarks_light));
648 createBookmarkFolderFab.setImageDrawable(resources.getDrawable(R.drawable.create_folder_light));
649 createBookmarkFab.setImageDrawable(resources.getDrawable(R.drawable.create_bookmark_light));
650 bookmarksListView.setBackgroundColor(resources.getColor(R.color.white));
653 // Set the launch bookmarks activity FAB to launch the bookmarks activity.
654 launchBookmarksActivityFab.setOnClickListener(v -> {
655 // Create an intent to launch the bookmarks activity.
656 Intent bookmarksIntent = new Intent(getApplicationContext(), BookmarksActivity.class);
658 // Include the current folder with the `Intent`.
659 bookmarksIntent.putExtra("Current Folder", currentBookmarksFolder);
662 startActivity(bookmarksIntent);
665 // Set the create new bookmark folder FAB to display an alert dialog.
666 createBookmarkFolderFab.setOnClickListener(v -> {
667 // Show the `CreateBookmarkFolderDialog` `AlertDialog` and name the instance `@string/create_folder`.
668 AppCompatDialogFragment createBookmarkFolderDialog = new CreateBookmarkFolderDialog();
669 createBookmarkFolderDialog.show(getSupportFragmentManager(), resources.getString(R.string.create_folder));
672 // Set the create new bookmark FAB to display an alert dialog.
673 createBookmarkFab.setOnClickListener(view -> {
674 // Show the `CreateBookmarkDialog` `AlertDialog` and name the instance `@string/create_bookmark`.
675 AppCompatDialogFragment createBookmarkDialog = new CreateBookmarkDialog();
676 createBookmarkDialog.show(getSupportFragmentManager(), resources.getString(R.string.create_bookmark));
679 // Create a double-tap listener to toggle full-screen mode.
680 final GestureDetector gestureDetector = new GestureDetector(this, new GestureDetector.SimpleOnGestureListener() {
681 // Override `onDoubleTap()`. All other events are handled using the default settings.
683 public boolean onDoubleTap(MotionEvent event) {
684 if (fullScreenBrowsingModeEnabled) { // Only process the double-tap if full screen browsing mode is enabled.
685 // Toggle `inFullScreenBrowsingMode`.
686 inFullScreenBrowsingMode = !inFullScreenBrowsingMode;
688 if (inFullScreenBrowsingMode) { // Switch to full screen mode.
689 // Hide the `appBar`.
692 // Hide the banner ad in the free flavor.
693 if (BuildConfig.FLAVOR.contentEquals("free")) {
694 // The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations.
695 AdHelper.hideAd(findViewById(R.id.adview));
698 // Modify the system bars.
699 if (hideSystemBarsOnFullscreen) { // Hide everything.
700 // Remove the translucent overlays.
701 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
703 // Remove the translucent status bar overlay on the `Drawer Layout`, which is special and needs its own command.
704 drawerLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
706 /* SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
707 * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
708 * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
710 rootCoordinatorLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
712 // Set `rootCoordinatorLayout` to fill the whole screen.
713 rootCoordinatorLayout.setFitsSystemWindows(false);
714 } else { // Hide everything except the status and navigation bars.
715 // Set `rootCoordinatorLayout` to fit under the status and navigation bars.
716 rootCoordinatorLayout.setFitsSystemWindows(false);
718 // 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.
719 if (translucentNavigationBarOnFullscreen) {
720 // Set the navigation bar to be translucent.
721 getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
724 } else { // Switch to normal viewing mode.
725 // Show the `appBar`.
728 // Show the `BannerAd` in the free flavor.
729 if (BuildConfig.FLAVOR.contentEquals("free")) {
730 // Reload the ad. The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations.
731 AdHelper.loadAd(findViewById(R.id.adview), getApplicationContext(), getString(R.string.ad_unit_id));
734 // Remove the translucent navigation bar flag if it is set.
735 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
737 // Add the translucent status flag if it is unset. This also resets `drawerLayout's` `View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN`.
738 getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
740 // Remove any `SYSTEM_UI` flags from `rootCoordinatorLayout`.
741 rootCoordinatorLayout.setSystemUiVisibility(0);
743 // Constrain `rootCoordinatorLayout` inside the status and navigation bars.
744 rootCoordinatorLayout.setFitsSystemWindows(true);
747 // Consume the double-tap.
749 } else { // Do not consume the double-tap because full screen browsing mode is disabled.
755 // Pass all touch events on `mainWebView` through `gestureDetector` to check for double-taps.
756 mainWebView.setOnTouchListener((View v, MotionEvent event) -> {
757 // Call `performClick()` on the view, which is required for accessibility.
760 // Send the `event` to `gestureDetector`.
761 return gestureDetector.onTouchEvent(event);
764 // Update `findOnPageCountTextView`.
765 mainWebView.setFindListener(new WebView.FindListener() {
766 // Get a handle for `findOnPageCountTextView`.
767 final TextView findOnPageCountTextView = (TextView) findViewById(R.id.find_on_page_count_textview);
770 public void onFindResultReceived(int activeMatchOrdinal, int numberOfMatches, boolean isDoneCounting) {
771 if ((isDoneCounting) && (numberOfMatches == 0)) { // There are no matches.
772 // Set `findOnPageCountTextView` to `0/0`.
773 findOnPageCountTextView.setText(R.string.zero_of_zero);
774 } else if (isDoneCounting) { // There are matches.
775 // `activeMatchOrdinal` is zero-based.
776 int activeMatch = activeMatchOrdinal + 1;
778 // Build the match string.
779 String matchString = activeMatch + "/" + numberOfMatches;
781 // Set `findOnPageCountTextView`.
782 findOnPageCountTextView.setText(matchString);
787 // Search for the string on the page whenever a character changes in the `findOnPageEditText`.
788 findOnPageEditText.addTextChangedListener(new TextWatcher() {
790 public void beforeTextChanged(CharSequence s, int start, int count, int after) {
795 public void onTextChanged(CharSequence s, int start, int before, int count) {
800 public void afterTextChanged(Editable s) {
801 // Search for the text in `mainWebView`.
802 mainWebView.findAllAsync(findOnPageEditText.getText().toString());
806 // Set the `check mark` button for the `findOnPageEditText` keyboard to close the soft keyboard.
807 findOnPageEditText.setOnKeyListener((v, keyCode, event) -> {
808 if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) { // The `enter` key was pressed.
809 // Hide the soft keyboard. `0` indicates no additional flags.
810 inputMethodManager.hideSoftInputFromWindow(mainWebView.getWindowToken(), 0);
812 // Consume the event.
814 } else { // A different key was pressed.
815 // Do not consume the event.
820 // Implement swipe to refresh.
821 swipeRefreshLayout = findViewById(R.id.swiperefreshlayout);
822 swipeRefreshLayout.setOnRefreshListener(() -> mainWebView.reload());
824 // Set the swipe to refresh color according to the theme.
826 swipeRefreshLayout.setColorSchemeResources(R.color.blue_600);
827 swipeRefreshLayout.setProgressBackgroundColorSchemeResource(R.color.gray_850);
829 swipeRefreshLayout.setColorSchemeResources(R.color.blue_700);
832 // `DrawerTitle` identifies the `DrawerLayouts` in accessibility mode.
833 drawerLayout.setDrawerTitle(GravityCompat.START, getString(R.string.navigation_drawer));
834 drawerLayout.setDrawerTitle(GravityCompat.END, getString(R.string.bookmarks));
836 // Listen for touches on the navigation menu.
837 final NavigationView navigationView = findViewById(R.id.navigationview);
838 navigationView.setNavigationItemSelectedListener(this);
840 // 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.
841 final Menu navigationMenu = navigationView.getMenu();
842 final MenuItem navigationBackMenuItem = navigationMenu.getItem(1);
843 final MenuItem navigationForwardMenuItem = navigationMenu.getItem(2);
844 final MenuItem navigationHistoryMenuItem = navigationMenu.getItem(3);
845 final MenuItem navigationRequestsMenuItem = navigationMenu.getItem(4);
847 // Initialize the bookmarks database helper. The `0` specifies a database version, but that is ignored and set instead using a constant in `BookmarksDatabaseHelper`.
848 bookmarksDatabaseHelper = new BookmarksDatabaseHelper(this, null, null, 0);
850 // Initialize `currentBookmarksFolder`. `""` is the home folder in the database.
851 currentBookmarksFolder = "";
853 // Load the home folder, which is `""` in the database.
854 loadBookmarksFolder();
856 bookmarksListView.setOnItemClickListener((parent, view, position, id) -> {
857 // Convert the id from long to int to match the format of the bookmarks database.
858 int databaseID = (int) id;
860 // Get the bookmark cursor for this ID and move it to the first row.
861 Cursor bookmarkCursor = bookmarksDatabaseHelper.getBookmarkCursor(databaseID);
862 bookmarkCursor.moveToFirst();
864 // Act upon the bookmark according to the type.
865 if (bookmarkCursor.getInt(bookmarkCursor.getColumnIndex(BookmarksDatabaseHelper.IS_FOLDER)) == 1) { // The selected bookmark is a folder.
866 // Store the new folder name in `currentBookmarksFolder`.
867 currentBookmarksFolder = bookmarkCursor.getString(bookmarkCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME));
869 // Load the new folder.
870 loadBookmarksFolder();
871 } else { // The selected bookmark is not a folder.
872 // Load the bookmark URL.
873 loadUrl(bookmarkCursor.getString(bookmarkCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_URL)));
875 // Close the bookmarks drawer.
876 drawerLayout.closeDrawer(GravityCompat.END);
879 // Close the `Cursor`.
880 bookmarkCursor.close();
883 bookmarksListView.setOnItemLongClickListener((parent, view, position, id) -> {
884 // Convert the database ID from `long` to `int`.
885 int databaseId = (int) id;
887 // Find out if the selected bookmark is a folder.
888 boolean isFolder = bookmarksDatabaseHelper.isFolder(databaseId);
891 // Save the current folder name, which is used in `onSaveEditBookmarkFolder()`.
892 oldFolderNameString = bookmarksCursor.getString(bookmarksCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME));
894 // Show the edit bookmark folder `AlertDialog` and name the instance `@string/edit_folder`.
895 AppCompatDialogFragment editFolderDialog = EditBookmarkFolderDialog.folderDatabaseId(databaseId);
896 editFolderDialog.show(getSupportFragmentManager(), resources.getString(R.string.edit_folder));
898 // Show the edit bookmark `AlertDialog` and name the instance `@string/edit_bookmark`.
899 AppCompatDialogFragment editBookmarkDialog = EditBookmarkDialog.bookmarkDatabaseId(databaseId);
900 editBookmarkDialog.show(getSupportFragmentManager(), resources.getString(R.string.edit_bookmark));
903 // Consume the event.
907 // Get the status bar pixel size.
908 int statusBarResourceId = resources.getIdentifier("status_bar_height", "dimen", "android");
909 int statusBarPixelSize = resources.getDimensionPixelSize(statusBarResourceId);
911 // Get the resource density.
912 float screenDensity = resources.getDisplayMetrics().density;
914 // Calculate the drawer header padding. This is used to move the text in the drawer headers below any cutouts.
915 int drawerHeaderPaddingLeftAndRight = (int) (15 * screenDensity);
916 int drawerHeaderPaddingTop = statusBarPixelSize + (int) (4 * screenDensity);
917 int drawerHeaderPaddingBottom = (int) (8 * screenDensity);
919 // The drawer listener is used to update the navigation menu.
920 drawerLayout.addDrawerListener(new DrawerLayout.DrawerListener() {
922 public void onDrawerSlide(@NonNull View drawerView, float slideOffset) {
926 public void onDrawerOpened(@NonNull View drawerView) {
930 public void onDrawerClosed(@NonNull View drawerView) {
934 public void onDrawerStateChanged(int newState) {
935 if ((newState == DrawerLayout.STATE_SETTLING) || (newState == DrawerLayout.STATE_DRAGGING)) { // A drawer is opening or closing.
936 // Get handles for the drawer headers.
937 TextView navigationHeaderTextView = findViewById(R.id.navigationText);
938 TextView bookmarksHeaderTextView = findViewById(R.id.bookmarks_title_textview);
940 // 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.
941 if (navigationHeaderTextView != null) {
942 navigationHeaderTextView.setPadding(drawerHeaderPaddingLeftAndRight, drawerHeaderPaddingTop, drawerHeaderPaddingLeftAndRight, drawerHeaderPaddingBottom);
945 // 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.
946 if (bookmarksHeaderTextView != null) {
947 bookmarksHeaderTextView.setPadding(drawerHeaderPaddingLeftAndRight, drawerHeaderPaddingTop, drawerHeaderPaddingLeftAndRight, drawerHeaderPaddingBottom);
950 // Update the back, forward, history, and requests menu items.
951 navigationBackMenuItem.setEnabled(mainWebView.canGoBack());
952 navigationForwardMenuItem.setEnabled(mainWebView.canGoForward());
953 navigationHistoryMenuItem.setEnabled((mainWebView.canGoBack() || mainWebView.canGoForward()));
954 navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
956 // Hide the keyboard (if displayed).
957 inputMethodManager.hideSoftInputFromWindow(mainWebView.getWindowToken(), 0);
959 // 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.
960 urlTextBox.clearFocus();
961 mainWebView.clearFocus();
966 // drawerToggle creates the hamburger icon at the start of the AppBar.
967 drawerToggle = new ActionBarDrawerToggle(this, drawerLayout, supportAppBar, R.string.open_navigation_drawer, R.string.close_navigation_drawer);
969 // Get a handle for the progress bar.
970 final ProgressBar progressBar = findViewById(R.id.progress_bar);
972 mainWebView.setWebChromeClient(new WebChromeClient() {
973 // Update the progress bar when a page is loading.
975 public void onProgressChanged(WebView view, int progress) {
976 // Inject the night mode CSS if night mode is enabled.
978 // `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
979 // used by WordPress. `text-decoration: none` removes all text underlines. `text-shadow: none` removes text shadows, which usually have a hard coded color.
980 // `border: none` removes all borders, which can also be used to underline text.
981 // `a {color: #1565C0}` sets links to be a dark blue. `!important` takes precedent over any existing sub-settings.
982 mainWebView.evaluateJavascript("(function() {var parent = document.getElementsByTagName('head').item(0); var style = document.createElement('style'); style.type = 'text/css'; " +
983 "style.innerHTML = '* {background-color: #212121 !important; color: #BDBDBD !important; box-shadow: none !important; text-decoration: none !important;" +
984 "text-shadow: none !important; border: none !important;} a {color: #1565C0 !important;}'; parent.appendChild(style)})()", value -> {
985 // Initialize a handler to display `mainWebView`.
986 Handler displayWebViewHandler = new Handler();
988 // Setup a runnable to display `mainWebView` after a delay to allow the CSS to be applied.
989 Runnable displayWebViewRunnable = () -> {
990 // Only display `mainWebView` if the progress bar is one. This prevents the display of the `WebView` while it is still loading.
991 if (progressBar.getVisibility() == View.GONE) {
992 mainWebView.setVisibility(View.VISIBLE);
996 // Displaying of `mainWebView` after 500 milliseconds.
997 displayWebViewHandler.postDelayed(displayWebViewRunnable, 500);
1001 // Update the progress bar.
1002 progressBar.setProgress(progress);
1004 // Set the visibility of the progress bar.
1005 if (progress < 100) {
1006 // Show the progress bar.
1007 progressBar.setVisibility(View.VISIBLE);
1009 // Hide the progress bar.
1010 progressBar.setVisibility(View.GONE);
1012 // Display `mainWebView` if night mode is disabled.
1013 // 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
1014 // currently enabled.
1016 mainWebView.setVisibility(View.VISIBLE);
1019 //Stop the swipe to refresh indicator if it is running
1020 swipeRefreshLayout.setRefreshing(false);
1024 // Set the favorite icon when it changes.
1026 public void onReceivedIcon(WebView view, Bitmap icon) {
1027 // Only update the favorite icon if the website has finished loading.
1028 if (progressBar.getVisibility() == View.GONE) {
1029 // Save a copy of the favorite icon.
1030 favoriteIconBitmap = icon;
1032 // Place the favorite icon in the appBar.
1033 favoriteIconImageView.setImageBitmap(Bitmap.createScaledBitmap(icon, 64, 64, true));
1037 // Save a copy of the title when it changes.
1039 public void onReceivedTitle(WebView view, String title) {
1040 // Save a copy of the title.
1041 webViewTitle = title;
1044 // Enter full screen video.
1046 public void onShowCustomView(View view, CustomViewCallback callback) {
1047 // Set the full screen video flag.
1048 displayingFullScreenVideo = true;
1050 // Pause the ad if this is the free flavor.
1051 if (BuildConfig.FLAVOR.contentEquals("free")) {
1052 // The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations.
1053 AdHelper.pauseAd(findViewById(R.id.adview));
1056 // Remove the translucent overlays.
1057 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
1059 // Remove the translucent status bar overlay on the `Drawer Layout`, which is special and needs its own command.
1060 drawerLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
1062 /* SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
1063 * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
1064 * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
1066 rootCoordinatorLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
1068 // Set `rootCoordinatorLayout` to fill the entire screen.
1069 rootCoordinatorLayout.setFitsSystemWindows(false);
1071 // Disable the sliding drawers.
1072 drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED);
1074 // Add `view` to `fullScreenVideoFrameLayout` and display it on the screen.
1075 fullScreenVideoFrameLayout.addView(view);
1076 fullScreenVideoFrameLayout.setVisibility(View.VISIBLE);
1079 // Exit full screen video.
1081 public void onHideCustomView() {
1082 // Unset the full screen video flag.
1083 displayingFullScreenVideo = false;
1085 // Hide `fullScreenVideoFrameLayout`.
1086 fullScreenVideoFrameLayout.removeAllViews();
1087 fullScreenVideoFrameLayout.setVisibility(View.GONE);
1089 // Enable the sliding drawers.
1090 drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED);
1092 // Apply the appropriate full screen mode the `SYSTEM_UI` flags.
1093 if (fullScreenBrowsingModeEnabled && inFullScreenBrowsingMode) { // Privacy Browser is currently in full screen browsing mode.
1094 if (hideSystemBarsOnFullscreen) { // Hide everything.
1095 // Remove the translucent navigation setting if it is currently flagged.
1096 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
1098 // Remove the translucent status bar overlay.
1099 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
1101 // Remove the translucent status bar overlay on the `Drawer Layout`, which is special and needs its own command.
1102 drawerLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
1104 /* SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
1105 * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
1106 * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
1108 rootCoordinatorLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
1109 } else { // Hide everything except the status and navigation bars.
1110 // Remove any `SYSTEM_UI` flags from `rootCoordinatorLayout`.
1111 rootCoordinatorLayout.setSystemUiVisibility(0);
1113 // Add the translucent status flag if it is unset.
1114 getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
1116 if (translucentNavigationBarOnFullscreen) {
1117 // Set the navigation bar to be translucent. This also resets `drawerLayout's` `View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN`.
1118 getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
1120 // Set the navigation bar to be black.
1121 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
1124 } else { // Switch to normal viewing mode.
1125 // Show the `appBar` if `findOnPageLinearLayout` is not visible.
1126 if (findOnPageLinearLayout.getVisibility() == View.GONE) {
1130 // Show the `BannerAd` in the free flavor.
1131 if (BuildConfig.FLAVOR.contentEquals("free")) {
1132 // Initialize the ad. The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations.
1133 AdHelper.initializeAds(findViewById(R.id.adview), getApplicationContext(), getFragmentManager(), getString(R.string.google_app_id), getString(R.string.ad_unit_id));
1136 // Remove any `SYSTEM_UI` flags from `rootCoordinatorLayout`.
1137 rootCoordinatorLayout.setSystemUiVisibility(0);
1139 // Remove the translucent navigation bar flag if it is set.
1140 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
1142 // Add the translucent status flag if it is unset. This also resets `drawerLayout's` `View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN`.
1143 getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
1145 // Constrain `rootCoordinatorLayout` inside the status and navigation bars.
1146 rootCoordinatorLayout.setFitsSystemWindows(true);
1149 // Show the ad if this is the free flavor.
1150 if (BuildConfig.FLAVOR.contentEquals("free")) {
1151 // Reload the ad. The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations.
1152 AdHelper.loadAd(findViewById(R.id.adview), getApplicationContext(), getString(R.string.ad_unit_id));
1158 public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) {
1159 // Show the file chooser if the device is running API >= 21.
1160 if (Build.VERSION.SDK_INT >= 21) {
1161 // Store the file path callback.
1162 fileChooserCallback = filePathCallback;
1164 // Create an intent to open a chooser based ont the file chooser parameters.
1165 Intent fileChooserIntent = fileChooserParams.createIntent();
1167 // Open the file chooser. Currently only one `startActivityForResult` exists in this activity, so the request code, used to differentiate them, is simply `0`.
1168 startActivityForResult(fileChooserIntent, 0);
1174 // Register `mainWebView` for a context menu. This is used to see link targets and download images.
1175 registerForContextMenu(mainWebView);
1177 // Allow the downloading of files.
1178 mainWebView.setDownloadListener((String url, String userAgent, String contentDisposition, String mimetype, long contentLength) -> {
1179 // Check if the download should be processed by an external app.
1180 if (downloadWithExternalApp) { // Download with an external app.
1181 openUrlWithExternalApp(url);
1182 } else { // Download with Android's download manager.
1183 // Check to see if the WRITE_EXTERNAL_STORAGE permission has already been granted.
1184 if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) { // The storage permission has not been granted.
1185 // The WRITE_EXTERNAL_STORAGE permission needs to be requested.
1187 // Store the variables for future use by `onRequestPermissionsResult()`.
1189 downloadContentDisposition = contentDisposition;
1190 downloadContentLength = contentLength;
1192 // Show a dialog if the user has previously denied the permission.
1193 if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) { // Show a dialog explaining the request first.
1194 // Instantiate the download location permission alert dialog and set the download type to DOWNLOAD_FILE.
1195 DialogFragment downloadLocationPermissionDialogFragment = DownloadLocationPermissionDialog.downloadType(DownloadLocationPermissionDialog.DOWNLOAD_FILE);
1197 // Show the download location permission alert dialog. The permission will be requested when the the dialog is closed.
1198 downloadLocationPermissionDialogFragment.show(getFragmentManager(), getString(R.string.download_location));
1199 } else { // Show the permission request directly.
1200 // Request the permission. The download dialog will be launched by `onRequestPermissionResult()`.
1201 ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_FILE_REQUEST_CODE);
1203 } else { // The storage permission has already been granted.
1204 // Get a handle for the download file alert dialog.
1205 AppCompatDialogFragment downloadFileDialogFragment = DownloadFileDialog.fromUrl(url, contentDisposition, contentLength);
1207 // Show the download file alert dialog.
1208 downloadFileDialogFragment.show(getSupportFragmentManager(), getString(R.string.download));
1213 // Allow pinch to zoom.
1214 mainWebView.getSettings().setBuiltInZoomControls(true);
1216 // Hide zoom controls.
1217 mainWebView.getSettings().setDisplayZoomControls(false);
1219 // Set `mainWebView` to use a wide viewport. Otherwise, some web pages will be scrunched and some content will render outside the screen.
1220 mainWebView.getSettings().setUseWideViewPort(true);
1222 // Set `mainWebView` to load in overview mode (zoomed out to the maximum width).
1223 mainWebView.getSettings().setLoadWithOverviewMode(true);
1225 // Explicitly disable geolocation.
1226 mainWebView.getSettings().setGeolocationEnabled(false);
1228 // Initialize cookieManager.
1229 cookieManager = CookieManager.getInstance();
1231 // 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).
1232 customHeaders.put("X-Requested-With", "");
1234 // Initialize the default preference values the first time the program is run. `false` keeps this command from resetting any current preferences back to default.
1235 PreferenceManager.setDefaultValues(this, R.xml.preferences, false);
1237 // Get a handle for the `Runtime`.
1238 privacyBrowserRuntime = Runtime.getRuntime();
1240 // Store the application's private data directory.
1241 privateDataDirectoryString = getApplicationInfo().dataDir;
1242 // `dataDir` will vary, but will be something like `/data/user/0/com.stoutner.privacybrowser.standard`, which links to `/data/data/com.stoutner.privacybrowser.standard`.
1244 // Initialize `inFullScreenBrowsingMode`, which is always false at this point because Privacy Browser never starts in full screen browsing mode.
1245 inFullScreenBrowsingMode = false;
1247 // Initialize the privacy settings variables.
1248 javaScriptEnabled = false;
1249 firstPartyCookiesEnabled = false;
1250 thirdPartyCookiesEnabled = false;
1251 domStorageEnabled = false;
1252 saveFormDataEnabled = false; // Form data can be removed once the minimum API >= 26.
1255 // Store the default user agent.
1256 webViewDefaultUserAgent = mainWebView.getSettings().getUserAgentString();
1258 // Initialize the WebView title.
1259 webViewTitle = getString(R.string.no_title);
1261 // Initialize the favorite icon bitmap. `ContextCompat` must be used until API >= 21.
1262 Drawable favoriteIconDrawable = ContextCompat.getDrawable(getApplicationContext(), R.drawable.world);
1263 BitmapDrawable favoriteIconBitmapDrawable = (BitmapDrawable) favoriteIconDrawable;
1264 assert favoriteIconBitmapDrawable != null;
1265 favoriteIconDefaultBitmap = favoriteIconBitmapDrawable.getBitmap();
1267 // If the favorite icon is null, load the default.
1268 if (favoriteIconBitmap == null) {
1269 favoriteIconBitmap = favoriteIconDefaultBitmap;
1272 // Initialize the user agent array adapter and string array.
1273 userAgentNamesArray = ArrayAdapter.createFromResource(this, R.array.user_agent_names, R.layout.spinner_item);
1274 userAgentDataArray = resources.getStringArray(R.array.user_agent_data);
1276 // Apply the app settings from the shared preferences.
1279 // Instantiate the block list helper.
1280 BlockListHelper blockListHelper = new BlockListHelper();
1282 // Initialize the list of resource requests.
1283 resourceRequests = new ArrayList<>();
1285 // Parse the block lists.
1286 final ArrayList<List<String[]>> easyList = blockListHelper.parseBlockList(getAssets(), "blocklists/easylist.txt");
1287 final ArrayList<List<String[]>> easyPrivacy = blockListHelper.parseBlockList(getAssets(), "blocklists/easyprivacy.txt");
1288 final ArrayList<List<String[]>> fanboysAnnoyanceList = blockListHelper.parseBlockList(getAssets(), "blocklists/fanboy-annoyance.txt");
1289 final ArrayList<List<String[]>> fanboysSocialList = blockListHelper.parseBlockList(getAssets(), "blocklists/fanboy-social.txt");
1290 final ArrayList<List<String[]>> ultraPrivacy = blockListHelper.parseBlockList(getAssets(), "blocklists/ultraprivacy.txt");
1292 // Store the list versions.
1293 easyListVersion = easyList.get(0).get(0)[0];
1294 easyPrivacyVersion = easyPrivacy.get(0).get(0)[0];
1295 fanboysAnnoyanceVersion = fanboysAnnoyanceList.get(0).get(0)[0];
1296 fanboysSocialVersion = fanboysSocialList.get(0).get(0)[0];
1297 ultraPrivacyVersion = ultraPrivacy.get(0).get(0)[0];
1299 // Get a handle for the activity. This is used to update the requests counter while the navigation menu is open.
1300 Activity activity = this;
1302 mainWebView.setWebViewClient(new WebViewClient() {
1303 // `shouldOverrideUrlLoading` makes this `WebView` the default handler for URLs inside the app, so that links are not kicked out to other apps.
1304 // The deprecated `shouldOverrideUrlLoading` must be used until API >= 24.
1305 @SuppressWarnings("deprecation")
1307 public boolean shouldOverrideUrlLoading(WebView view, String url) {
1308 if (url.startsWith("http")) { // Load the URL in Privacy Browser.
1309 // Reset the formatted URL string so the page will load correctly if blocking of third-party requests is enabled.
1310 formattedUrlString = "";
1312 // Apply the domain settings for the new URL. `applyDomainSettings` doesn't do anything if the domain has not changed.
1313 boolean userAgentChanged = applyDomainSettings(url, true, false);
1315 // Check if the user agent has changed.
1316 if (userAgentChanged) {
1317 // Manually load the URL. The changing of the user agent will cause WebView to reload the previous URL.
1318 mainWebView.loadUrl(url, customHeaders);
1320 // Returning true indicates that Privacy Browser is manually handling the loading of the URL.
1323 // Returning false causes the current WebView to handle the URL and prevents it from adding redirects to the history list.
1326 } else if (url.startsWith("mailto:")) { // Load the email address in an external email program.
1327 // Use `ACTION_SENDTO` instead of `ACTION_SEND` so that only email programs are launched.
1328 Intent emailIntent = new Intent(Intent.ACTION_SENDTO);
1330 // Parse the url and set it as the data for the intent.
1331 emailIntent.setData(Uri.parse(url));
1333 // Open the email program in a new task instead of as part of Privacy Browser.
1334 emailIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
1337 startActivity(emailIntent);
1339 // Returning true indicates Privacy Browser is handling the URL by creating an intent.
1341 } else if (url.startsWith("tel:")) { // Load the phone number in the dialer.
1342 // Open the dialer and load the phone number, but wait for the user to place the call.
1343 Intent dialIntent = new Intent(Intent.ACTION_DIAL);
1345 // Add the phone number to the intent.
1346 dialIntent.setData(Uri.parse(url));
1348 // Open the dialer in a new task instead of as part of Privacy Browser.
1349 dialIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
1352 startActivity(dialIntent);
1354 // Returning true indicates Privacy Browser is handling the URL by creating an intent.
1356 } else { // Load a system chooser to select an app that can handle the URL.
1357 // Open an app that can handle the URL.
1358 Intent genericIntent = new Intent(Intent.ACTION_VIEW);
1360 // Add the URL to the intent.
1361 genericIntent.setData(Uri.parse(url));
1363 // List all apps that can handle the URL instead of just opening the first one.
1364 genericIntent.addCategory(Intent.CATEGORY_BROWSABLE);
1366 // Open the app in a new task instead of as part of Privacy Browser.
1367 genericIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
1369 // Start the app or display a snackbar if no app is available to handle the URL.
1371 startActivity(genericIntent);
1372 } catch (ActivityNotFoundException exception) {
1373 Snackbar.make(mainWebView, getString(R.string.unrecognized_url) + " " + url, Snackbar.LENGTH_SHORT).show();
1376 // Returning true indicates Privacy Browser is handling the URL by creating an intent.
1381 // Check requests against the block lists. The deprecated `shouldInterceptRequest()` must be used until minimum API >= 21.
1382 @SuppressWarnings("deprecation")
1384 public WebResourceResponse shouldInterceptRequest(WebView view, String url){
1385 // Create an empty web resource response to be used if the resource request is blocked.
1386 WebResourceResponse emptyWebResourceResponse = new WebResourceResponse("text/plain", "utf8", new ByteArrayInputStream("".getBytes()));
1388 // Reset the whitelist results tracker.
1389 whiteListResultStringArray = null;
1391 // Initialize the third party request tracker.
1392 boolean isThirdPartyRequest = false;
1394 // Initialize the current domain string.
1395 String currentDomain = "";
1397 // Nobody is happy when comparing null strings.
1398 if (!(formattedUrlString == null) && !(url == null)) {
1399 // Get the domain strings to URIs.
1400 Uri currentDomainUri = Uri.parse(formattedUrlString);
1401 Uri requestDomainUri = Uri.parse(url);
1403 // Get the domain host names.
1404 String currentBaseDomain = currentDomainUri.getHost();
1405 String requestBaseDomain = requestDomainUri.getHost();
1407 // Update the current domain variable.
1408 currentDomain = currentBaseDomain;
1410 // Only compare the current base domain and the request base domain if neither is null.
1411 if (!(currentBaseDomain == null) && !(requestBaseDomain == null)) {
1412 // Determine the current base domain.
1413 while (currentBaseDomain.indexOf(".", currentBaseDomain.indexOf(".") + 1) > 0) { // There is at least one subdomain.
1414 // Remove the first subdomain.
1415 currentBaseDomain = currentBaseDomain.substring(currentBaseDomain.indexOf(".") + 1);
1418 // Determine the request base domain.
1419 while (requestBaseDomain.indexOf(".", requestBaseDomain.indexOf(".") + 1) > 0) { // There is at least one subdomain.
1420 // Remove the first subdomain.
1421 requestBaseDomain = requestBaseDomain.substring(requestBaseDomain.indexOf(".") + 1);
1424 // Update the third party request tracker.
1425 isThirdPartyRequest = !currentBaseDomain.equals(requestBaseDomain);
1429 // Block third-party requests if enabled.
1430 if (isThirdPartyRequest && blockAllThirdPartyRequests) {
1431 // Increment the blocked requests counters.
1433 thirdPartyBlockedRequests++;
1435 // Update the titles of the blocklist menu items. This must be run from the UI thread.
1436 activity.runOnUiThread(() -> {
1437 navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
1438 blocklistsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
1439 blockAllThirdPartyRequestsMenuItem.setTitle(thirdPartyBlockedRequests + " - " + getString(R.string.block_all_third_party_requests));
1442 // Add the request to the log.
1443 resourceRequests.add(new String[]{String.valueOf(REQUEST_THIRD_PARTY), url});
1445 // Return an empty web resource response.
1446 return emptyWebResourceResponse;
1449 // Check UltraPrivacy if it is enabled.
1450 if (ultraPrivacyEnabled) {
1451 if (blockListHelper.isBlocked(currentDomain, url, isThirdPartyRequest, ultraPrivacy)) {
1452 // Increment the blocked requests counters.
1454 ultraPrivacyBlockedRequests++;
1456 // Update the titles of the blocklist menu items. This must be run from the UI thread.
1457 activity.runOnUiThread(() -> {
1458 navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
1459 blocklistsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
1460 ultraPrivacyMenuItem.setTitle(ultraPrivacyBlockedRequests + " - " + getString(R.string.ultraprivacy));
1463 // The resource request was blocked. Return an empty web resource response.
1464 return emptyWebResourceResponse;
1467 // If the whitelist result is not null, the request has been allowed by UltraPrivacy.
1468 if (whiteListResultStringArray != null) {
1469 // Add a whitelist entry to the resource requests array.
1470 resourceRequests.add(whiteListResultStringArray);
1472 // The resource request has been allowed by UltraPrivacy. `return null` loads the requested resource.
1477 // Check EasyList if it is enabled.
1478 if (easyListEnabled) {
1479 if (blockListHelper.isBlocked(currentDomain, url, isThirdPartyRequest, easyList)) {
1480 // Increment the blocked requests counters.
1482 easyListBlockedRequests++;
1484 // Update the titles of the blocklist menu items. This must be run from the UI thread.
1485 activity.runOnUiThread(() -> {
1486 navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
1487 blocklistsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
1488 easyListMenuItem.setTitle(easyListBlockedRequests + " - " + getString(R.string.easylist));
1491 // Reset the whitelist results tracker (because otherwise it will sometimes add results to the list due to a race condition).
1492 whiteListResultStringArray = null;
1494 // The resource request was blocked. Return an empty web resource response.
1495 return emptyWebResourceResponse;
1499 // Check EasyPrivacy if it is enabled.
1500 if (easyPrivacyEnabled) {
1501 if (blockListHelper.isBlocked(currentDomain, url, isThirdPartyRequest, easyPrivacy)) {
1502 // Increment the blocked requests counters.
1504 easyPrivacyBlockedRequests++;
1506 // Update the titles of the blocklist menu items. This must be run from the UI thread.
1507 activity.runOnUiThread(() -> {
1508 navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
1509 blocklistsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
1510 easyPrivacyMenuItem.setTitle(easyPrivacyBlockedRequests + " - " + getString(R.string.easyprivacy));
1513 // Reset the whitelist results tracker (because otherwise it will sometimes add results to the list due to a race condition).
1514 whiteListResultStringArray = null;
1516 // The resource request was blocked. Return an empty web resource response.
1517 return emptyWebResourceResponse;
1521 // Check Fanboy’s Annoyance List if it is enabled.
1522 if (fanboysAnnoyanceListEnabled) {
1523 if (blockListHelper.isBlocked(currentDomain, url, isThirdPartyRequest, fanboysAnnoyanceList)) {
1524 // Increment the blocked requests counters.
1526 fanboysAnnoyanceListBlockedRequests++;
1528 // Update the titles of the blocklist menu items. This must be run from the UI thread.
1529 activity.runOnUiThread(() -> {
1530 navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
1531 blocklistsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
1532 fanboysAnnoyanceListMenuItem.setTitle(fanboysAnnoyanceListBlockedRequests + " - " + getString(R.string.fanboys_annoyance_list));
1535 // Reset the whitelist results tracker (because otherwise it will sometimes add results to the list due to a race condition).
1536 whiteListResultStringArray = null;
1538 // The resource request was blocked. Return an empty web resource response.
1539 return emptyWebResourceResponse;
1541 } else if (fanboysSocialBlockingListEnabled){ // Only check Fanboy’s Social Blocking List if Fanboy’s Annoyance List is disabled.
1542 if (blockListHelper.isBlocked(currentDomain, url, isThirdPartyRequest, fanboysSocialList)) {
1543 // Increment the blocked requests counters.
1545 fanboysSocialBlockingListBlockedRequests++;
1547 // Update the titles of the blocklist menu items. This must be run from the UI thread.
1548 activity.runOnUiThread(() -> {
1549 navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
1550 blocklistsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
1551 fanboysSocialBlockingListMenuItem.setTitle(fanboysSocialBlockingListBlockedRequests + " - " + getString(R.string.fanboys_social_blocking_list));
1554 // Reset the whitelist results tracker (because otherwise it will sometimes add results to the list due to a race condition).
1555 whiteListResultStringArray = null;
1557 // The resource request was blocked. Return an empty web resource response.
1558 return emptyWebResourceResponse;
1562 // Add the request to the log because it hasn't been processed by any of the previous checks.
1563 if (whiteListResultStringArray != null ) { // The request was processed by a whitelist.
1564 resourceRequests.add(whiteListResultStringArray);
1565 } else { // The request didn't match any blocklist entry. Log it as a default request.
1566 resourceRequests.add(new String[]{String.valueOf(REQUEST_DEFAULT), url});
1569 // The resource request has not been blocked. `return null` loads the requested resource.
1573 // Handle HTTP authentication requests.
1575 public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, String host, String realm) {
1576 // Store `handler` so it can be accessed from `onHttpAuthenticationCancel()` and `onHttpAuthenticationProceed()`.
1577 httpAuthHandler = handler;
1579 // Display the HTTP authentication dialog.
1580 AppCompatDialogFragment httpAuthenticationDialogFragment = HttpAuthenticationDialog.displayDialog(host, realm);
1581 httpAuthenticationDialogFragment.show(getSupportFragmentManager(), getString(R.string.http_authentication));
1584 // Update the URL in urlTextBox when the page starts to load.
1586 public void onPageStarted(WebView view, String url, Bitmap favicon) {
1587 // Reset the list of resource requests.
1588 resourceRequests.clear();
1590 // Initialize the counters for requests blocked by each blocklist.
1591 blockedRequests = 0;
1592 easyListBlockedRequests = 0;
1593 easyPrivacyBlockedRequests = 0;
1594 fanboysAnnoyanceListBlockedRequests = 0;
1595 fanboysSocialBlockingListBlockedRequests = 0;
1596 ultraPrivacyBlockedRequests = 0;
1597 thirdPartyBlockedRequests = 0;
1599 // If night mode is enabled, hide `mainWebView` until after the night mode CSS is applied.
1601 mainWebView.setVisibility(View.INVISIBLE);
1604 // Hide the keyboard. `0` indicates no additional flags.
1605 inputMethodManager.hideSoftInputFromWindow(mainWebView.getWindowToken(), 0);
1607 // Check to see if Privacy Browser is waiting on Orbot.
1608 if (!waitingForOrbot) { // We are not waiting on Orbot, so we need to process the URL.
1609 // We need to update `formattedUrlString` at the beginning of the load, so that if the user toggles JavaScript during the load the new website is reloaded.
1610 formattedUrlString = url;
1612 // Display the formatted URL text.
1613 urlTextBox.setText(formattedUrlString);
1615 // Apply text highlighting to `urlTextBox`.
1618 // Apply any custom domain settings if the URL was loaded by navigating history.
1619 if (navigatingHistory) {
1620 // Apply the domain settings.
1621 boolean userAgentChanged = applyDomainSettings(url, true, false);
1623 // Reset `navigatingHistory`.
1624 navigatingHistory = false;
1626 // Manually load the URL if the user agent has changed, which will have caused the previous URL to be reloaded.
1627 if (userAgentChanged) {
1628 loadUrl(formattedUrlString);
1632 // 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.
1633 urlIsLoading = true;
1635 // Replace Refresh with Stop if the menu item has been created. (The WebView typically begins loading before the menu items are instantiated.)
1636 if (refreshMenuItem != null) {
1638 refreshMenuItem.setTitle(R.string.stop);
1640 // If the icon is displayed in the AppBar, set it according to the theme.
1641 if (displayAdditionalAppBarIcons) {
1643 refreshMenuItem.setIcon(R.drawable.close_dark);
1645 refreshMenuItem.setIcon(R.drawable.close_light);
1652 // It is necessary to update `formattedUrlString` and `urlTextBox` after the page finishes loading because the final URL can change during load.
1654 public void onPageFinished(WebView view, String url) {
1655 // Reset the wide view port if it has been turned off by the waiting for Orbot message.
1656 if (!waitingForOrbot) {
1657 // Only use a wide view port if the URL starts with `http`, not for `file://` and `content://`.
1658 mainWebView.getSettings().setUseWideViewPort(url.startsWith("http"));
1661 // Flush any cookies to persistent storage. `CookieManager` has become very lazy about flushing cookies in recent versions.
1662 if (firstPartyCookiesEnabled && Build.VERSION.SDK_INT >= 21) {
1663 cookieManager.flush();
1666 // Update the Refresh menu item if it has been created.
1667 if (refreshMenuItem != null) {
1668 // Reset the Refresh title.
1669 refreshMenuItem.setTitle(R.string.refresh);
1671 // If the icon is displayed in the AppBar, reset it according to the theme.
1672 if (displayAdditionalAppBarIcons) {
1674 refreshMenuItem.setIcon(R.drawable.refresh_enabled_dark);
1676 refreshMenuItem.setIcon(R.drawable.refresh_enabled_light);
1681 // Reset `urlIsLoading`, which is used to prevent reloads on redirect if the user agent changes.
1682 urlIsLoading = false;
1684 // Clear the cache and history if Incognito Mode is enabled.
1685 if (incognitoModeEnabled) {
1686 // Clear the cache. `true` includes disk files.
1687 mainWebView.clearCache(true);
1689 // Clear the back/forward history.
1690 mainWebView.clearHistory();
1692 // Manually delete cache folders.
1694 // Delete the main cache directory.
1695 privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/cache");
1697 // Delete the secondary `Service Worker` cache directory.
1698 // A `String[]` must be used because the directory contains a space and `Runtime.exec` will not escape the string correctly otherwise.
1699 privacyBrowserRuntime.exec(new String[] {"rm", "-rf", privateDataDirectoryString + "/app_webview/Service Worker/"});
1700 } catch (IOException e) {
1701 // Do nothing if an error is thrown.
1705 // Update `urlTextBox` and apply domain settings if not waiting on Orbot.
1706 if (!waitingForOrbot) {
1707 // Check to see if `WebView` has set `url` to be `about:blank`.
1708 if (url.equals("about:blank")) { // `WebView` is blank, so `formattedUrlString` should be `""` and `urlTextBox` should display a hint.
1709 // Set `formattedUrlString` to `""`.
1710 formattedUrlString = "";
1712 urlTextBox.setText(formattedUrlString);
1714 // Request focus for `urlTextBox`.
1715 urlTextBox.requestFocus();
1717 // Display the keyboard.
1718 inputMethodManager.showSoftInput(urlTextBox, 0);
1720 // Apply the domain settings. This clears any settings from the previous domain.
1721 applyDomainSettings(formattedUrlString, true, false);
1722 } else { // `WebView` has loaded a webpage.
1723 // Set `formattedUrlString`.
1724 formattedUrlString = url;
1726 // Only update `urlTextBox` if the user is not typing in it.
1727 if (!urlTextBox.hasFocus()) {
1728 // Display the formatted URL text.
1729 urlTextBox.setText(formattedUrlString);
1731 // Apply text highlighting to `urlTextBox`.
1736 // Store the SSL certificate so it can be accessed from `ViewSslCertificateDialog` and `PinnedSslCertificateMismatchDialog`.
1737 sslCertificate = mainWebView.getCertificate();
1739 // Check the current website SSL certificate against the pinned SSL certificate if there is a pinned SSL certificate the user has not chosen to ignore it for this session.
1740 // Also ignore if changes in the user agent causes an error while navigating history.
1741 if (pinnedDomainSslCertificate && !ignorePinnedSslCertificate && navigatingHistory) {
1742 // Initialize the current SSL certificate variables.
1743 String currentWebsiteIssuedToCName = "";
1744 String currentWebsiteIssuedToOName = "";
1745 String currentWebsiteIssuedToUName = "";
1746 String currentWebsiteIssuedByCName = "";
1747 String currentWebsiteIssuedByOName = "";
1748 String currentWebsiteIssuedByUName = "";
1749 Date currentWebsiteSslStartDate = null;
1750 Date currentWebsiteSslEndDate = null;
1753 // Extract the individual pieces of information from the current website SSL certificate if it is not null.
1754 if (sslCertificate != null) {
1755 currentWebsiteIssuedToCName = sslCertificate.getIssuedTo().getCName();
1756 currentWebsiteIssuedToOName = sslCertificate.getIssuedTo().getOName();
1757 currentWebsiteIssuedToUName = sslCertificate.getIssuedTo().getUName();
1758 currentWebsiteIssuedByCName = sslCertificate.getIssuedBy().getCName();
1759 currentWebsiteIssuedByOName = sslCertificate.getIssuedBy().getOName();
1760 currentWebsiteIssuedByUName = sslCertificate.getIssuedBy().getUName();
1761 currentWebsiteSslStartDate = sslCertificate.getValidNotBeforeDate();
1762 currentWebsiteSslEndDate = sslCertificate.getValidNotAfterDate();
1765 // 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`.
1766 String currentWebsiteSslStartDateString = "";
1767 String currentWebsiteSslEndDateString = "";
1768 String pinnedDomainSslStartDateString = "";
1769 String pinnedDomainSslEndDateString = "";
1771 // Convert the `Dates` to `Strings` if they are not `null`.
1772 if (currentWebsiteSslStartDate != null) {
1773 currentWebsiteSslStartDateString = currentWebsiteSslStartDate.toString();
1776 if (currentWebsiteSslEndDate != null) {
1777 currentWebsiteSslEndDateString = currentWebsiteSslEndDate.toString();
1780 if (pinnedDomainSslStartDate != null) {
1781 pinnedDomainSslStartDateString = pinnedDomainSslStartDate.toString();
1784 if (pinnedDomainSslEndDate != null) {
1785 pinnedDomainSslEndDateString = pinnedDomainSslEndDate.toString();
1788 // Check to see if the pinned SSL certificate matches the current website certificate.
1789 if (!currentWebsiteIssuedToCName.equals(pinnedDomainSslIssuedToCNameString) || !currentWebsiteIssuedToOName.equals(pinnedDomainSslIssuedToONameString) ||
1790 !currentWebsiteIssuedToUName.equals(pinnedDomainSslIssuedToUNameString) || !currentWebsiteIssuedByCName.equals(pinnedDomainSslIssuedByCNameString) ||
1791 !currentWebsiteIssuedByOName.equals(pinnedDomainSslIssuedByONameString) || !currentWebsiteIssuedByUName.equals(pinnedDomainSslIssuedByUNameString) ||
1792 !currentWebsiteSslStartDateString.equals(pinnedDomainSslStartDateString) || !currentWebsiteSslEndDateString.equals(pinnedDomainSslEndDateString)) {
1793 // The pinned SSL certificate doesn't match the current domain certificate.
1794 //Display the pinned SSL certificate mismatch `AlertDialog`.
1795 AppCompatDialogFragment pinnedSslCertificateMismatchDialogFragment = new PinnedSslCertificateMismatchDialog();
1796 pinnedSslCertificateMismatchDialogFragment.show(getSupportFragmentManager(), getString(R.string.ssl_certificate_mismatch));
1802 // Handle SSL Certificate errors.
1804 public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
1805 // Get the current website SSL certificate.
1806 SslCertificate currentWebsiteSslCertificate = error.getCertificate();
1808 // Extract the individual pieces of information from the current website SSL certificate.
1809 String currentWebsiteIssuedToCName = currentWebsiteSslCertificate.getIssuedTo().getCName();
1810 String currentWebsiteIssuedToOName = currentWebsiteSslCertificate.getIssuedTo().getOName();
1811 String currentWebsiteIssuedToUName = currentWebsiteSslCertificate.getIssuedTo().getUName();
1812 String currentWebsiteIssuedByCName = currentWebsiteSslCertificate.getIssuedBy().getCName();
1813 String currentWebsiteIssuedByOName = currentWebsiteSslCertificate.getIssuedBy().getOName();
1814 String currentWebsiteIssuedByUName = currentWebsiteSslCertificate.getIssuedBy().getUName();
1815 Date currentWebsiteSslStartDate = currentWebsiteSslCertificate.getValidNotBeforeDate();
1816 Date currentWebsiteSslEndDate = currentWebsiteSslCertificate.getValidNotAfterDate();
1818 // Proceed to the website if the current SSL website certificate matches the pinned domain certificate.
1819 if (pinnedDomainSslCertificate &&
1820 currentWebsiteIssuedToCName.equals(pinnedDomainSslIssuedToCNameString) && currentWebsiteIssuedToOName.equals(pinnedDomainSslIssuedToONameString) &&
1821 currentWebsiteIssuedToUName.equals(pinnedDomainSslIssuedToUNameString) && currentWebsiteIssuedByCName.equals(pinnedDomainSslIssuedByCNameString) &&
1822 currentWebsiteIssuedByOName.equals(pinnedDomainSslIssuedByONameString) && currentWebsiteIssuedByUName.equals(pinnedDomainSslIssuedByUNameString) &&
1823 currentWebsiteSslStartDate.equals(pinnedDomainSslStartDate) && currentWebsiteSslEndDate.equals(pinnedDomainSslEndDate)) {
1824 // An SSL certificate is pinned and matches the current domain certificate.
1825 // Proceed to the website without displaying an error.
1827 } else { // Either there isn't a pinned SSL certificate or it doesn't match the current website certificate.
1828 // Store `handler` so it can be accesses from `onSslErrorCancel()` and `onSslErrorProceed()`.
1829 sslErrorHandler = handler;
1831 // Display the SSL error `AlertDialog`.
1832 AppCompatDialogFragment sslCertificateErrorDialogFragment = SslCertificateErrorDialog.displayDialog(error);
1833 sslCertificateErrorDialogFragment.show(getSupportFragmentManager(), getString(R.string.ssl_certificate_error));
1838 // Get the intent that started the app.
1839 Intent launchingIntent = getIntent();
1841 // Get the information from the intent.
1842 String launchingIntentAction = launchingIntent.getAction();
1843 Uri launchingIntentUriData = launchingIntent.getData();
1845 // If the intent action is a web search, perform the search.
1846 if ((launchingIntentAction != null) && launchingIntentAction.equals(Intent.ACTION_WEB_SEARCH)) {
1847 // Create an encoded URL string.
1848 String encodedUrlString;
1850 // Sanitize the search input and convert it to a search.
1852 encodedUrlString = URLEncoder.encode(launchingIntent.getStringExtra(SearchManager.QUERY), "UTF-8");
1853 } catch (UnsupportedEncodingException exception) {
1854 encodedUrlString = "";
1857 // Add the base search URL.
1858 formattedUrlString = searchURL + encodedUrlString;
1859 } else if (launchingIntentUriData != null){ // Check to see if the intent contains a new URL.
1860 // Set the formatted URL string.
1861 formattedUrlString = launchingIntentUriData.toString();
1864 // Load the website if not waiting for Orbot to connect.
1865 if (!waitingForOrbot) {
1866 loadUrl(formattedUrlString);
1871 protected void onNewIntent(Intent intent) {
1872 // Sets the new intent as the activity intent, so that any future `getIntent()`s pick up this one instead of creating a new activity.
1875 // Get the information from the intent.
1876 String intentAction = intent.getAction();
1877 Uri intentUriData = intent.getData();
1879 // If the intent action is a web search, perform the search.
1880 if ((intentAction != null) && intentAction.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(intent.getStringExtra(SearchManager.QUERY), "UTF-8");
1887 } catch (UnsupportedEncodingException exception) {
1888 encodedUrlString = "";
1891 // Add the base search URL.
1892 formattedUrlString = searchURL + encodedUrlString;
1893 } else if (intentUriData != null){ // Check to see if the intent contains a new URL.
1894 // Set the formatted URL string.
1895 formattedUrlString = intentUriData.toString();
1899 loadUrl(formattedUrlString);
1901 // Close the navigation drawer if it is open.
1902 if (drawerLayout.isDrawerVisible(GravityCompat.START)) {
1903 drawerLayout.closeDrawer(GravityCompat.START);
1906 // Close the bookmarks drawer if it is open.
1907 if (drawerLayout.isDrawerVisible(GravityCompat.END)) {
1908 drawerLayout.closeDrawer(GravityCompat.END);
1911 // Clear the keyboard if displayed and remove the focus on the urlTextBar if it has it.
1912 mainWebView.requestFocus();
1916 public void onRestart() {
1917 // Run the default commands.
1920 // Make sure Orbot is running if Privacy Browser is proxying through Orbot.
1921 if (proxyThroughOrbot) {
1922 // Request Orbot to start. If Orbot is already running no hard will be caused by this request.
1923 Intent orbotIntent = new Intent("org.torproject.android.intent.action.START");
1925 // Send the intent to the Orbot package.
1926 orbotIntent.setPackage("org.torproject.android");
1929 sendBroadcast(orbotIntent);
1932 // Apply the app settings if returning from the Settings activity..
1933 if (reapplyAppSettingsOnRestart) {
1934 // Apply the app settings.
1937 // Reload the webpage if displaying of images has been disabled in the Settings activity.
1938 if (reloadOnRestart) {
1939 // Reload `mainWebView`.
1940 mainWebView.reload();
1942 // Reset `reloadOnRestartBoolean`.
1943 reloadOnRestart = false;
1946 // Reset the return from settings flag.
1947 reapplyAppSettingsOnRestart = false;
1950 // Apply the domain settings if returning from the Domains activity.
1951 if (reapplyDomainSettingsOnRestart) {
1952 // Reapply the domain settings.
1953 applyDomainSettings(formattedUrlString, false, true);
1955 // Reset `reapplyDomainSettingsOnRestart`.
1956 reapplyDomainSettingsOnRestart = false;
1959 // Load the URL on restart to apply changes to night mode.
1960 if (loadUrlOnRestart) {
1961 // Load the current `formattedUrlString`.
1962 loadUrl(formattedUrlString);
1964 // Reset `loadUrlOnRestart.
1965 loadUrlOnRestart = false;
1968 // Update the bookmarks drawer if returning from the Bookmarks activity.
1969 if (restartFromBookmarksActivity) {
1970 // Close the bookmarks drawer.
1971 drawerLayout.closeDrawer(GravityCompat.END);
1973 // Reload the bookmarks drawer.
1974 loadBookmarksFolder();
1976 // Reset `restartFromBookmarksActivity`.
1977 restartFromBookmarksActivity = false;
1980 // Update the privacy icon. `true` runs `invalidateOptionsMenu` as the last step. This can be important if the screen was rotated.
1981 updatePrivacyIcons(true);
1984 // `onResume()` runs after `onStart()`, which runs after `onCreate()` and `onRestart()`.
1986 public void onResume() {
1987 // Run the default commands.
1990 // Resume JavaScript (if enabled).
1991 mainWebView.resumeTimers();
1993 // Resume `mainWebView`.
1994 mainWebView.onResume();
1996 // Resume the adView for the free flavor.
1997 if (BuildConfig.FLAVOR.contentEquals("free")) {
1999 AdHelper.resumeAd(findViewById(R.id.adview));
2002 // Display a message to the user if waiting for Orbot.
2003 if (waitingForOrbot && !orbotStatus.equals("ON")) {
2004 // Disable the wide view port so that the waiting for Orbot text is displayed correctly.
2005 mainWebView.getSettings().setUseWideViewPort(false);
2007 // Load a waiting page. `null` specifies no encoding, which defaults to ASCII.
2008 mainWebView.loadData(waitingForOrbotHtmlString, "text/html", null);
2011 if (displayingFullScreenVideo) {
2012 // Remove the translucent overlays.
2013 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
2015 // Remove the translucent status bar overlay on the `Drawer Layout`, which is special and needs its own command.
2016 drawerLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
2018 /* SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
2019 * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
2020 * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
2022 rootCoordinatorLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
2027 public void onPause() {
2028 // Run the default commands.
2031 // Pause `mainWebView`.
2032 mainWebView.onPause();
2034 // Stop all JavaScript.
2035 mainWebView.pauseTimers();
2037 // Pause the ad or it will continue to consume resources in the background on the free flavor.
2038 if (BuildConfig.FLAVOR.contentEquals("free")) {
2040 AdHelper.pauseAd(findViewById(R.id.adview));
2045 public void onDestroy() {
2046 // Unregister the Orbot status broadcast receiver.
2047 this.unregisterReceiver(orbotStatusBroadcastReceiver);
2049 // Close the bookmarks cursor and database.
2050 bookmarksCursor.close();
2051 bookmarksDatabaseHelper.close();
2053 // Run the default commands.
2058 public boolean onCreateOptionsMenu(Menu menu) {
2059 // Inflate the menu; this adds items to the action bar if it is present.
2060 getMenuInflater().inflate(R.menu.webview_options_menu, menu);
2062 // Set mainMenu so it can be used by `onOptionsItemSelected()` and `updatePrivacyIcons`.
2065 // Set the initial status of the privacy icons. `false` does not call `invalidateOptionsMenu` as the last step.
2066 updatePrivacyIcons(false);
2068 // Get handles for the menu items.
2069 MenuItem toggleFirstPartyCookiesMenuItem = menu.findItem(R.id.toggle_first_party_cookies);
2070 MenuItem toggleThirdPartyCookiesMenuItem = menu.findItem(R.id.toggle_third_party_cookies);
2071 MenuItem toggleDomStorageMenuItem = menu.findItem(R.id.toggle_dom_storage);
2072 MenuItem toggleSaveFormDataMenuItem = menu.findItem(R.id.toggle_save_form_data); // Form data can be removed once the minimum API >= 26.
2073 MenuItem clearFormDataMenuItem = menu.findItem(R.id.clear_form_data); // Form data can be removed once the minimum API >= 26.
2074 refreshMenuItem = menu.findItem(R.id.refresh);
2075 blocklistsMenuItem = menu.findItem(R.id.blocklists);
2076 easyListMenuItem = menu.findItem(R.id.easylist);
2077 easyPrivacyMenuItem = menu.findItem(R.id.easyprivacy);
2078 fanboysAnnoyanceListMenuItem = menu.findItem(R.id.fanboys_annoyance_list);
2079 fanboysSocialBlockingListMenuItem = menu.findItem(R.id.fanboys_social_blocking_list);
2080 ultraPrivacyMenuItem = menu.findItem(R.id.ultraprivacy);
2081 blockAllThirdPartyRequestsMenuItem = menu.findItem(R.id.block_all_third_party_requests);
2082 MenuItem adConsentMenuItem = menu.findItem(R.id.ad_consent);
2084 // Only display third-party cookies if API >= 21
2085 toggleThirdPartyCookiesMenuItem.setVisible(Build.VERSION.SDK_INT >= 21);
2087 // Only display the form data menu items if the API < 26.
2088 toggleSaveFormDataMenuItem.setVisible(Build.VERSION.SDK_INT < 26);
2089 clearFormDataMenuItem.setVisible(Build.VERSION.SDK_INT < 26);
2091 // Only show Ad Consent if this is the free flavor.
2092 adConsentMenuItem.setVisible(BuildConfig.FLAVOR.contentEquals("free"));
2094 // Get the shared preference values.
2095 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
2097 // Get the status of the additional AppBar icons.
2098 displayAdditionalAppBarIcons = sharedPreferences.getBoolean("display_additional_app_bar_icons", false);
2100 // 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.
2101 if (displayAdditionalAppBarIcons) {
2102 toggleFirstPartyCookiesMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
2103 toggleDomStorageMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
2104 refreshMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
2105 } else { //Do not display the additional icons.
2106 toggleFirstPartyCookiesMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
2107 toggleDomStorageMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
2108 refreshMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
2111 // Replace Refresh with Stop if a URL is already loading.
2114 refreshMenuItem.setTitle(R.string.stop);
2116 // If the icon is displayed in the AppBar, set it according to the theme.
2117 if (displayAdditionalAppBarIcons) {
2119 refreshMenuItem.setIcon(R.drawable.close_dark);
2121 refreshMenuItem.setIcon(R.drawable.close_light);
2130 public boolean onPrepareOptionsMenu(Menu menu) {
2131 // Get handles for the menu items.
2132 MenuItem addOrEditDomain = menu.findItem(R.id.add_or_edit_domain);
2133 MenuItem toggleFirstPartyCookiesMenuItem = menu.findItem(R.id.toggle_first_party_cookies);
2134 MenuItem toggleThirdPartyCookiesMenuItem = menu.findItem(R.id.toggle_third_party_cookies);
2135 MenuItem toggleDomStorageMenuItem = menu.findItem(R.id.toggle_dom_storage);
2136 MenuItem toggleSaveFormDataMenuItem = menu.findItem(R.id.toggle_save_form_data); // Form data can be removed once the minimum API >= 26.
2137 MenuItem clearDataMenuItem = menu.findItem(R.id.clear_data);
2138 MenuItem clearCookiesMenuItem = menu.findItem(R.id.clear_cookies);
2139 MenuItem clearDOMStorageMenuItem = menu.findItem(R.id.clear_dom_storage);
2140 MenuItem clearFormDataMenuItem = menu.findItem(R.id.clear_form_data); // Form data can be removed once the minimum API >= 26.
2141 MenuItem fontSizeMenuItem = menu.findItem(R.id.font_size);
2142 MenuItem swipeToRefreshMenuItem = menu.findItem(R.id.swipe_to_refresh);
2143 MenuItem displayImagesMenuItem = menu.findItem(R.id.display_images);
2144 MenuItem nightModeMenuItem = menu.findItem(R.id.night_mode);
2145 MenuItem proxyThroughOrbotMenuItem = menu.findItem(R.id.proxy_through_orbot);
2147 // Set the text for the domain menu item.
2148 if (domainSettingsApplied) {
2149 addOrEditDomain.setTitle(R.string.edit_domain_settings);
2151 addOrEditDomain.setTitle(R.string.add_domain_settings);
2154 // Set the status of the menu item checkboxes.
2155 toggleFirstPartyCookiesMenuItem.setChecked(firstPartyCookiesEnabled);
2156 toggleThirdPartyCookiesMenuItem.setChecked(thirdPartyCookiesEnabled);
2157 toggleDomStorageMenuItem.setChecked(domStorageEnabled);
2158 toggleSaveFormDataMenuItem.setChecked(saveFormDataEnabled); // Form data can be removed once the minimum API >= 26.
2159 easyListMenuItem.setChecked(easyListEnabled);
2160 easyPrivacyMenuItem.setChecked(easyPrivacyEnabled);
2161 fanboysAnnoyanceListMenuItem.setChecked(fanboysAnnoyanceListEnabled);
2162 fanboysSocialBlockingListMenuItem.setChecked(fanboysSocialBlockingListEnabled);
2163 ultraPrivacyMenuItem.setChecked(ultraPrivacyEnabled);
2164 blockAllThirdPartyRequestsMenuItem.setChecked(blockAllThirdPartyRequests);
2165 swipeToRefreshMenuItem.setChecked(swipeRefreshLayout.isEnabled());
2166 displayImagesMenuItem.setChecked(mainWebView.getSettings().getLoadsImagesAutomatically());
2167 nightModeMenuItem.setChecked(nightMode);
2168 proxyThroughOrbotMenuItem.setChecked(proxyThroughOrbot);
2170 // Enable third-party cookies if first-party cookies are enabled.
2171 toggleThirdPartyCookiesMenuItem.setEnabled(firstPartyCookiesEnabled);
2173 // Enable DOM Storage if JavaScript is enabled.
2174 toggleDomStorageMenuItem.setEnabled(javaScriptEnabled);
2176 // Enable Clear Cookies if there are any.
2177 clearCookiesMenuItem.setEnabled(cookieManager.hasCookies());
2179 // Get a count of the number of files in the Local Storage directory.
2180 File localStorageDirectory = new File (privateDataDirectoryString + "/app_webview/Local Storage/");
2181 int localStorageDirectoryNumberOfFiles = 0;
2182 if (localStorageDirectory.exists()) {
2183 localStorageDirectoryNumberOfFiles = localStorageDirectory.list().length;
2186 // Get a count of the number of files in the IndexedDB directory.
2187 File indexedDBDirectory = new File (privateDataDirectoryString + "/app_webview/IndexedDB");
2188 int indexedDBDirectoryNumberOfFiles = 0;
2189 if (indexedDBDirectory.exists()) {
2190 indexedDBDirectoryNumberOfFiles = indexedDBDirectory.list().length;
2193 // Enable Clear DOM Storage if there is any.
2194 clearDOMStorageMenuItem.setEnabled(localStorageDirectoryNumberOfFiles > 0 || indexedDBDirectoryNumberOfFiles > 0);
2196 // Enable Clear Form Data is there is any. This can be removed once the minimum API >= 26.
2197 if (Build.VERSION.SDK_INT < 26) {
2198 WebViewDatabase mainWebViewDatabase = WebViewDatabase.getInstance(this);
2199 clearFormDataMenuItem.setEnabled(mainWebViewDatabase.hasFormData());
2201 // Disable clear form data because it is not supported on current version of Android.
2202 clearFormDataMenuItem.setEnabled(false);
2205 // Enable Clear Data if any of the submenu items are enabled.
2206 clearDataMenuItem.setEnabled(clearCookiesMenuItem.isEnabled() || clearDOMStorageMenuItem.isEnabled() || clearFormDataMenuItem.isEnabled());
2208 // Disable Fanboy's Social Blocking List if Fanboy's Annoyance List is checked.
2209 fanboysSocialBlockingListMenuItem.setEnabled(!fanboysAnnoyanceListEnabled);
2211 // Initialize the display names for the blocklists with the number of blocked requests.
2212 blocklistsMenuItem.setTitle(getString(R.string.blocklists) + " - " + blockedRequests);
2213 easyListMenuItem.setTitle(easyListBlockedRequests + " - " + getString(R.string.easylist));
2214 easyPrivacyMenuItem.setTitle(easyPrivacyBlockedRequests + " - " + getString(R.string.easyprivacy));
2215 fanboysAnnoyanceListMenuItem.setTitle(fanboysAnnoyanceListBlockedRequests + " - " + getString(R.string.fanboys_annoyance_list));
2216 fanboysSocialBlockingListMenuItem.setTitle(fanboysSocialBlockingListBlockedRequests + " - " + getString(R.string.fanboys_social_blocking_list));
2217 ultraPrivacyMenuItem.setTitle(ultraPrivacyBlockedRequests + " - " + getString(R.string.ultraprivacy));
2218 blockAllThirdPartyRequestsMenuItem.setTitle(thirdPartyBlockedRequests + " - " + getString(R.string.block_all_third_party_requests));
2220 // Get the current user agent.
2221 String currentUserAgent = mainWebView.getSettings().getUserAgentString();
2223 // Select the current user agent menu item. A switch statement cannot be used because the user agents are not compile time constants.
2224 if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[0])) { // Privacy Browser.
2225 menu.findItem(R.id.user_agent_privacy_browser).setChecked(true);
2226 } else if (currentUserAgent.equals(webViewDefaultUserAgent)) { // WebView Default.
2227 menu.findItem(R.id.user_agent_webview_default).setChecked(true);
2228 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[2])) { // Firefox on Android.
2229 menu.findItem(R.id.user_agent_firefox_on_android).setChecked(true);
2230 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[3])) { // Chrome on Android.
2231 menu.findItem(R.id.user_agent_chrome_on_android).setChecked(true);
2232 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[4])) { // Safari on iOS.
2233 menu.findItem(R.id.user_agent_safari_on_ios).setChecked(true);
2234 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[5])) { // Firefox on Linux.
2235 menu.findItem(R.id.user_agent_firefox_on_linux).setChecked(true);
2236 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[6])) { // Chromium on Linux.
2237 menu.findItem(R.id.user_agent_chromium_on_linux).setChecked(true);
2238 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[7])) { // Firefox on Windows.
2239 menu.findItem(R.id.user_agent_firefox_on_windows).setChecked(true);
2240 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[8])) { // Chrome on Windows.
2241 menu.findItem(R.id.user_agent_chrome_on_windows).setChecked(true);
2242 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[9])) { // Edge on Windows.
2243 menu.findItem(R.id.user_agent_edge_on_windows).setChecked(true);
2244 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[10])) { // Internet Explorer on Windows.
2245 menu.findItem(R.id.user_agent_internet_explorer_on_windows).setChecked(true);
2246 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[11])) { // Safari on macOS.
2247 menu.findItem(R.id.user_agent_safari_on_macos).setChecked(true);
2248 } else { // Custom user agent.
2249 menu.findItem(R.id.user_agent_custom).setChecked(true);
2252 // Initialize font size variables.
2253 int fontSize = mainWebView.getSettings().getTextZoom();
2254 String fontSizeTitle;
2255 MenuItem selectedFontSizeMenuItem;
2257 // Prepare the font size title and current size menu item.
2260 fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.twenty_five_percent);
2261 selectedFontSizeMenuItem = menu.findItem(R.id.font_size_twenty_five_percent);
2265 fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.fifty_percent);
2266 selectedFontSizeMenuItem = menu.findItem(R.id.font_size_fifty_percent);
2270 fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.seventy_five_percent);
2271 selectedFontSizeMenuItem = menu.findItem(R.id.font_size_seventy_five_percent);
2275 fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.one_hundred_percent);
2276 selectedFontSizeMenuItem = menu.findItem(R.id.font_size_one_hundred_percent);
2280 fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.one_hundred_twenty_five_percent);
2281 selectedFontSizeMenuItem = menu.findItem(R.id.font_size_one_hundred_twenty_five_percent);
2285 fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.one_hundred_fifty_percent);
2286 selectedFontSizeMenuItem = menu.findItem(R.id.font_size_one_hundred_fifty_percent);
2290 fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.one_hundred_seventy_five_percent);
2291 selectedFontSizeMenuItem = menu.findItem(R.id.font_size_one_hundred_seventy_five_percent);
2295 fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.two_hundred_percent);
2296 selectedFontSizeMenuItem = menu.findItem(R.id.font_size_two_hundred_percent);
2300 fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.one_hundred_percent);
2301 selectedFontSizeMenuItem = menu.findItem(R.id.font_size_one_hundred_percent);
2305 // Set the font size title and select the current size menu item.
2306 fontSizeMenuItem.setTitle(fontSizeTitle);
2307 selectedFontSizeMenuItem.setChecked(true);
2309 // Run all the other default commands.
2310 super.onPrepareOptionsMenu(menu);
2312 // Display the menu.
2317 // Remove Android Studio's warning about the dangers of using SetJavaScriptEnabled.
2318 @SuppressLint("SetJavaScriptEnabled")
2319 // removeAllCookies is deprecated, but it is required for API < 21.
2320 @SuppressWarnings("deprecation")
2321 public boolean onOptionsItemSelected(MenuItem menuItem) {
2322 // Get the selected menu item ID.
2323 int menuItemId = menuItem.getItemId();
2325 // Set the commands that relate to the menu entries.
2326 switch (menuItemId) {
2327 case R.id.toggle_javascript:
2328 // Switch the status of javaScriptEnabled.
2329 javaScriptEnabled = !javaScriptEnabled;
2331 // Apply the new JavaScript status.
2332 mainWebView.getSettings().setJavaScriptEnabled(javaScriptEnabled);
2334 // Update the privacy icon. `true` runs `invalidateOptionsMenu` as the last step.
2335 updatePrivacyIcons(true);
2337 // Display a `Snackbar`.
2338 if (javaScriptEnabled) { // JavaScrip is enabled.
2339 Snackbar.make(findViewById(R.id.main_webview), R.string.javascript_enabled, Snackbar.LENGTH_SHORT).show();
2340 } else if (firstPartyCookiesEnabled) { // JavaScript is disabled, but first-party cookies are enabled.
2341 Snackbar.make(findViewById(R.id.main_webview), R.string.javascript_disabled, Snackbar.LENGTH_SHORT).show();
2342 } else { // Privacy mode.
2343 Snackbar.make(findViewById(R.id.main_webview), R.string.privacy_mode, Snackbar.LENGTH_SHORT).show();
2346 // Reload the WebView.
2347 mainWebView.reload();
2350 case R.id.add_or_edit_domain:
2351 if (domainSettingsApplied) { // Edit the current domain settings.
2352 // Reapply the domain settings on returning to `MainWebViewActivity`.
2353 reapplyDomainSettingsOnRestart = true;
2354 currentDomainName = "";
2356 // Create an intent to launch the domains activity.
2357 Intent domainsIntent = new Intent(this, DomainsActivity.class);
2359 // Put extra information instructing the domains activity to directly load the current domain and close on back instead of returning to the domains list.
2360 domainsIntent.putExtra("loadDomain", domainSettingsDatabaseId);
2361 domainsIntent.putExtra("closeOnBack", true);
2364 startActivity(domainsIntent);
2365 } else { // Add a new domain.
2366 // Apply the new domain settings on returning to `MainWebViewActivity`.
2367 reapplyDomainSettingsOnRestart = true;
2368 currentDomainName = "";
2370 // Get the current domain
2371 Uri currentUri = Uri.parse(formattedUrlString);
2372 String currentDomain = currentUri.getHost();
2374 // Initialize the database handler. The `0` specifies the database version, but that is ignored and set instead using a constant in `DomainsDatabaseHelper`.
2375 DomainsDatabaseHelper domainsDatabaseHelper = new DomainsDatabaseHelper(this, null, null, 0);
2377 // Create the domain and store the database ID.
2378 int newDomainDatabaseId = domainsDatabaseHelper.addDomain(currentDomain);
2380 // Create an intent to launch the domains activity.
2381 Intent domainsIntent = new Intent(this, DomainsActivity.class);
2383 // Put extra information instructing the domains activity to directly load the new domain and close on back instead of returning to the domains list.
2384 domainsIntent.putExtra("loadDomain", newDomainDatabaseId);
2385 domainsIntent.putExtra("closeOnBack", true);
2388 startActivity(domainsIntent);
2392 case R.id.toggle_first_party_cookies:
2393 // Switch the status of firstPartyCookiesEnabled.
2394 firstPartyCookiesEnabled = !firstPartyCookiesEnabled;
2396 // Update the menu checkbox.
2397 menuItem.setChecked(firstPartyCookiesEnabled);
2399 // Apply the new cookie status.
2400 cookieManager.setAcceptCookie(firstPartyCookiesEnabled);
2402 // Update the privacy icon. `true` runs `invalidateOptionsMenu` as the last step.
2403 updatePrivacyIcons(true);
2405 // Display a `Snackbar`.
2406 if (firstPartyCookiesEnabled) { // First-party cookies are enabled.
2407 Snackbar.make(findViewById(R.id.main_webview), R.string.first_party_cookies_enabled, Snackbar.LENGTH_SHORT).show();
2408 } else if (javaScriptEnabled) { // JavaScript is still enabled.
2409 Snackbar.make(findViewById(R.id.main_webview), R.string.first_party_cookies_disabled, Snackbar.LENGTH_SHORT).show();
2410 } else { // Privacy mode.
2411 Snackbar.make(findViewById(R.id.main_webview), R.string.privacy_mode, Snackbar.LENGTH_SHORT).show();
2414 // Reload the WebView.
2415 mainWebView.reload();
2418 case R.id.toggle_third_party_cookies:
2419 if (Build.VERSION.SDK_INT >= 21) {
2420 // Switch the status of thirdPartyCookiesEnabled.
2421 thirdPartyCookiesEnabled = !thirdPartyCookiesEnabled;
2423 // Update the menu checkbox.
2424 menuItem.setChecked(thirdPartyCookiesEnabled);
2426 // Apply the new cookie status.
2427 cookieManager.setAcceptThirdPartyCookies(mainWebView, thirdPartyCookiesEnabled);
2429 // Display a `Snackbar`.
2430 if (thirdPartyCookiesEnabled) {
2431 Snackbar.make(findViewById(R.id.main_webview), R.string.third_party_cookies_enabled, Snackbar.LENGTH_SHORT).show();
2433 Snackbar.make(findViewById(R.id.main_webview), R.string.third_party_cookies_disabled, Snackbar.LENGTH_SHORT).show();
2436 // Reload the WebView.
2437 mainWebView.reload();
2438 } // Else do nothing because SDK < 21.
2441 case R.id.toggle_dom_storage:
2442 // Switch the status of domStorageEnabled.
2443 domStorageEnabled = !domStorageEnabled;
2445 // Update the menu checkbox.
2446 menuItem.setChecked(domStorageEnabled);
2448 // Apply the new DOM Storage status.
2449 mainWebView.getSettings().setDomStorageEnabled(domStorageEnabled);
2451 // Update the privacy icon. `true` runs `invalidateOptionsMenu` as the last step.
2452 updatePrivacyIcons(true);
2454 // Display a `Snackbar`.
2455 if (domStorageEnabled) {
2456 Snackbar.make(findViewById(R.id.main_webview), R.string.dom_storage_enabled, Snackbar.LENGTH_SHORT).show();
2458 Snackbar.make(findViewById(R.id.main_webview), R.string.dom_storage_disabled, Snackbar.LENGTH_SHORT).show();
2461 // Reload the WebView.
2462 mainWebView.reload();
2465 // Form data can be removed once the minimum API >= 26.
2466 case R.id.toggle_save_form_data:
2467 // Switch the status of saveFormDataEnabled.
2468 saveFormDataEnabled = !saveFormDataEnabled;
2470 // Update the menu checkbox.
2471 menuItem.setChecked(saveFormDataEnabled);
2473 // Apply the new form data status.
2474 mainWebView.getSettings().setSaveFormData(saveFormDataEnabled);
2476 // Display a `Snackbar`.
2477 if (saveFormDataEnabled) {
2478 Snackbar.make(findViewById(R.id.main_webview), R.string.form_data_enabled, Snackbar.LENGTH_SHORT).show();
2480 Snackbar.make(findViewById(R.id.main_webview), R.string.form_data_disabled, Snackbar.LENGTH_SHORT).show();
2483 // Update the privacy icon. `true` runs `invalidateOptionsMenu` as the last step.
2484 updatePrivacyIcons(true);
2486 // Reload the WebView.
2487 mainWebView.reload();
2490 case R.id.clear_cookies:
2491 Snackbar.make(findViewById(R.id.main_webview), R.string.cookies_deleted, Snackbar.LENGTH_LONG)
2492 .setAction(R.string.undo, v -> {
2493 // Do nothing because everything will be handled by `onDismissed()` below.
2495 .addCallback(new Snackbar.Callback() {
2497 public void onDismissed(Snackbar snackbar, int event) {
2499 // The user pushed the `Undo` button.
2500 case Snackbar.Callback.DISMISS_EVENT_ACTION:
2504 // The `Snackbar` was dismissed without the `Undo` button being pushed.
2506 // `cookieManager.removeAllCookie()` varies by SDK.
2507 if (Build.VERSION.SDK_INT < 21) {
2508 cookieManager.removeAllCookie();
2510 // `null` indicates no callback.
2511 cookieManager.removeAllCookies(null);
2519 case R.id.clear_dom_storage:
2520 Snackbar.make(findViewById(R.id.main_webview), R.string.dom_storage_deleted, Snackbar.LENGTH_LONG)
2521 .setAction(R.string.undo, v -> {
2522 // Do nothing because everything will be handled by `onDismissed()` below.
2524 .addCallback(new Snackbar.Callback() {
2526 public void onDismissed(Snackbar snackbar, int event) {
2528 // The user pushed the `Undo` button.
2529 case Snackbar.Callback.DISMISS_EVENT_ACTION:
2533 // The `Snackbar` was dismissed without the `Undo` button being pushed.
2535 // Delete the DOM Storage.
2536 WebStorage webStorage = WebStorage.getInstance();
2537 webStorage.deleteAllData();
2539 // Initialize a handler to manually delete the DOM storage files and directories.
2540 Handler deleteDomStorageHandler = new Handler();
2542 // Setup a runnable to manually delete the DOM storage files and directories.
2543 Runnable deleteDomStorageRunnable = () -> {
2545 // A `String[]` must be used because the directory contains a space and `Runtime.exec` will otherwise not escape the string correctly.
2546 privacyBrowserRuntime.exec(new String[]{"rm", "-rf", privateDataDirectoryString + "/app_webview/Local Storage/"});
2548 // Multiple commands must be used because `Runtime.exec()` does not like `*`.
2549 privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/IndexedDB");
2550 privacyBrowserRuntime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager");
2551 privacyBrowserRuntime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager-journal");
2552 privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/databases");
2553 } catch (IOException e) {
2554 // Do nothing if an error is thrown.
2558 // Manually delete the DOM storage files after 200 milliseconds.
2559 deleteDomStorageHandler.postDelayed(deleteDomStorageRunnable, 200);
2566 // Form data can be remove once the minimum API >= 26.
2567 case R.id.clear_form_data:
2568 Snackbar.make(findViewById(R.id.main_webview), R.string.form_data_deleted, Snackbar.LENGTH_LONG)
2569 .setAction(R.string.undo, v -> {
2570 // Do nothing because everything will be handled by `onDismissed()` below.
2572 .addCallback(new Snackbar.Callback() {
2574 public void onDismissed(Snackbar snackbar, int event) {
2576 // The user pushed the `Undo` button.
2577 case Snackbar.Callback.DISMISS_EVENT_ACTION:
2581 // The `Snackbar` was dismissed without the `Undo` button being pushed.
2583 // Delete the form data.
2584 WebViewDatabase mainWebViewDatabase = WebViewDatabase.getInstance(getApplicationContext());
2585 mainWebViewDatabase.clearFormData();
2593 // Toggle the EasyList status.
2594 easyListEnabled = !easyListEnabled;
2596 // Update the menu checkbox.
2597 menuItem.setChecked(easyListEnabled);
2599 // Reload the main WebView.
2600 mainWebView.reload();
2603 case R.id.easyprivacy:
2604 // Toggle the EasyPrivacy status.
2605 easyPrivacyEnabled = !easyPrivacyEnabled;
2607 // Update the menu checkbox.
2608 menuItem.setChecked(easyPrivacyEnabled);
2610 // Reload the main WebView.
2611 mainWebView.reload();
2614 case R.id.fanboys_annoyance_list:
2615 // Toggle Fanboy's Annoyance List status.
2616 fanboysAnnoyanceListEnabled = !fanboysAnnoyanceListEnabled;
2618 // Update the menu checkbox.
2619 menuItem.setChecked(fanboysAnnoyanceListEnabled);
2621 // Update the staus of Fanboy's Social Blocking List.
2622 MenuItem fanboysSocialBlockingListMenuItem = mainMenu.findItem(R.id.fanboys_social_blocking_list);
2623 fanboysSocialBlockingListMenuItem.setEnabled(!fanboysAnnoyanceListEnabled);
2625 // Reload the main WebView.
2626 mainWebView.reload();
2629 case R.id.fanboys_social_blocking_list:
2630 // Toggle Fanboy's Social Blocking List status.
2631 fanboysSocialBlockingListEnabled = !fanboysSocialBlockingListEnabled;
2633 // Update the menu checkbox.
2634 menuItem.setChecked(fanboysSocialBlockingListEnabled);
2636 // Reload the main WebView.
2637 mainWebView.reload();
2640 case R.id.ultraprivacy:
2641 // Toggle the UltraPrivacy status.
2642 ultraPrivacyEnabled = !ultraPrivacyEnabled;
2644 // Update the menu checkbox.
2645 menuItem.setChecked(ultraPrivacyEnabled);
2647 // Reload the main WebView.
2648 mainWebView.reload();
2651 case R.id.block_all_third_party_requests:
2652 //Toggle the third-party requests blocker status.
2653 blockAllThirdPartyRequests = !blockAllThirdPartyRequests;
2655 // Update the menu checkbox.
2656 menuItem.setChecked(blockAllThirdPartyRequests);
2658 // Reload the main WebView.
2659 mainWebView.reload();
2662 case R.id.user_agent_privacy_browser:
2663 // Update the user agent.
2664 mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[0]);
2666 // Reload the WebView.
2667 mainWebView.reload();
2670 case R.id.user_agent_webview_default:
2671 // Update the user agent.
2672 mainWebView.getSettings().setUserAgentString("");
2674 // Reload the WebView.
2675 mainWebView.reload();
2678 case R.id.user_agent_firefox_on_android:
2679 // Update the user agent.
2680 mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[2]);
2682 // Reload the WebView.
2683 mainWebView.reload();
2686 case R.id.user_agent_chrome_on_android:
2687 // Update the user agent.
2688 mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[3]);
2690 // Reload the WebView.
2691 mainWebView.reload();
2694 case R.id.user_agent_safari_on_ios:
2695 // Update the user agent.
2696 mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[4]);
2698 // Reload the WebView.
2699 mainWebView.reload();
2702 case R.id.user_agent_firefox_on_linux:
2703 // Update the user agent.
2704 mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[5]);
2706 // Reload the WebView.
2707 mainWebView.reload();
2710 case R.id.user_agent_chromium_on_linux:
2711 // Update the user agent.
2712 mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[6]);
2714 // Reload the WebView.
2715 mainWebView.reload();
2718 case R.id.user_agent_firefox_on_windows:
2719 // Update the user agent.
2720 mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[7]);
2722 // Reload the WebView.
2723 mainWebView.reload();
2726 case R.id.user_agent_chrome_on_windows:
2727 // Update the user agent.
2728 mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[8]);
2730 // Reload the WebView.
2731 mainWebView.reload();
2734 case R.id.user_agent_edge_on_windows:
2735 // Update the user agent.
2736 mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[9]);
2738 // Reload the WebView.
2739 mainWebView.reload();
2742 case R.id.user_agent_internet_explorer_on_windows:
2743 // Update the user agent.
2744 mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[10]);
2746 // Reload the WebView.
2747 mainWebView.reload();
2750 case R.id.user_agent_safari_on_macos:
2751 // Update the user agent.
2752 mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[11]);
2754 // Reload the WebView.
2755 mainWebView.reload();
2758 case R.id.user_agent_custom:
2759 // Update the user agent.
2760 mainWebView.getSettings().setUserAgentString(defaultCustomUserAgentString);
2762 // Reload the WebView.
2763 mainWebView.reload();
2766 case R.id.font_size_twenty_five_percent:
2767 mainWebView.getSettings().setTextZoom(25);
2770 case R.id.font_size_fifty_percent:
2771 mainWebView.getSettings().setTextZoom(50);
2774 case R.id.font_size_seventy_five_percent:
2775 mainWebView.getSettings().setTextZoom(75);
2778 case R.id.font_size_one_hundred_percent:
2779 mainWebView.getSettings().setTextZoom(100);
2782 case R.id.font_size_one_hundred_twenty_five_percent:
2783 mainWebView.getSettings().setTextZoom(125);
2786 case R.id.font_size_one_hundred_fifty_percent:
2787 mainWebView.getSettings().setTextZoom(150);
2790 case R.id.font_size_one_hundred_seventy_five_percent:
2791 mainWebView.getSettings().setTextZoom(175);
2794 case R.id.font_size_two_hundred_percent:
2795 mainWebView.getSettings().setTextZoom(200);
2798 case R.id.swipe_to_refresh:
2799 // Toggle swipe to refresh.
2800 swipeRefreshLayout.setEnabled(!swipeRefreshLayout.isEnabled());
2803 case R.id.display_images:
2804 if (mainWebView.getSettings().getLoadsImagesAutomatically()) { // Images are currently loaded automatically.
2805 mainWebView.getSettings().setLoadsImagesAutomatically(false);
2806 mainWebView.reload();
2807 } else { // Images are not currently loaded automatically.
2808 mainWebView.getSettings().setLoadsImagesAutomatically(true);
2812 case R.id.night_mode:
2813 // Toggle night mode.
2814 nightMode = !nightMode;
2816 // Enable or disable JavaScript according to night mode, the global preference, and any domain settings.
2817 if (nightMode) { // Night mode is enabled. Enable JavaScript.
2818 // Update the global variable.
2819 javaScriptEnabled = true;
2820 } else if (domainSettingsApplied) { // Night mode is disabled and domain settings are applied. Set JavaScript according to the domain settings.
2821 // Get the JavaScript preference that was stored the last time domain settings were loaded.
2822 javaScriptEnabled = domainSettingsJavaScriptEnabled;
2823 } else { // Night mode is disabled and domain settings are not applied. Set JavaScript according to the global preference.
2824 // Get a handle for the shared preference.
2825 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
2827 // Get the JavaScript preference.
2828 javaScriptEnabled = sharedPreferences.getBoolean("javascript", false);
2831 // Apply the JavaScript setting to the WebView.
2832 mainWebView.getSettings().setJavaScriptEnabled(javaScriptEnabled);
2834 // Update the privacy icons.
2835 updatePrivacyIcons(false);
2837 // Reload the website.
2838 mainWebView.reload();
2842 // Get a `PrintManager` instance.
2843 PrintManager printManager = (PrintManager) getSystemService(Context.PRINT_SERVICE);
2845 // Convert `mainWebView` to `printDocumentAdapter`.
2846 PrintDocumentAdapter printDocumentAdapter = mainWebView.createPrintDocumentAdapter();
2848 // Remove the lint error below that `printManager` might be `null`.
2849 assert printManager != null;
2851 // Print the document. The print attributes are `null`.
2852 printManager.print(getString(R.string.privacy_browser_web_page), printDocumentAdapter, null);
2855 case R.id.view_source:
2856 // Launch the View Source activity.
2857 Intent viewSourceIntent = new Intent(this, ViewSourceActivity.class);
2858 startActivity(viewSourceIntent);
2861 case R.id.proxy_through_orbot:
2862 // Toggle the proxy through Orbot variable.
2863 proxyThroughOrbot = !proxyThroughOrbot;
2865 // Apply the proxy through Orbot settings.
2866 applyProxyThroughOrbot(true);
2870 // Setup the share string.
2871 String shareString = webViewTitle + " – " + urlTextBox.getText().toString();
2873 // Create the share intent.
2874 Intent shareIntent = new Intent(Intent.ACTION_SEND);
2875 shareIntent.putExtra(Intent.EXTRA_TEXT, shareString);
2876 shareIntent.setType("text/plain");
2879 startActivity(Intent.createChooser(shareIntent, "Share URL"));
2882 case R.id.find_on_page:
2883 // Hide the URL app bar.
2884 supportAppBar.setVisibility(View.GONE);
2886 // Show the Find on Page `RelativeLayout`.
2887 findOnPageLinearLayout.setVisibility(View.VISIBLE);
2889 // Display the keyboard. We have to wait 200 ms before running the command to work around a bug in Android.
2890 // http://stackoverflow.com/questions/5520085/android-show-softkeyboard-with-showsoftinput-is-not-working
2891 findOnPageEditText.postDelayed(() -> {
2892 // Set the focus on `findOnPageEditText`.
2893 findOnPageEditText.requestFocus();
2895 // Display the keyboard. `0` sets no input flags.
2896 inputMethodManager.showSoftInput(findOnPageEditText, 0);
2900 case R.id.add_to_homescreen:
2901 // Show the `CreateHomeScreenShortcutDialog` `AlertDialog` and name this instance `R.string.create_shortcut`.
2902 AppCompatDialogFragment createHomeScreenShortcutDialogFragment = new CreateHomeScreenShortcutDialog();
2903 createHomeScreenShortcutDialogFragment.show(getSupportFragmentManager(), getString(R.string.create_shortcut));
2905 //Everything else will be handled by `CreateHomeScreenShortcutDialog` and the associated listener below.
2909 if (menuItem.getTitle().equals(getString(R.string.refresh))) { // The refresh button was pushed.
2910 // Reload the WebView.
2911 mainWebView.reload();
2912 } else { // The stop button was pushed.
2913 // Stop the loading of the WebView.
2914 mainWebView.stopLoading();
2918 case R.id.ad_consent:
2919 // Display the ad consent dialog.
2920 DialogFragment adConsentDialogFragment = new AdConsentDialog();
2921 adConsentDialogFragment.show(getFragmentManager(), getString(R.string.ad_consent));
2925 // Don't consume the event.
2926 return super.onOptionsItemSelected(menuItem);
2930 // removeAllCookies is deprecated, but it is required for API < 21.
2931 @SuppressWarnings("deprecation")
2933 public boolean onNavigationItemSelected(@NonNull MenuItem menuItem) {
2934 int menuItemId = menuItem.getItemId();
2936 switch (menuItemId) {
2942 if (mainWebView.canGoBack()) {
2943 // Reset the formatted URL string so the page will load correctly if blocking of third-party requests is enabled.
2944 formattedUrlString = "";
2946 // Set `navigatingHistory` so that the domain settings are applied when the new URL is loaded.
2947 navigatingHistory = true;
2949 // Load the previous website in the history.
2950 mainWebView.goBack();
2955 if (mainWebView.canGoForward()) {
2956 // Reset the formatted URL string so the page will load correctly if blocking of third-party requests is enabled.
2957 formattedUrlString = "";
2959 // Set `navigatingHistory` so that the domain settings are applied when the new URL is loaded.
2960 navigatingHistory = true;
2962 // Load the next website in the history.
2963 mainWebView.goForward();
2968 // Get the `WebBackForwardList`.
2969 WebBackForwardList webBackForwardList = mainWebView.copyBackForwardList();
2971 // Show the `UrlHistoryDialog` `AlertDialog` and name this instance `R.string.history`. `this` is the `Context`.
2972 AppCompatDialogFragment urlHistoryDialogFragment = UrlHistoryDialog.loadBackForwardList(this, webBackForwardList);
2973 urlHistoryDialogFragment.show(getSupportFragmentManager(), getString(R.string.history));
2977 // Launch the requests activity.
2978 Intent requestsIntent = new Intent(this, RequestsActivity.class);
2979 startActivity(requestsIntent);
2982 case R.id.downloads:
2983 // Launch the system Download Manager.
2984 Intent downloadManagerIntent = new Intent(DownloadManager.ACTION_VIEW_DOWNLOADS);
2986 // Launch as a new task so that Download Manager and Privacy Browser show as separate windows in the recent tasks list.
2987 downloadManagerIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
2989 startActivity(downloadManagerIntent);
2993 // Set the flag to reapply the domain settings on restart when returning from Domain Settings.
2994 reapplyDomainSettingsOnRestart = true;
2995 currentDomainName = "";
2997 // Launch the domains activity.
2998 Intent domainsIntent = new Intent(this, DomainsActivity.class);
2999 startActivity(domainsIntent);
3003 // Set the flag to reapply app settings on restart when returning from Settings.
3004 reapplyAppSettingsOnRestart = true;
3006 // Set the flag to reapply the domain settings on restart when returning from Settings.
3007 reapplyDomainSettingsOnRestart = true;
3008 currentDomainName = "";
3010 // Launch the settings activity.
3011 Intent settingsIntent = new Intent(this, SettingsActivity.class);
3012 startActivity(settingsIntent);
3015 case R.id.import_export:
3016 // Launch the import/export activity.
3017 Intent importExportIntent = new Intent (this, ImportExportActivity.class);
3018 startActivity(importExportIntent);
3022 // Launch `GuideActivity`.
3023 Intent guideIntent = new Intent(this, GuideActivity.class);
3024 startActivity(guideIntent);
3028 // Launch `AboutActivity`.
3029 Intent aboutIntent = new Intent(this, AboutActivity.class);
3030 startActivity(aboutIntent);
3033 case R.id.clearAndExit:
3034 // Close the bookmarks cursor and database.
3035 bookmarksCursor.close();
3036 bookmarksDatabaseHelper.close();
3038 // Get a handle for `sharedPreferences`. `this` references the current context.
3039 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
3041 boolean clearEverything = sharedPreferences.getBoolean("clear_everything", true);
3044 if (clearEverything || sharedPreferences.getBoolean("clear_cookies", true)) {
3045 // The command to remove cookies changed slightly in API 21.
3046 if (Build.VERSION.SDK_INT >= 21) {
3047 cookieManager.removeAllCookies(null);
3049 cookieManager.removeAllCookie();
3052 // Manually delete the cookies database, as `CookieManager` sometimes will not flush its changes to disk before `System.exit(0)` is run.
3054 // We have to use two commands because `Runtime.exec()` does not like `*`.
3055 privacyBrowserRuntime.exec("rm -f " + privateDataDirectoryString + "/app_webview/Cookies");
3056 privacyBrowserRuntime.exec("rm -f " + privateDataDirectoryString + "/app_webview/Cookies-journal");
3057 } catch (IOException e) {
3058 // Do nothing if an error is thrown.
3062 // Clear DOM storage.
3063 if (clearEverything || sharedPreferences.getBoolean("clear_dom_storage", true)) {
3064 // Ask `WebStorage` to clear the DOM storage.
3065 WebStorage webStorage = WebStorage.getInstance();
3066 webStorage.deleteAllData();
3068 // Manually delete the DOM storage files and directories, as `WebStorage` sometimes will not flush its changes to disk before `System.exit(0)` is run.
3070 // A `String[]` must be used because the directory contains a space and `Runtime.exec` will otherwise not escape the string correctly.
3071 privacyBrowserRuntime.exec(new String[] {"rm", "-rf", privateDataDirectoryString + "/app_webview/Local Storage/"});
3073 // Multiple commands must be used because `Runtime.exec()` does not like `*`.
3074 privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/IndexedDB");
3075 privacyBrowserRuntime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager");
3076 privacyBrowserRuntime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager-journal");
3077 privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/databases");
3078 } catch (IOException e) {
3079 // Do nothing if an error is thrown.
3083 // Clear form data if the API < 26.
3084 if ((Build.VERSION.SDK_INT < 26) && (clearEverything || sharedPreferences.getBoolean("clear_form_data", true))) {
3085 WebViewDatabase webViewDatabase = WebViewDatabase.getInstance(this);
3086 webViewDatabase.clearFormData();
3088 // Manually delete the form data database, as `WebViewDatabase` sometimes will not flush its changes to disk before `System.exit(0)` is run.
3090 // We have to use a `String[]` because the database contains a space and `Runtime.exec` will not escape the string correctly otherwise.
3091 privacyBrowserRuntime.exec(new String[] {"rm", "-f", privateDataDirectoryString + "/app_webview/Web Data"});
3092 privacyBrowserRuntime.exec(new String[] {"rm", "-f", privateDataDirectoryString + "/app_webview/Web Data-journal"});
3093 } catch (IOException e) {
3094 // Do nothing if an error is thrown.
3099 if (clearEverything || sharedPreferences.getBoolean("clear_cache", true)) {
3100 // `true` includes disk files.
3101 mainWebView.clearCache(true);
3103 // Manually delete the cache directories.
3105 // Delete the main cache directory.
3106 privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/cache");
3108 // Delete the secondary `Service Worker` cache directory.
3109 // A `String[]` must be used because the directory contains a space and `Runtime.exec` will not escape the string correctly otherwise.
3110 privacyBrowserRuntime.exec(new String[] {"rm", "-rf", privateDataDirectoryString + "/app_webview/Service Worker/"});
3111 } catch (IOException e) {
3112 // Do nothing if an error is thrown.
3116 // Clear SSL certificate preferences.
3117 mainWebView.clearSslPreferences();
3119 // Clear the back/forward history.
3120 mainWebView.clearHistory();
3122 // Clear `formattedUrlString`.
3123 formattedUrlString = null;
3125 // Clear `customHeaders`.
3126 customHeaders.clear();
3128 // Detach all views from `mainWebViewRelativeLayout`.
3129 mainWebViewRelativeLayout.removeAllViews();
3131 // Destroy the internal state of `mainWebView`.
3132 mainWebView.destroy();
3134 // Manually delete the `app_webview` folder, which contains the cookies, DOM storage, form data, and `Service Worker` cache.
3135 // See `https://code.google.com/p/android/issues/detail?id=233826&thanks=233826&ts=1486670530`.
3136 if (clearEverything) {
3138 privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/app_webview");
3139 } catch (IOException e) {
3140 // Do nothing if an error is thrown.
3144 // Close Privacy Browser. `finishAndRemoveTask` also removes Privacy Browser from the recent app list.
3145 if (Build.VERSION.SDK_INT >= 21) {
3146 finishAndRemoveTask();
3151 // Remove the terminated program from RAM. The status code is `0`.
3156 // Close the navigation drawer.
3157 drawerLayout.closeDrawer(GravityCompat.START);
3162 public void onPostCreate(Bundle savedInstanceState) {
3163 super.onPostCreate(savedInstanceState);
3165 // Sync the state of the DrawerToggle after onRestoreInstanceState has finished.
3166 drawerToggle.syncState();
3170 public void onConfigurationChanged(Configuration newConfig) {
3171 super.onConfigurationChanged(newConfig);
3173 // Reload the ad for the free flavor if we not in full screen mode.
3174 if (BuildConfig.FLAVOR.contentEquals("free") && !inFullScreenBrowsingMode) {
3175 // Reload the ad. The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations.
3176 AdHelper.loadAd(findViewById(R.id.adview), getApplicationContext(), getString(R.string.ad_unit_id));
3179 // `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:
3180 // https://code.google.com/p/android/issues/detail?id=20493#c8
3181 // ActivityCompat.invalidateOptionsMenu(this);
3185 public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.ContextMenuInfo menuInfo) {
3186 // Store the `HitTestResult`.
3187 final WebView.HitTestResult hitTestResult = mainWebView.getHitTestResult();
3190 final String imageUrl;
3191 final String linkUrl;
3193 // Get a handle for the `ClipboardManager`.
3194 final ClipboardManager clipboardManager = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
3196 // Remove the lint errors below that `clipboardManager` might be `null`.
3197 assert clipboardManager != null;
3199 switch (hitTestResult.getType()) {
3200 // `SRC_ANCHOR_TYPE` is a link.
3201 case WebView.HitTestResult.SRC_ANCHOR_TYPE:
3202 // Get the target URL.
3203 linkUrl = hitTestResult.getExtra();
3205 // Set the target URL as the title of the `ContextMenu`.
3206 menu.setHeaderTitle(linkUrl);
3208 // Add a Load URL entry.
3209 menu.add(R.string.load_url).setOnMenuItemClickListener((MenuItem item) -> {
3214 // Add a Copy URL entry.
3215 menu.add(R.string.copy_url).setOnMenuItemClickListener((MenuItem item) -> {
3216 // Save the link URL in a `ClipData`.
3217 ClipData srcAnchorTypeClipData = ClipData.newPlainText(getString(R.string.url), linkUrl);
3219 // Set the `ClipData` as the clipboard's primary clip.
3220 clipboardManager.setPrimaryClip(srcAnchorTypeClipData);
3224 // Add a Download URL entry.
3225 menu.add(R.string.download_url).setOnMenuItemClickListener((MenuItem item) -> {
3226 // Check if the download should be processed by an external app.
3227 if (downloadWithExternalApp) { // Download with an external app.
3228 openUrlWithExternalApp(linkUrl);
3229 } else { // Download with Android's download manager.
3230 // Check to see if the storage permission has already been granted.
3231 if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) { // The storage permission needs to be requested.
3232 // Store the variables for future use by `onRequestPermissionsResult()`.
3233 downloadUrl = linkUrl;
3234 downloadContentDisposition = "none";
3235 downloadContentLength = -1;
3237 // Show a dialog if the user has previously denied the permission.
3238 if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) { // Show a dialog explaining the request first.
3239 // Instantiate the download location permission alert dialog and set the download type to DOWNLOAD_FILE.
3240 DialogFragment downloadLocationPermissionDialogFragment = DownloadLocationPermissionDialog.downloadType(DownloadLocationPermissionDialog.DOWNLOAD_FILE);
3242 // Show the download location permission alert dialog. The permission will be requested when the the dialog is closed.
3243 downloadLocationPermissionDialogFragment.show(getFragmentManager(), getString(R.string.download_location));
3244 } else { // Show the permission request directly.
3245 // Request the permission. The download dialog will be launched by `onRequestPermissionResult()`.
3246 ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_FILE_REQUEST_CODE);
3248 } else { // The storage permission has already been granted.
3249 // Get a handle for the download file alert dialog.
3250 AppCompatDialogFragment downloadFileDialogFragment = DownloadFileDialog.fromUrl(linkUrl, "none", -1);
3252 // Show the download file alert dialog.
3253 downloadFileDialogFragment.show(getSupportFragmentManager(), getString(R.string.download));
3259 // Add a `Cancel` entry, which by default closes the `ContextMenu`.
3260 menu.add(R.string.cancel);
3263 case WebView.HitTestResult.EMAIL_TYPE:
3264 // Get the target URL.
3265 linkUrl = hitTestResult.getExtra();
3267 // Set the target URL as the title of the `ContextMenu`.
3268 menu.setHeaderTitle(linkUrl);
3270 // Add a `Write Email` entry.
3271 menu.add(R.string.write_email).setOnMenuItemClickListener(item -> {
3272 // Use `ACTION_SENDTO` instead of `ACTION_SEND` so that only email programs are launched.
3273 Intent emailIntent = new Intent(Intent.ACTION_SENDTO);
3275 // Parse the url and set it as the data for the `Intent`.
3276 emailIntent.setData(Uri.parse("mailto:" + linkUrl));
3278 // `FLAG_ACTIVITY_NEW_TASK` opens the email program in a new task instead as part of Privacy Browser.
3279 emailIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
3282 startActivity(emailIntent);
3286 // Add a `Copy Email Address` entry.
3287 menu.add(R.string.copy_email_address).setOnMenuItemClickListener(item -> {
3288 // Save the email address in a `ClipData`.
3289 ClipData srcEmailTypeClipData = ClipData.newPlainText(getString(R.string.email_address), linkUrl);
3291 // Set the `ClipData` as the clipboard's primary clip.
3292 clipboardManager.setPrimaryClip(srcEmailTypeClipData);
3296 // Add a `Cancel` entry, which by default closes the `ContextMenu`.
3297 menu.add(R.string.cancel);
3300 // `IMAGE_TYPE` is an image.
3301 case WebView.HitTestResult.IMAGE_TYPE:
3302 // Get the image URL.
3303 imageUrl = hitTestResult.getExtra();
3305 // Set the image URL as the title of the `ContextMenu`.
3306 menu.setHeaderTitle(imageUrl);
3308 // Add a `View Image` entry.
3309 menu.add(R.string.view_image).setOnMenuItemClickListener(item -> {
3314 // Add a `Download Image` entry.
3315 menu.add(R.string.download_image).setOnMenuItemClickListener((MenuItem item) -> {
3316 // Check if the download should be processed by an external app.
3317 if (downloadWithExternalApp) { // Download with an external app.
3318 openUrlWithExternalApp(imageUrl);
3319 } else { // Download with Android's download manager.
3320 // Check to see if the storage permission has already been granted.
3321 if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) { // The storage permission needs to be requested.
3322 // Store the image URL for use by `onRequestPermissionResult()`.
3323 downloadImageUrl = imageUrl;
3325 // Show a dialog if the user has previously denied the permission.
3326 if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) { // Show a dialog explaining the request first.
3327 // Instantiate the download location permission alert dialog and set the download type to DOWNLOAD_IMAGE.
3328 DialogFragment downloadLocationPermissionDialogFragment = DownloadLocationPermissionDialog.downloadType(DownloadLocationPermissionDialog.DOWNLOAD_IMAGE);
3330 // Show the download location permission alert dialog. The permission will be requested when the dialog is closed.
3331 downloadLocationPermissionDialogFragment.show(getFragmentManager(), getString(R.string.download_location));
3332 } else { // Show the permission request directly.
3333 // Request the permission. The download dialog will be launched by `onRequestPermissionResult().
3334 ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_IMAGE_REQUEST_CODE);
3336 } else { // The storage permission has already been granted.
3337 // Get a handle for the download image alert dialog.
3338 AppCompatDialogFragment downloadImageDialogFragment = DownloadImageDialog.imageUrl(imageUrl);
3340 // Show the download image alert dialog.
3341 downloadImageDialogFragment.show(getSupportFragmentManager(), getString(R.string.download));
3347 // Add a `Copy URL` entry.
3348 menu.add(R.string.copy_url).setOnMenuItemClickListener(item -> {
3349 // Save the image URL in a `ClipData`.
3350 ClipData srcImageTypeClipData = ClipData.newPlainText(getString(R.string.url), imageUrl);
3352 // Set the `ClipData` as the clipboard's primary clip.
3353 clipboardManager.setPrimaryClip(srcImageTypeClipData);
3357 // Add a `Cancel` entry, which by default closes the `ContextMenu`.
3358 menu.add(R.string.cancel);
3362 // `SRC_IMAGE_ANCHOR_TYPE` is an image that is also a link.
3363 case WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE:
3364 // Get the image URL.
3365 imageUrl = hitTestResult.getExtra();
3367 // Set the image URL as the title of the `ContextMenu`.
3368 menu.setHeaderTitle(imageUrl);
3370 // Add a `View Image` entry.
3371 menu.add(R.string.view_image).setOnMenuItemClickListener(item -> {
3376 // Add a `Download Image` entry.
3377 menu.add(R.string.download_image).setOnMenuItemClickListener((MenuItem item) -> {
3378 // Check if the download should be processed by an external app.
3379 if (downloadWithExternalApp) { // Download with an external app.
3380 openUrlWithExternalApp(imageUrl);
3381 } else { // Download with Android's download manager.
3382 // Check to see if the storage permission has already been granted.
3383 if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) { // The storage permission needs to be requested.
3384 // Store the image URL for use by `onRequestPermissionResult()`.
3385 downloadImageUrl = imageUrl;
3387 // Show a dialog if the user has previously denied the permission.
3388 if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) { // Show a dialog explaining the request first.
3389 // Instantiate the download location permission alert dialog and set the download type to DOWNLOAD_IMAGE.
3390 DialogFragment downloadLocationPermissionDialogFragment = DownloadLocationPermissionDialog.downloadType(DownloadLocationPermissionDialog.DOWNLOAD_IMAGE);
3392 // Show the download location permission alert dialog. The permission will be requested when the dialog is closed.
3393 downloadLocationPermissionDialogFragment.show(getFragmentManager(), getString(R.string.download_location));
3394 } else { // Show the permission request directly.
3395 // Request the permission. The download dialog will be launched by `onRequestPermissionResult().
3396 ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_IMAGE_REQUEST_CODE);
3398 } else { // The storage permission has already been granted.
3399 // Get a handle for the download image alert dialog.
3400 AppCompatDialogFragment downloadImageDialogFragment = DownloadImageDialog.imageUrl(imageUrl);
3402 // Show the download image alert dialog.
3403 downloadImageDialogFragment.show(getSupportFragmentManager(), getString(R.string.download));
3409 // Add a `Copy URL` entry.
3410 menu.add(R.string.copy_url).setOnMenuItemClickListener(item -> {
3411 // Save the image URL in a `ClipData`.
3412 ClipData srcImageAnchorTypeClipData = ClipData.newPlainText(getString(R.string.url), imageUrl);
3414 // Set the `ClipData` as the clipboard's primary clip.
3415 clipboardManager.setPrimaryClip(srcImageAnchorTypeClipData);
3419 // Add a `Cancel` entry, which by default closes the `ContextMenu`.
3420 menu.add(R.string.cancel);
3426 public void onCreateBookmark(AppCompatDialogFragment dialogFragment) {
3427 // Get the `EditTexts` from the `dialogFragment`.
3428 EditText createBookmarkNameEditText = dialogFragment.getDialog().findViewById(R.id.create_bookmark_name_edittext);
3429 EditText createBookmarkUrlEditText = dialogFragment.getDialog().findViewById(R.id.create_bookmark_url_edittext);
3431 // Extract the strings from the `EditTexts`.
3432 String bookmarkNameString = createBookmarkNameEditText.getText().toString();
3433 String bookmarkUrlString = createBookmarkUrlEditText.getText().toString();
3435 // Convert the favoriteIcon Bitmap to a byte array. `0` is for lossless compression (the only option for a PNG).
3436 ByteArrayOutputStream favoriteIconByteArrayOutputStream = new ByteArrayOutputStream();
3437 favoriteIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, favoriteIconByteArrayOutputStream);
3438 byte[] favoriteIconByteArray = favoriteIconByteArrayOutputStream.toByteArray();
3440 // Display the new bookmark below the current items in the (0 indexed) list.
3441 int newBookmarkDisplayOrder = bookmarksListView.getCount();
3443 // Create the bookmark.
3444 bookmarksDatabaseHelper.createBookmark(bookmarkNameString, bookmarkUrlString, currentBookmarksFolder, newBookmarkDisplayOrder, favoriteIconByteArray);
3446 // Update `bookmarksCursor` with the current contents of this folder.
3447 bookmarksCursor = bookmarksDatabaseHelper.getAllBookmarksCursorByDisplayOrder(currentBookmarksFolder);
3449 // Update the `ListView`.
3450 bookmarksCursorAdapter.changeCursor(bookmarksCursor);
3452 // Scroll to the new bookmark.
3453 bookmarksListView.setSelection(newBookmarkDisplayOrder);
3457 public void onCreateBookmarkFolder(AppCompatDialogFragment dialogFragment) {
3458 // Get handles for the views in `dialogFragment`.
3459 EditText createFolderNameEditText = dialogFragment.getDialog().findViewById(R.id.create_folder_name_edittext);
3460 RadioButton defaultFolderIconRadioButton = dialogFragment.getDialog().findViewById(R.id.create_folder_default_icon_radiobutton);
3461 ImageView folderIconImageView = dialogFragment.getDialog().findViewById(R.id.create_folder_default_icon);
3463 // Get new folder name string.
3464 String folderNameString = createFolderNameEditText.getText().toString();
3466 // Get the new folder icon `Bitmap`.
3467 Bitmap folderIconBitmap;
3468 if (defaultFolderIconRadioButton.isChecked()) { // Use the default folder icon.
3469 // Get the default folder icon and convert it to a `Bitmap`.
3470 Drawable folderIconDrawable = folderIconImageView.getDrawable();
3471 BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable;
3472 folderIconBitmap = folderIconBitmapDrawable.getBitmap();
3473 } else { // Use the `WebView` favorite icon.
3474 folderIconBitmap = favoriteIconBitmap;
3477 // Convert `folderIconBitmap` to a byte array. `0` is for lossless compression (the only option for a PNG).
3478 ByteArrayOutputStream folderIconByteArrayOutputStream = new ByteArrayOutputStream();
3479 folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, folderIconByteArrayOutputStream);
3480 byte[] folderIconByteArray = folderIconByteArrayOutputStream.toByteArray();
3482 // Move all the bookmarks down one in the display order.
3483 for (int i = 0; i < bookmarksListView.getCount(); i++) {
3484 int databaseId = (int) bookmarksListView.getItemIdAtPosition(i);
3485 bookmarksDatabaseHelper.updateDisplayOrder(databaseId, i + 1);
3488 // Create the folder, which will be placed at the top of the `ListView`.
3489 bookmarksDatabaseHelper.createFolder(folderNameString, currentBookmarksFolder, folderIconByteArray);
3491 // Update `bookmarksCursor` with the current contents of this folder.
3492 bookmarksCursor = bookmarksDatabaseHelper.getAllBookmarksCursorByDisplayOrder(currentBookmarksFolder);
3494 // Update the `ListView`.
3495 bookmarksCursorAdapter.changeCursor(bookmarksCursor);
3497 // Scroll to the new folder.
3498 bookmarksListView.setSelection(0);
3502 public void onCreateHomeScreenShortcut(AppCompatDialogFragment dialogFragment) {
3503 // Get the shortcut name.
3504 EditText shortcutNameEditText = dialogFragment.getDialog().findViewById(R.id.shortcut_name_edittext);
3505 String shortcutNameString = shortcutNameEditText.getText().toString();
3507 // Convert the favorite icon bitmap to an `Icon`. `IconCompat` is required until API >= 26.
3508 IconCompat favoriteIcon = IconCompat.createWithBitmap(favoriteIconBitmap);
3510 // Setup the shortcut intent.
3511 Intent shortcutIntent = new Intent(Intent.ACTION_VIEW);
3512 shortcutIntent.setData(Uri.parse(formattedUrlString));
3514 // Create a shortcut info builder. The shortcut name becomes the shortcut ID.
3515 ShortcutInfoCompat.Builder shortcutInfoBuilder = new ShortcutInfoCompat.Builder(this, shortcutNameString);
3517 // Add the required fields to the shortcut info builder.
3518 shortcutInfoBuilder.setIcon(favoriteIcon);
3519 shortcutInfoBuilder.setIntent(shortcutIntent);
3520 shortcutInfoBuilder.setShortLabel(shortcutNameString);
3522 // Request the pin. `ShortcutManagerCompat` can be switched to `ShortcutManager` once API >= 26.
3523 ShortcutManagerCompat.requestPinShortcut(this, shortcutInfoBuilder.build(), null);
3527 public void onCloseDownloadLocationPermissionDialog(int downloadType) {
3528 switch (downloadType) {
3529 case DownloadLocationPermissionDialog.DOWNLOAD_FILE:
3530 // Request the WRITE_EXTERNAL_STORAGE permission with a file request code.
3531 ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_FILE_REQUEST_CODE);
3534 case DownloadLocationPermissionDialog.DOWNLOAD_IMAGE:
3535 // Request the WRITE_EXTERNAL_STORAGE permission with an image request code.
3536 ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_IMAGE_REQUEST_CODE);
3542 public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
3543 switch (requestCode) {
3544 case DOWNLOAD_FILE_REQUEST_CODE:
3545 // Show the download file alert dialog. When the dialog closes, the correct command will be used based on the permission status.
3546 AppCompatDialogFragment downloadFileDialogFragment = DownloadFileDialog.fromUrl(downloadUrl, downloadContentDisposition, downloadContentLength);
3548 // On API 23, displaying the fragment must be delayed or the app will crash.
3549 if (Build.VERSION.SDK_INT == 23) {
3550 new Handler().postDelayed(() -> downloadFileDialogFragment.show(getSupportFragmentManager(), getString(R.string.download)), 500);
3552 downloadFileDialogFragment.show(getSupportFragmentManager(), getString(R.string.download));
3555 // Reset the download variables.
3557 downloadContentDisposition = "";
3558 downloadContentLength = 0;
3561 case DOWNLOAD_IMAGE_REQUEST_CODE:
3562 // Show the download image alert dialog. When the dialog closes, the correct command will be used based on the permission status.
3563 AppCompatDialogFragment downloadImageDialogFragment = DownloadImageDialog.imageUrl(downloadImageUrl);
3565 // On API 23, displaying the fragment must be delayed or the app will crash.
3566 if (Build.VERSION.SDK_INT == 23) {
3567 new Handler().postDelayed(() -> downloadImageDialogFragment.show(getSupportFragmentManager(), getString(R.string.download)), 500);
3569 downloadImageDialogFragment.show(getSupportFragmentManager(), getString(R.string.download));
3572 // Reset the image URL variable.
3573 downloadImageUrl = "";
3579 public void onDownloadImage(AppCompatDialogFragment dialogFragment, String imageUrl) {
3580 // Download the image if it has an HTTP or HTTPS URI.
3581 if (imageUrl.startsWith("http")) {
3582 // Get a handle for the system `DOWNLOAD_SERVICE`.
3583 DownloadManager downloadManager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
3585 // Parse `imageUrl`.
3586 DownloadManager.Request downloadRequest = new DownloadManager.Request(Uri.parse(imageUrl));
3588 // Pass cookies to download manager if cookies are enabled. This is required to download images from websites that require a login.
3589 // Code contributed 2017 Hendrik Knackstedt. Copyright assigned to Soren Stoutner <soren@stoutner.com>.
3590 if (firstPartyCookiesEnabled) {
3591 // Get the cookies for `imageUrl`.
3592 String cookies = cookieManager.getCookie(imageUrl);
3594 // Add the cookies to `downloadRequest`. In the HTTP request header, cookies are named `Cookie`.
3595 downloadRequest.addRequestHeader("Cookie", cookies);
3598 // Get the file name from the dialog fragment.
3599 EditText downloadImageNameEditText = dialogFragment.getDialog().findViewById(R.id.download_image_name);
3600 String imageName = downloadImageNameEditText.getText().toString();
3602 // Specify the download location.
3603 if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { // External write permission granted.
3604 // Download to the public download directory.
3605 downloadRequest.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, imageName);
3606 } else { // External write permission denied.
3607 // Download to the app's external download directory.
3608 downloadRequest.setDestinationInExternalFilesDir(this, Environment.DIRECTORY_DOWNLOADS, imageName);
3611 // Allow `MediaScanner` to index the download if it is a media file.
3612 downloadRequest.allowScanningByMediaScanner();
3614 // Add the URL as the description for the download.
3615 downloadRequest.setDescription(imageUrl);
3617 // Show the download notification after the download is completed.
3618 downloadRequest.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
3620 // Remove the lint warning below that `downloadManager` might be `null`.
3621 assert downloadManager != null;
3623 // Initiate the download.
3624 downloadManager.enqueue(downloadRequest);
3625 } else { // The image is not an HTTP or HTTPS URI.
3626 Snackbar.make(mainWebView, R.string.cannot_download_image, Snackbar.LENGTH_INDEFINITE).show();
3631 public void onDownloadFile(AppCompatDialogFragment dialogFragment, String downloadUrl) {
3632 // Download the file if it has an HTTP or HTTPS URI.
3633 if (downloadUrl.startsWith("http")) {
3634 // Get a handle for the system `DOWNLOAD_SERVICE`.
3635 DownloadManager downloadManager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
3637 // Parse `downloadUrl`.
3638 DownloadManager.Request downloadRequest = new DownloadManager.Request(Uri.parse(downloadUrl));
3640 // Pass cookies to download manager if cookies are enabled. This is required to download files from websites that require a login.
3641 // Code contributed 2017 Hendrik Knackstedt. Copyright assigned to Soren Stoutner <soren@stoutner.com>.
3642 if (firstPartyCookiesEnabled) {
3643 // Get the cookies for `downloadUrl`.
3644 String cookies = cookieManager.getCookie(downloadUrl);
3646 // Add the cookies to `downloadRequest`. In the HTTP request header, cookies are named `Cookie`.
3647 downloadRequest.addRequestHeader("Cookie", cookies);
3650 // Get the file name from the dialog fragment.
3651 EditText downloadFileNameEditText = dialogFragment.getDialog().findViewById(R.id.download_file_name);
3652 String fileName = downloadFileNameEditText.getText().toString();
3654 // Specify the download location.
3655 if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { // External write permission granted.
3656 // Download to the public download directory.
3657 downloadRequest.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, fileName);
3658 } else { // External write permission denied.
3659 // Download to the app's external download directory.
3660 downloadRequest.setDestinationInExternalFilesDir(this, Environment.DIRECTORY_DOWNLOADS, fileName);
3663 // Allow `MediaScanner` to index the download if it is a media file.
3664 downloadRequest.allowScanningByMediaScanner();
3666 // Add the URL as the description for the download.
3667 downloadRequest.setDescription(downloadUrl);
3669 // Show the download notification after the download is completed.
3670 downloadRequest.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
3672 // Remove the lint warning below that `downloadManager` might be `null`.
3673 assert downloadManager != null;
3675 // Initiate the download.
3676 downloadManager.enqueue(downloadRequest);
3677 } else { // The download is not an HTTP or HTTPS URI.
3678 Snackbar.make(mainWebView, R.string.cannot_download_file, Snackbar.LENGTH_INDEFINITE).show();
3683 public void onSaveBookmark(AppCompatDialogFragment dialogFragment, int selectedBookmarkDatabaseId) {
3684 // Get handles for the views from `dialogFragment`.
3685 EditText editBookmarkNameEditText = dialogFragment.getDialog().findViewById(R.id.edit_bookmark_name_edittext);
3686 EditText editBookmarkUrlEditText = dialogFragment.getDialog().findViewById(R.id.edit_bookmark_url_edittext);
3687 RadioButton currentBookmarkIconRadioButton = dialogFragment.getDialog().findViewById(R.id.edit_bookmark_current_icon_radiobutton);
3689 // Store the bookmark strings.
3690 String bookmarkNameString = editBookmarkNameEditText.getText().toString();
3691 String bookmarkUrlString = editBookmarkUrlEditText.getText().toString();
3693 // Update the bookmark.
3694 if (currentBookmarkIconRadioButton.isChecked()) { // Update the bookmark without changing the favorite icon.
3695 bookmarksDatabaseHelper.updateBookmark(selectedBookmarkDatabaseId, bookmarkNameString, bookmarkUrlString);
3696 } else { // Update the bookmark using the `WebView` favorite icon.
3697 // Convert the favorite icon to a byte array. `0` is for lossless compression (the only option for a PNG).
3698 ByteArrayOutputStream newFavoriteIconByteArrayOutputStream = new ByteArrayOutputStream();
3699 favoriteIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, newFavoriteIconByteArrayOutputStream);
3700 byte[] newFavoriteIconByteArray = newFavoriteIconByteArrayOutputStream.toByteArray();
3702 // Update the bookmark and the favorite icon.
3703 bookmarksDatabaseHelper.updateBookmark(selectedBookmarkDatabaseId, bookmarkNameString, bookmarkUrlString, newFavoriteIconByteArray);
3706 // Update `bookmarksCursor` with the current contents of this folder.
3707 bookmarksCursor = bookmarksDatabaseHelper.getAllBookmarksCursorByDisplayOrder(currentBookmarksFolder);
3709 // Update the `ListView`.
3710 bookmarksCursorAdapter.changeCursor(bookmarksCursor);
3714 public void onSaveBookmarkFolder(AppCompatDialogFragment dialogFragment, int selectedFolderDatabaseId) {
3715 // Get handles for the views from `dialogFragment`.
3716 EditText editFolderNameEditText = dialogFragment.getDialog().findViewById(R.id.edit_folder_name_edittext);
3717 RadioButton currentFolderIconRadioButton = dialogFragment.getDialog().findViewById(R.id.edit_folder_current_icon_radiobutton);
3718 RadioButton defaultFolderIconRadioButton = dialogFragment.getDialog().findViewById(R.id.edit_folder_default_icon_radiobutton);
3719 ImageView folderIconImageView = dialogFragment.getDialog().findViewById(R.id.edit_folder_default_icon_imageview);
3721 // Get the new folder name.
3722 String newFolderNameString = editFolderNameEditText.getText().toString();
3724 // Check if the favorite icon has changed.
3725 if (currentFolderIconRadioButton.isChecked()) { // Only the name has changed.
3726 // Update the name in the database.
3727 bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, oldFolderNameString, newFolderNameString);
3728 } else if (!currentFolderIconRadioButton.isChecked() && newFolderNameString.equals(oldFolderNameString)) { // Only the icon has changed.
3729 // Get the new folder icon `Bitmap`.
3730 Bitmap folderIconBitmap;
3731 if (defaultFolderIconRadioButton.isChecked()) {
3732 // Get the default folder icon and convert it to a `Bitmap`.
3733 Drawable folderIconDrawable = folderIconImageView.getDrawable();
3734 BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable;
3735 folderIconBitmap = folderIconBitmapDrawable.getBitmap();
3736 } else { // Use the `WebView` favorite icon.
3737 folderIconBitmap = favoriteIconBitmap;
3740 // Convert the folder `Bitmap` to a byte array. `0` is for lossless compression (the only option for a PNG).
3741 ByteArrayOutputStream folderIconByteArrayOutputStream = new ByteArrayOutputStream();
3742 folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, folderIconByteArrayOutputStream);
3743 byte[] folderIconByteArray = folderIconByteArrayOutputStream.toByteArray();
3745 // Update the folder icon in the database.
3746 bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, folderIconByteArray);
3747 } else { // The folder icon and the name have changed.
3748 // Get the new folder icon `Bitmap`.
3749 Bitmap folderIconBitmap;
3750 if (defaultFolderIconRadioButton.isChecked()) {
3751 // Get the default folder icon and convert it to a `Bitmap`.
3752 Drawable folderIconDrawable = folderIconImageView.getDrawable();
3753 BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable;
3754 folderIconBitmap = folderIconBitmapDrawable.getBitmap();
3755 } else { // Use the `WebView` favorite icon.
3756 folderIconBitmap = MainWebViewActivity.favoriteIconBitmap;
3759 // Convert the folder `Bitmap` to a byte array. `0` is for lossless compression (the only option for a PNG).
3760 ByteArrayOutputStream folderIconByteArrayOutputStream = new ByteArrayOutputStream();
3761 folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, folderIconByteArrayOutputStream);
3762 byte[] folderIconByteArray = folderIconByteArrayOutputStream.toByteArray();
3764 // Update the folder name and icon in the database.
3765 bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, oldFolderNameString, newFolderNameString, folderIconByteArray);
3768 // Update `bookmarksCursor` with the current contents of this folder.
3769 bookmarksCursor = bookmarksDatabaseHelper.getAllBookmarksCursorByDisplayOrder(currentBookmarksFolder);
3771 // Update the `ListView`.
3772 bookmarksCursorAdapter.changeCursor(bookmarksCursor);
3776 public void onHttpAuthenticationCancel() {
3777 // Cancel the `HttpAuthHandler`.
3778 httpAuthHandler.cancel();
3782 public void onHttpAuthenticationProceed(AppCompatDialogFragment dialogFragment) {
3783 // Get handles for the `EditTexts`.
3784 EditText usernameEditText = dialogFragment.getDialog().findViewById(R.id.http_authentication_username);
3785 EditText passwordEditText = dialogFragment.getDialog().findViewById(R.id.http_authentication_password);
3787 // Proceed with the HTTP authentication.
3788 httpAuthHandler.proceed(usernameEditText.getText().toString(), passwordEditText.getText().toString());
3791 public void viewSslCertificate(View view) {
3792 // Show the `ViewSslCertificateDialog` `AlertDialog` and name this instance `@string/view_ssl_certificate`.
3793 DialogFragment viewSslCertificateDialogFragment = new ViewSslCertificateDialog();
3794 viewSslCertificateDialogFragment.show(getFragmentManager(), getString(R.string.view_ssl_certificate));
3798 public void onSslErrorCancel() {
3799 sslErrorHandler.cancel();
3803 public void onSslErrorProceed() {
3804 sslErrorHandler.proceed();
3808 public void onSslMismatchBack() {
3809 if (mainWebView.canGoBack()) { // There is a back page in the history.
3810 // Reset the formatted URL string so the page will load correctly if blocking of third-party requests is enabled.
3811 formattedUrlString = "";
3813 // Set `navigatingHistory` so that the domain settings are applied when the new URL is loaded.
3814 navigatingHistory = true;
3817 mainWebView.goBack();
3818 } else { // There are no pages to go back to.
3819 // Load a blank page
3825 public void onSslMismatchProceed() {
3826 // Do not check the pinned SSL certificate for this domain again until the domain changes.
3827 ignorePinnedSslCertificate = true;
3831 public void onUrlHistoryEntrySelected(int moveBackOrForwardSteps) {
3832 // Reset the formatted URL string so the page will load correctly if blocking of third-party requests is enabled.
3833 formattedUrlString = "";
3835 // Set `navigatingHistory` so that the domain settings are applied when the new URL is loaded.
3836 navigatingHistory = true;
3838 // Load the history entry.
3839 mainWebView.goBackOrForward(moveBackOrForwardSteps);
3843 public void onClearHistory() {
3844 // Clear the history.
3845 mainWebView.clearHistory();
3848 // Override `onBackPressed` to handle the navigation drawer and `mainWebView`.
3850 public void onBackPressed() {
3851 if (drawerLayout.isDrawerVisible(GravityCompat.START)) { // The navigation drawer is open.
3852 // Close the navigation drawer.
3853 drawerLayout.closeDrawer(GravityCompat.START);
3854 } else if (drawerLayout.isDrawerVisible(GravityCompat.END)){ // The bookmarks drawer is open.
3855 if (currentBookmarksFolder.isEmpty()) { // The home folder is displayed.
3856 // close the bookmarks drawer.
3857 drawerLayout.closeDrawer(GravityCompat.END);
3858 } else { // A subfolder is displayed.
3859 // Place the former parent folder in `currentFolder`.
3860 currentBookmarksFolder = bookmarksDatabaseHelper.getParentFolder(currentBookmarksFolder);
3862 // Load the new folder.
3863 loadBookmarksFolder();
3866 } else if (mainWebView.canGoBack()) { // There is at least one item in the `WebView` history.
3867 // Reset the formatted URL string so the page will load correctly if blocking of third-party requests is enabled.
3868 formattedUrlString = "";
3870 // Set `navigatingHistory` so that the domain settings are applied when the new URL is loaded.
3871 navigatingHistory = true;
3874 mainWebView.goBack();
3875 } else { // There isn't anything to do in Privacy Browser.
3876 // Pass `onBackPressed()` to the system.
3877 super.onBackPressed();
3881 // 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.
3883 public void onActivityResult(int requestCode, int resultCode, Intent data) {
3884 // File uploads only work on API >= 21.
3885 if (Build.VERSION.SDK_INT >= 21) {
3886 // Pass the file to the WebView.
3887 fileChooserCallback.onReceiveValue(WebChromeClient.FileChooserParams.parseResult(resultCode, data));
3891 private void loadUrlFromTextBox() {
3892 // Get the text from urlTextBox and convert it to a string. trim() removes white spaces from the beginning and end of the string.
3893 String unformattedUrlString = urlTextBox.getText().toString().trim();
3895 // Check to see if `unformattedUrlString` is a valid URL. Otherwise, convert it into a search.
3896 if (unformattedUrlString.startsWith("content://")) {
3897 // Load the entire content URL.
3898 formattedUrlString = unformattedUrlString;
3899 } else if (Patterns.WEB_URL.matcher(unformattedUrlString).matches() || unformattedUrlString.startsWith("http://") || unformattedUrlString.startsWith("https://")
3900 || unformattedUrlString.startsWith("file://")) {
3901 // Add `https://` at the beginning if there is no protocol. Otherwise the app will segfault.
3902 if (!unformattedUrlString.startsWith("http") && !unformattedUrlString.startsWith("file://") && !unformattedUrlString.startsWith("content://")) {
3903 unformattedUrlString = "https://" + unformattedUrlString;
3906 // Initialize `unformattedUrl`.
3907 URL unformattedUrl = null;
3909 // 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.
3911 unformattedUrl = new URL(unformattedUrlString);
3912 } catch (MalformedURLException e) {
3913 e.printStackTrace();
3916 // The ternary operator (? :) makes sure that a null pointer exception is not thrown, which would happen if `.get` was called on a `null` value.
3917 String scheme = unformattedUrl != null ? unformattedUrl.getProtocol() : null;
3918 String authority = unformattedUrl != null ? unformattedUrl.getAuthority() : null;
3919 String path = unformattedUrl != null ? unformattedUrl.getPath() : null;
3920 String query = unformattedUrl != null ? unformattedUrl.getQuery() : null;
3921 String fragment = unformattedUrl != null ? unformattedUrl.getRef() : null;
3924 Uri.Builder formattedUri = new Uri.Builder();
3925 formattedUri.scheme(scheme).authority(authority).path(path).query(query).fragment(fragment);
3927 // Decode `formattedUri` as a `String` in `UTF-8`.
3929 formattedUrlString = URLDecoder.decode(formattedUri.build().toString(), "UTF-8");
3930 } catch (UnsupportedEncodingException exception) {
3931 // Load a blank string.
3932 formattedUrlString = "";
3934 } else if (unformattedUrlString.isEmpty()){ // Load a blank web site.
3935 // Load a blank string.
3936 formattedUrlString = "";
3937 } else { // Search for the contents of the URL box.
3938 // Create an encoded URL String.
3939 String encodedUrlString;
3941 // Sanitize the search input.
3943 encodedUrlString = URLEncoder.encode(unformattedUrlString, "UTF-8");
3944 } catch (UnsupportedEncodingException exception) {
3945 encodedUrlString = "";
3948 // Add the base search URL.
3949 formattedUrlString = searchURL + encodedUrlString;
3952 // 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.
3953 urlTextBox.clearFocus();
3956 loadUrl(formattedUrlString);
3959 private void loadUrl(String url) {// Apply any custom domain settings.
3960 // Set the URL as the formatted URL string so that checking third-party requests works correctly.
3961 formattedUrlString = url;
3963 // Apply the domain settings.
3964 applyDomainSettings(url, true, false);
3966 // If loading a website, set `urlIsLoading` to prevent changes in the user agent on websites with redirects from reloading the current website.
3967 urlIsLoading = !url.equals("");
3970 mainWebView.loadUrl(url, customHeaders);
3973 public void findPreviousOnPage(View view) {
3974 // Go to the previous highlighted phrase on the page. `false` goes backwards instead of forwards.
3975 mainWebView.findNext(false);
3978 public void findNextOnPage(View view) {
3979 // Go to the next highlighted phrase on the page. `true` goes forwards instead of backwards.
3980 mainWebView.findNext(true);
3983 public void closeFindOnPage(View view) {
3984 // Delete the contents of `find_on_page_edittext`.
3985 findOnPageEditText.setText(null);
3987 // Clear the highlighted phrases.
3988 mainWebView.clearMatches();
3990 // Hide the Find on Page `RelativeLayout`.
3991 findOnPageLinearLayout.setVisibility(View.GONE);
3993 // Show the URL app bar.
3994 supportAppBar.setVisibility(View.VISIBLE);
3996 // Hide the keyboard so we can see the webpage. `0` indicates no additional flags.
3997 inputMethodManager.hideSoftInputFromWindow(mainWebView.getWindowToken(), 0);
4000 private void applyAppSettings() {
4001 // Get a handle for the shared preferences.
4002 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
4004 // Store the values from the shared preferences in variables.
4005 incognitoModeEnabled = sharedPreferences.getBoolean("incognito_mode", false);
4006 boolean doNotTrackEnabled = sharedPreferences.getBoolean("do_not_track", false);
4007 proxyThroughOrbot = sharedPreferences.getBoolean("proxy_through_orbot", false);
4008 fullScreenBrowsingModeEnabled = sharedPreferences.getBoolean("full_screen_browsing_mode", false);
4009 hideSystemBarsOnFullscreen = sharedPreferences.getBoolean("hide_system_bars", false);
4010 translucentNavigationBarOnFullscreen = sharedPreferences.getBoolean("translucent_navigation_bar", true);
4011 downloadWithExternalApp = sharedPreferences.getBoolean("download_with_external_app", false);
4013 // Apply the proxy through Orbot settings.
4014 applyProxyThroughOrbot(false);
4016 // Set Do Not Track status.
4017 if (doNotTrackEnabled) {
4018 customHeaders.put("DNT", "1");
4020 customHeaders.remove("DNT");
4023 // Apply the appropriate full screen mode the `SYSTEM_UI` flags.
4024 if (fullScreenBrowsingModeEnabled && inFullScreenBrowsingMode) { // Privacy Browser is currently in full screen browsing mode.
4025 if (hideSystemBarsOnFullscreen) { // Hide everything.
4026 // Remove the translucent navigation setting if it is currently flagged.
4027 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
4029 // Remove the translucent status bar overlay.
4030 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
4032 // Remove the translucent status bar overlay on the `Drawer Layout`, which is special and needs its own command.
4033 drawerLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
4035 /* SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
4036 * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
4037 * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
4039 rootCoordinatorLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
4040 } else { // Hide everything except the status and navigation bars.
4041 // Remove any `SYSTEM_UI` flags from `rootCoordinatorLayout`.
4042 rootCoordinatorLayout.setSystemUiVisibility(0);
4044 // Add the translucent status flag if it is unset.
4045 getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
4047 if (translucentNavigationBarOnFullscreen) {
4048 // Set the navigation bar to be translucent.
4049 getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
4051 // Set the navigation bar to be black.
4052 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
4055 } else { // Privacy Browser is not in full screen browsing mode.
4056 // 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.
4057 inFullScreenBrowsingMode = false;
4059 // Show the `appBar` if `findOnPageLinearLayout` is not visible.
4060 if (findOnPageLinearLayout.getVisibility() == View.GONE) {
4064 // Show the `BannerAd` in the free flavor.
4065 if (BuildConfig.FLAVOR.contentEquals("free")) {
4066 // Initialize the ad. The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations.
4067 AdHelper.initializeAds(findViewById(R.id.adview), getApplicationContext(), getFragmentManager(), getString(R.string.google_app_id), getString(R.string.ad_unit_id));
4070 // Remove any `SYSTEM_UI` flags from `rootCoordinatorLayout`.
4071 rootCoordinatorLayout.setSystemUiVisibility(0);
4073 // Remove the translucent navigation bar flag if it is set.
4074 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
4076 // Add the translucent status flag if it is unset. This also resets `drawerLayout's` `View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN`.
4077 getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
4079 // Constrain `rootCoordinatorLayout` inside the status and navigation bars.
4080 rootCoordinatorLayout.setFitsSystemWindows(true);
4084 // `reloadWebsite` is used if returning from the Domains activity. Otherwise JavaScript might not function correctly if it is newly enabled.
4085 // The deprecated `.getDrawable()` must be used until the minimum API >= 21.
4086 @SuppressWarnings("deprecation")
4087 private boolean applyDomainSettings(String url, boolean resetFavoriteIcon, boolean reloadWebsite) {
4088 // Get the current user agent.
4089 String initialUserAgent = mainWebView.getSettings().getUserAgentString();
4091 // Initialize a variable to track if the user agent changes.
4092 boolean userAgentChanged = false;
4094 // Parse the URL into a URI.
4095 Uri uri = Uri.parse(url);
4097 // Extract the domain from `uri`.
4098 String hostName = uri.getHost();
4100 // Initialize `loadingNewDomainName`.
4101 boolean loadingNewDomainName;
4103 // If either `hostName` or `currentDomainName` are `null`, run the options for loading a new domain name.
4104 // The lint suggestion to simplify the `if` statement is incorrect, because `hostName.equals(currentDomainName)` can produce a `null object reference.`
4105 //noinspection SimplifiableIfStatement
4106 if ((hostName == null) || (currentDomainName == null)) {
4107 loadingNewDomainName = true;
4108 } else { // Determine if `hostName` equals `currentDomainName`.
4109 loadingNewDomainName = !hostName.equals(currentDomainName);
4112 // Strings don't like to be null.
4113 if (hostName == null) {
4117 // 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.
4118 if (loadingNewDomainName) {
4119 // Set the new `hostname` as the `currentDomainName`.
4120 currentDomainName = hostName;
4122 // Reset `ignorePinnedSslCertificate`.
4123 ignorePinnedSslCertificate = false;
4125 // Reset the favorite icon if specified.
4126 if (resetFavoriteIcon) {
4127 favoriteIconBitmap = favoriteIconDefaultBitmap;
4128 favoriteIconImageView.setImageBitmap(Bitmap.createScaledBitmap(favoriteIconBitmap, 64, 64, true));
4131 // Initialize the database handler. The `0` specifies the database version, but that is ignored and set instead using a constant in `DomainsDatabaseHelper`.
4132 DomainsDatabaseHelper domainsDatabaseHelper = new DomainsDatabaseHelper(this, null, null, 0);
4134 // Get a full cursor from `domainsDatabaseHelper`.
4135 Cursor domainNameCursor = domainsDatabaseHelper.getDomainNameCursorOrderedByDomain();
4137 // Initialize `domainSettingsSet`.
4138 Set<String> domainSettingsSet = new HashSet<>();
4140 // Get the domain name column index.
4141 int domainNameColumnIndex = domainNameCursor.getColumnIndex(DomainsDatabaseHelper.DOMAIN_NAME);
4143 // Populate `domainSettingsSet`.
4144 for (int i = 0; i < domainNameCursor.getCount(); i++) {
4145 // Move `domainsCursor` to the current row.
4146 domainNameCursor.moveToPosition(i);
4148 // Store the domain name in `domainSettingsSet`.
4149 domainSettingsSet.add(domainNameCursor.getString(domainNameColumnIndex));
4152 // Close `domainNameCursor.
4153 domainNameCursor.close();
4155 // Initialize variables to track if domain settings will be applied and, if so, under which name.
4156 domainSettingsApplied = false;
4157 String domainNameInDatabase = null;
4159 // Check the hostname.
4160 if (domainSettingsSet.contains(hostName)) {
4161 domainSettingsApplied = true;
4162 domainNameInDatabase = hostName;
4165 // Check all the subdomains of the host name against wildcard domains in the domain cursor.
4166 while (!domainSettingsApplied && hostName.contains(".")) { // Stop checking if domain settings are already applied or there are no more `.` in the host name.
4167 if (domainSettingsSet.contains("*." + hostName)) { // Check the host name prepended by `*.`.
4168 // Apply the domain settings.
4169 domainSettingsApplied = true;
4171 // Store the applied domain names as it appears in the database.
4172 domainNameInDatabase = "*." + hostName;
4175 // Strip out the lowest subdomain of of the host name.
4176 hostName = hostName.substring(hostName.indexOf(".") + 1);
4180 // Get a handle for the shared preference.
4181 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
4183 // Store the general preference information.
4184 String defaultFontSizeString = sharedPreferences.getString("font_size", getString(R.string.font_size_default_value));
4185 String defaultUserAgentName = sharedPreferences.getString("user_agent", getString(R.string.user_agent_default_value));
4186 defaultCustomUserAgentString = sharedPreferences.getString("custom_user_agent", getString(R.string.custom_user_agent_default_value));
4187 boolean defaultSwipeToRefresh = sharedPreferences.getBoolean("swipe_to_refresh", true);
4188 nightMode = sharedPreferences.getBoolean("night_mode", false);
4189 boolean displayWebpageImages = sharedPreferences.getBoolean("display_webpage_images", true);
4191 if (domainSettingsApplied) { // The url has custom domain settings.
4192 // Get a cursor for the current host and move it to the first position.
4193 Cursor currentHostDomainSettingsCursor = domainsDatabaseHelper.getCursorForDomainName(domainNameInDatabase);
4194 currentHostDomainSettingsCursor.moveToFirst();
4196 // Get the settings from the cursor.
4197 domainSettingsDatabaseId = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper._ID)));
4198 javaScriptEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_JAVASCRIPT)) == 1);
4199 firstPartyCookiesEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FIRST_PARTY_COOKIES)) == 1);
4200 thirdPartyCookiesEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_THIRD_PARTY_COOKIES)) == 1);
4201 domStorageEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_DOM_STORAGE)) == 1);
4202 // Form data can be removed once the minimum API >= 26.
4203 saveFormDataEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FORM_DATA)) == 1);
4204 easyListEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_EASYLIST)) == 1);
4205 easyPrivacyEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_EASYPRIVACY)) == 1);
4206 fanboysAnnoyanceListEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FANBOYS_ANNOYANCE_LIST)) == 1);
4207 fanboysSocialBlockingListEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FANBOYS_SOCIAL_BLOCKING_LIST)) == 1);
4208 ultraPrivacyEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_ULTRAPRIVACY)) == 1);
4209 blockAllThirdPartyRequests = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.BLOCK_ALL_THIRD_PARTY_REQUESTS)) == 1);
4210 String userAgentName = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.USER_AGENT));
4211 int fontSize = currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.FONT_SIZE));
4212 int swipeToRefreshInt = currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SWIPE_TO_REFRESH));
4213 int nightModeInt = currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.NIGHT_MODE));
4214 int displayWebpageImagesInt = currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.DISPLAY_IMAGES));
4215 pinnedDomainSslCertificate = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.PINNED_SSL_CERTIFICATE)) == 1);
4216 pinnedDomainSslIssuedToCNameString = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_TO_COMMON_NAME));
4217 pinnedDomainSslIssuedToONameString = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_TO_ORGANIZATION));
4218 pinnedDomainSslIssuedToUNameString = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_TO_ORGANIZATIONAL_UNIT));
4219 pinnedDomainSslIssuedByCNameString = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_BY_COMMON_NAME));
4220 pinnedDomainSslIssuedByONameString = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_BY_ORGANIZATION));
4221 pinnedDomainSslIssuedByUNameString = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_BY_ORGANIZATIONAL_UNIT));
4223 // Set `nightMode` according to `nightModeInt`. If `nightModeInt` is `DomainsDatabaseHelper.NIGHT_MODE_SYSTEM_DEFAULT` the current setting from `sharedPreferences` will be used.
4224 switch (nightModeInt) {
4225 case DomainsDatabaseHelper.NIGHT_MODE_ENABLED:
4229 case DomainsDatabaseHelper.NIGHT_MODE_DISABLED:
4234 // Store the domain JavaScript status. This is used by the options menu night mode toggle.
4235 domainSettingsJavaScriptEnabled = javaScriptEnabled;
4237 // Enable JavaScript if night mode is enabled.
4239 javaScriptEnabled = true;
4242 // Set the pinned SSL certificate start date to `null` if the saved date `long` is 0.
4243 if (currentHostDomainSettingsCursor.getLong(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_START_DATE)) == 0) {
4244 pinnedDomainSslStartDate = null;
4246 pinnedDomainSslStartDate = new Date(currentHostDomainSettingsCursor.getLong(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_START_DATE)));
4249 // Set the pinned SSL certificate end date to `null` if the saved date `long` is 0.
4250 if (currentHostDomainSettingsCursor.getLong(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_END_DATE)) == 0) {
4251 pinnedDomainSslEndDate = null;
4253 pinnedDomainSslEndDate = new Date(currentHostDomainSettingsCursor.getLong(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_END_DATE)));
4256 // Close `currentHostDomainSettingsCursor`.
4257 currentHostDomainSettingsCursor.close();
4259 // Apply the domain settings.
4260 mainWebView.getSettings().setJavaScriptEnabled(javaScriptEnabled);
4261 cookieManager.setAcceptCookie(firstPartyCookiesEnabled);
4262 mainWebView.getSettings().setDomStorageEnabled(domStorageEnabled);
4264 // Apply the form data setting if the API < 26.
4265 if (Build.VERSION.SDK_INT < 26) {
4266 mainWebView.getSettings().setSaveFormData(saveFormDataEnabled);
4269 // Apply the font size.
4270 if (fontSize == 0) { // Apply the default font size.
4271 mainWebView.getSettings().setTextZoom(Integer.valueOf(defaultFontSizeString));
4272 } else { // Apply the specified font size.
4273 mainWebView.getSettings().setTextZoom(fontSize);
4276 // Set third-party cookies status if API >= 21.
4277 if (Build.VERSION.SDK_INT >= 21) {
4278 cookieManager.setAcceptThirdPartyCookies(mainWebView, thirdPartyCookiesEnabled);
4281 // 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.
4282 // <https://redmine.stoutner.com/issues/160>
4283 if (!urlIsLoading) {
4284 // Set the user agent.
4285 if (userAgentName.equals(getString(R.string.system_default_user_agent))) { // Use the system default user agent.
4286 // Get the array position of the default user agent name.
4287 int defaultUserAgentArrayPosition = userAgentNamesArray.getPosition(defaultUserAgentName);
4289 // Set the user agent according to the system default.
4290 switch (defaultUserAgentArrayPosition) {
4291 case UNRECOGNIZED_USER_AGENT: // The default user agent name is not on the canonical list.
4292 // This is probably because it was set in an older version of Privacy Browser before the switch to persistent user agent names.
4293 mainWebView.getSettings().setUserAgentString(defaultUserAgentName);
4296 case SETTINGS_WEBVIEW_DEFAULT_USER_AGENT:
4297 // Set the user agent to `""`, which uses the default value.
4298 mainWebView.getSettings().setUserAgentString("");
4301 case SETTINGS_CUSTOM_USER_AGENT:
4302 // Set the custom user agent.
4303 mainWebView.getSettings().setUserAgentString(defaultCustomUserAgentString);
4307 // Get the user agent string from the user agent data array
4308 mainWebView.getSettings().setUserAgentString(userAgentDataArray[defaultUserAgentArrayPosition]);
4310 } else { // Set the user agent according to the stored name.
4311 // Get the array position of the user agent name.
4312 int userAgentArrayPosition = userAgentNamesArray.getPosition(userAgentName);
4314 switch (userAgentArrayPosition) {
4315 case UNRECOGNIZED_USER_AGENT: // The user agent name contains a custom user agent.
4316 mainWebView.getSettings().setUserAgentString(userAgentName);
4319 case SETTINGS_WEBVIEW_DEFAULT_USER_AGENT:
4320 // Set the user agent to `""`, which uses the default value.
4321 mainWebView.getSettings().setUserAgentString("");
4325 // Get the user agent string from the user agent data array.
4326 mainWebView.getSettings().setUserAgentString(userAgentDataArray[userAgentArrayPosition]);
4330 // Store the applied user agent string, which is used in the View Source activity.
4331 appliedUserAgentString = mainWebView.getSettings().getUserAgentString();
4333 // Update the user agent change tracker.
4334 userAgentChanged = !appliedUserAgentString.equals(initialUserAgent);
4337 // Set swipe to refresh.
4338 switch (swipeToRefreshInt) {
4339 case DomainsDatabaseHelper.SWIPE_TO_REFRESH_SYSTEM_DEFAULT:
4340 // Set swipe to refresh according to the default.
4341 swipeRefreshLayout.setEnabled(defaultSwipeToRefresh);
4344 case DomainsDatabaseHelper.SWIPE_TO_REFRESH_ENABLED:
4345 // Enable swipe to refresh.
4346 swipeRefreshLayout.setEnabled(true);
4349 case DomainsDatabaseHelper.SWIPE_TO_REFRESH_DISABLED:
4350 // Disable swipe to refresh.
4351 swipeRefreshLayout.setEnabled(false);
4354 // Set the loading of webpage images.
4355 switch (displayWebpageImagesInt) {
4356 case DomainsDatabaseHelper.DISPLAY_WEBPAGE_IMAGES_SYSTEM_DEFAULT:
4357 mainWebView.getSettings().setLoadsImagesAutomatically(displayWebpageImages);
4360 case DomainsDatabaseHelper.DISPLAY_WEBPAGE_IMAGES_ENABLED:
4361 mainWebView.getSettings().setLoadsImagesAutomatically(true);
4364 case DomainsDatabaseHelper.DISPLAY_WEBPAGE_IMAGES_DISABLED:
4365 mainWebView.getSettings().setLoadsImagesAutomatically(false);
4369 // 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.
4371 urlAppBarRelativeLayout.setBackground(getResources().getDrawable(R.drawable.url_bar_background_dark_blue));
4373 urlAppBarRelativeLayout.setBackground(getResources().getDrawable(R.drawable.url_bar_background_light_green));
4375 } else { // The new URL does not have custom domain settings. Load the defaults.
4376 // Store the values from `sharedPreferences` in variables.
4377 javaScriptEnabled = sharedPreferences.getBoolean("javascript", false);
4378 firstPartyCookiesEnabled = sharedPreferences.getBoolean("first_party_cookies", false);
4379 thirdPartyCookiesEnabled = sharedPreferences.getBoolean("third_party_cookies", false);
4380 domStorageEnabled = sharedPreferences.getBoolean("dom_storage", false);
4381 saveFormDataEnabled = sharedPreferences.getBoolean("save_form_data", false); // Form data can be removed once the minimum API >= 26.
4382 easyListEnabled = sharedPreferences.getBoolean("easylist", true);
4383 easyPrivacyEnabled = sharedPreferences.getBoolean("easyprivacy", true);
4384 fanboysAnnoyanceListEnabled = sharedPreferences.getBoolean("fanboys_annoyance_list", true);
4385 fanboysSocialBlockingListEnabled = sharedPreferences.getBoolean("fanboys_social_blocking_list", true);
4386 ultraPrivacyEnabled = sharedPreferences.getBoolean("ultraprivacy", true);
4387 blockAllThirdPartyRequests = sharedPreferences.getBoolean("block_all_third_party_requests", false);
4389 // Set `javaScriptEnabled` to be `true` if `night_mode` is `true`.
4391 javaScriptEnabled = true;
4394 // Apply the default settings.
4395 mainWebView.getSettings().setJavaScriptEnabled(javaScriptEnabled);
4396 cookieManager.setAcceptCookie(firstPartyCookiesEnabled);
4397 mainWebView.getSettings().setDomStorageEnabled(domStorageEnabled);
4398 mainWebView.getSettings().setTextZoom(Integer.valueOf(defaultFontSizeString));
4399 swipeRefreshLayout.setEnabled(defaultSwipeToRefresh);
4401 // Apply the form data setting if the API < 26.
4402 if (Build.VERSION.SDK_INT < 26) {
4403 mainWebView.getSettings().setSaveFormData(saveFormDataEnabled);
4406 // Reset the pinned SSL certificate information.
4407 domainSettingsDatabaseId = -1;
4408 pinnedDomainSslCertificate = false;
4409 pinnedDomainSslIssuedToCNameString = "";
4410 pinnedDomainSslIssuedToONameString = "";
4411 pinnedDomainSslIssuedToUNameString = "";
4412 pinnedDomainSslIssuedByCNameString = "";
4413 pinnedDomainSslIssuedByONameString = "";
4414 pinnedDomainSslIssuedByUNameString = "";
4415 pinnedDomainSslStartDate = null;
4416 pinnedDomainSslEndDate = null;
4418 // Set third-party cookies status if API >= 21.
4419 if (Build.VERSION.SDK_INT >= 21) {
4420 cookieManager.setAcceptThirdPartyCookies(mainWebView, thirdPartyCookiesEnabled);
4423 // 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.
4424 // <https://redmine.stoutner.com/issues/160>
4425 if (!urlIsLoading) {
4426 // Get the array position of the user agent name.
4427 int userAgentArrayPosition = userAgentNamesArray.getPosition(defaultUserAgentName);
4429 // Set the user agent.
4430 switch (userAgentArrayPosition) {
4431 case UNRECOGNIZED_USER_AGENT: // The default user agent name is not on the canonical list.
4432 // This is probably because it was set in an older version of Privacy Browser before the switch to persistent user agent names.
4433 mainWebView.getSettings().setUserAgentString(defaultUserAgentName);
4436 case SETTINGS_WEBVIEW_DEFAULT_USER_AGENT:
4437 // Set the user agent to `""`, which uses the default value.
4438 mainWebView.getSettings().setUserAgentString("");
4441 case SETTINGS_CUSTOM_USER_AGENT:
4442 // Set the custom user agent.
4443 mainWebView.getSettings().setUserAgentString(defaultCustomUserAgentString);
4447 // Get the user agent string from the user agent data array
4448 mainWebView.getSettings().setUserAgentString(userAgentDataArray[userAgentArrayPosition]);
4451 // Store the applied user agent string, which is used in the View Source activity.
4452 appliedUserAgentString = mainWebView.getSettings().getUserAgentString();
4454 // Update the user agent change tracker.
4455 userAgentChanged = !appliedUserAgentString.equals(initialUserAgent);
4458 // Set the loading of webpage images.
4459 mainWebView.getSettings().setLoadsImagesAutomatically(displayWebpageImages);
4461 // Set a transparent background on `urlTextBox`. We have to use the deprecated `.getDrawable()` until the minimum API >= 21.
4462 urlAppBarRelativeLayout.setBackgroundDrawable(getResources().getDrawable(R.color.transparent));
4465 // Close the domains database helper.
4466 domainsDatabaseHelper.close();
4468 // Update the privacy icons, but only if `mainMenu` has already been populated.
4469 if (mainMenu != null) {
4470 updatePrivacyIcons(true);
4474 // Reload the website if returning from the Domains activity.
4475 if (reloadWebsite) {
4476 mainWebView.reload();
4479 // Return the user agent changed status.
4480 return userAgentChanged;
4483 private void applyProxyThroughOrbot(boolean reloadWebsite) {
4484 // Get a handle for the shared preferences.
4485 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
4487 // Get the search preferences.
4488 String homepageString = sharedPreferences.getString("homepage", getString(R.string.homepage_default_value));
4489 String torHomepageString = sharedPreferences.getString("tor_homepage", getString(R.string.tor_homepage_default_value));
4490 String torSearchString = sharedPreferences.getString("tor_search", getString(R.string.tor_search_default_value));
4491 String torSearchCustomUrlString = sharedPreferences.getString("tor_search_custom_url", getString(R.string.tor_search_custom_url_default_value));
4492 String searchString = sharedPreferences.getString("search", getString(R.string.search_default_value));
4493 String searchCustomUrlString = sharedPreferences.getString("search_custom_url", getString(R.string.search_custom_url_default_value));
4495 // Set the homepage, search, and proxy options.
4496 if (proxyThroughOrbot) { // Set the Tor options.
4497 // Set `torHomepageString` as `homepage`.
4498 homepage = torHomepageString;
4500 // If formattedUrlString is null assign the homepage to it.
4501 if (formattedUrlString == null) {
4502 formattedUrlString = homepage;
4505 // Set the search URL.
4506 if (torSearchString.equals("Custom URL")) { // Get the custom URL string.
4507 searchURL = torSearchCustomUrlString;
4508 } else { // Use the string from the pre-built list.
4509 searchURL = torSearchString;
4512 // Set the proxy. `this` refers to the current activity where an `AlertDialog` might be displayed.
4513 OrbotProxyHelper.setProxy(getApplicationContext(), this, "localhost", "8118");
4515 // Set the `appBar` background to indicate proxying through Orbot is enabled. `this` refers to the context.
4517 appBar.setBackgroundDrawable(ContextCompat.getDrawable(this, R.color.dark_blue_30));
4519 appBar.setBackgroundDrawable(ContextCompat.getDrawable(this, R.color.blue_50));
4522 // Check to see if Orbot is ready.
4523 if (!orbotStatus.equals("ON")) { // Orbot is not ready.
4524 // Set `waitingForOrbot`.
4525 waitingForOrbot = true;
4527 // Disable the wide view port so that the waiting for Orbot text is displayed correctly.
4528 mainWebView.getSettings().setUseWideViewPort(false);
4530 // Load a waiting page. `null` specifies no encoding, which defaults to ASCII.
4531 mainWebView.loadData(waitingForOrbotHtmlString, "text/html", null);
4532 } else if (reloadWebsite) { // Orbot is ready and the website should be reloaded.
4533 // Reload the website.
4534 mainWebView.reload();
4536 } else { // Set the non-Tor options.
4537 // Set `homepageString` as `homepage`.
4538 homepage = homepageString;
4540 // If formattedUrlString is null assign the homepage to it.
4541 if (formattedUrlString == null) {
4542 formattedUrlString = homepage;
4545 // Set the search URL.
4546 if (searchString.equals("Custom URL")) { // Get the custom URL string.
4547 searchURL = searchCustomUrlString;
4548 } else { // Use the string from the pre-built list.
4549 searchURL = searchString;
4552 // Reset the proxy to default. The host is `""` and the port is `"0"`.
4553 OrbotProxyHelper.setProxy(getApplicationContext(), this, "", "0");
4555 // Set the default `appBar` background. `this` refers to the context.
4557 appBar.setBackgroundDrawable(ContextCompat.getDrawable(this, R.color.gray_900));
4559 appBar.setBackgroundDrawable(ContextCompat.getDrawable(this, R.color.gray_100));
4562 // Reset `waitingForOrbot.
4563 waitingForOrbot = false;
4565 // Reload the website if requested.
4566 if (reloadWebsite) {
4567 mainWebView.reload();
4572 private void updatePrivacyIcons(boolean runInvalidateOptionsMenu) {
4573 // Get handles for the menu items.
4574 MenuItem privacyMenuItem = mainMenu.findItem(R.id.toggle_javascript);
4575 MenuItem firstPartyCookiesMenuItem = mainMenu.findItem(R.id.toggle_first_party_cookies);
4576 MenuItem domStorageMenuItem = mainMenu.findItem(R.id.toggle_dom_storage);
4577 MenuItem refreshMenuItem = mainMenu.findItem(R.id.refresh);
4579 // Update the privacy icon.
4580 if (javaScriptEnabled) { // JavaScript is enabled.
4581 privacyMenuItem.setIcon(R.drawable.javascript_enabled);
4582 } else if (firstPartyCookiesEnabled) { // JavaScript is disabled but cookies are enabled.
4583 privacyMenuItem.setIcon(R.drawable.warning);
4584 } else { // All the dangerous features are disabled.
4585 privacyMenuItem.setIcon(R.drawable.privacy_mode);
4588 // Update the first-party cookies icon.
4589 if (firstPartyCookiesEnabled) { // First-party cookies are enabled.
4590 firstPartyCookiesMenuItem.setIcon(R.drawable.cookies_enabled);
4591 } else { // First-party cookies are disabled.
4593 firstPartyCookiesMenuItem.setIcon(R.drawable.cookies_disabled_dark);
4595 firstPartyCookiesMenuItem.setIcon(R.drawable.cookies_disabled_light);
4599 // Update the DOM storage icon.
4600 if (javaScriptEnabled && domStorageEnabled) { // Both JavaScript and DOM storage are enabled.
4601 domStorageMenuItem.setIcon(R.drawable.dom_storage_enabled);
4602 } else if (javaScriptEnabled) { // JavaScript is enabled but DOM storage is disabled.
4604 domStorageMenuItem.setIcon(R.drawable.dom_storage_disabled_dark);
4606 domStorageMenuItem.setIcon(R.drawable.dom_storage_disabled_light);
4608 } else { // JavaScript is disabled, so DOM storage is ghosted.
4610 domStorageMenuItem.setIcon(R.drawable.dom_storage_ghosted_dark);
4612 domStorageMenuItem.setIcon(R.drawable.dom_storage_ghosted_light);
4616 // Update the refresh icon.
4618 refreshMenuItem.setIcon(R.drawable.refresh_enabled_dark);
4620 refreshMenuItem.setIcon(R.drawable.refresh_enabled_light);
4623 // `invalidateOptionsMenu` calls `onPrepareOptionsMenu()` and redraws the icons in the `AppBar`.
4624 if (runInvalidateOptionsMenu) {
4625 invalidateOptionsMenu();
4629 private void openUrlWithExternalApp(String url) {
4630 // Create a download intent. Not specifying the action type will display the maximum number of options.
4631 Intent downloadIntent = new Intent();
4633 // Set the URI and the mime type. `"*/*"` will display the maximum number of options.
4634 downloadIntent.setDataAndType(Uri.parse(url), "text/html");
4636 // Flag the intent to open in a new task.
4637 downloadIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
4639 // Show the chooser.
4640 startActivity(Intent.createChooser(downloadIntent, getString(R.string.open_with)));
4643 private void highlightUrlText() {
4644 // Get the URL string.
4645 String urlString = urlTextBox.getText().toString();
4647 // Highlight the URL according to the protocol.
4648 if (urlString.startsWith("file://")) { // This is a file URL.
4649 // De-emphasize only the protocol.
4650 urlTextBox.getText().setSpan(initialGrayColorSpan, 0, 7, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
4651 } else if (urlString.startsWith("content://")) {
4652 // De-emphasize only the protocol.
4653 urlTextBox.getText().setSpan(initialGrayColorSpan, 0, 10, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
4654 } else { // This is a web URL.
4655 // Get the index of the `/` immediately after the domain name.
4656 int endOfDomainName = urlString.indexOf("/", (urlString.indexOf("//") + 2));
4658 // Create a base URL string.
4661 // Get the base URL.
4662 if (endOfDomainName > 0) { // There is at least one character after the base URL.
4663 // Get the base URL.
4664 baseUrl = urlString.substring(0, endOfDomainName);
4665 } else { // There are no characters after the base URL.
4666 // Set the base URL to be the entire URL string.
4667 baseUrl = urlString;
4670 // Get the index of the last `.` in the domain.
4671 int lastDotIndex = baseUrl.lastIndexOf(".");
4673 // Get the index of the penultimate `.` in the domain.
4674 int penultimateDotIndex = baseUrl.lastIndexOf(".", lastDotIndex - 1);
4676 // Markup the beginning of the URL.
4677 if (urlString.startsWith("http://")) { // Highlight the protocol of connections that are not encrypted.
4678 urlTextBox.getText().setSpan(redColorSpan, 0, 7, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
4680 // De-emphasize subdomains.
4681 if (penultimateDotIndex > 0) { // There is more than one subdomain in the domain name.
4682 urlTextBox.getText().setSpan(initialGrayColorSpan, 7, penultimateDotIndex + 1, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
4684 } else if (urlString.startsWith("https://")) { // De-emphasize the protocol of connections that are encrypted.
4685 if (penultimateDotIndex > 0) { // There is more than one subdomain in the domain name.
4686 // De-emphasize the protocol and the additional subdomains.
4687 urlTextBox.getText().setSpan(initialGrayColorSpan, 0, penultimateDotIndex + 1, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
4688 } else { // There is only one subdomain in the domain name.
4689 // De-emphasize only the protocol.
4690 urlTextBox.getText().setSpan(initialGrayColorSpan, 0, 8, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
4694 // De-emphasize the text after the domain name.
4695 if (endOfDomainName > 0) {
4696 urlTextBox.getText().setSpan(finalGrayColorSpan, endOfDomainName, urlString.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
4701 private void loadBookmarksFolder() {
4702 // Update the bookmarks cursor with the contents of the bookmarks database for the current folder.
4703 bookmarksCursor = bookmarksDatabaseHelper.getAllBookmarksCursorByDisplayOrder(currentBookmarksFolder);
4705 // Populate the bookmarks cursor adapter. `this` specifies the `Context`. `false` disables `autoRequery`.
4706 bookmarksCursorAdapter = new CursorAdapter(this, bookmarksCursor, false) {
4708 public View newView(Context context, Cursor cursor, ViewGroup parent) {
4709 // Inflate the individual item layout. `false` does not attach it to the root.
4710 return getLayoutInflater().inflate(R.layout.bookmarks_drawer_item_linearlayout, parent, false);
4714 public void bindView(View view, Context context, Cursor cursor) {
4715 // Get handles for the views.
4716 ImageView bookmarkFavoriteIcon = view.findViewById(R.id.bookmark_favorite_icon);
4717 TextView bookmarkNameTextView = view.findViewById(R.id.bookmark_name);
4719 // Get the favorite icon byte array from the cursor.
4720 byte[] favoriteIconByteArray = cursor.getBlob(cursor.getColumnIndex(BookmarksDatabaseHelper.FAVORITE_ICON));
4722 // Convert the byte array to a `Bitmap` beginning at the first byte and ending at the last.
4723 Bitmap favoriteIconBitmap = BitmapFactory.decodeByteArray(favoriteIconByteArray, 0, favoriteIconByteArray.length);
4725 // Display the bitmap in `bookmarkFavoriteIcon`.
4726 bookmarkFavoriteIcon.setImageBitmap(favoriteIconBitmap);
4728 // Get the bookmark name from the cursor and display it in `bookmarkNameTextView`.
4729 String bookmarkNameString = cursor.getString(cursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME));
4730 bookmarkNameTextView.setText(bookmarkNameString);
4732 // Make the font bold for folders.
4733 if (cursor.getInt(cursor.getColumnIndex(BookmarksDatabaseHelper.IS_FOLDER)) == 1) {
4734 bookmarkNameTextView.setTypeface(Typeface.DEFAULT_BOLD);
4735 } else { // Reset the font to default for normal bookmarks.
4736 bookmarkNameTextView.setTypeface(Typeface.DEFAULT);
4741 // Populate the `ListView` with the adapter.
4742 bookmarksListView.setAdapter(bookmarksCursorAdapter);
4744 // Set the bookmarks drawer title.
4745 if (currentBookmarksFolder.isEmpty()) {
4746 bookmarksTitleTextView.setText(R.string.bookmarks);
4748 bookmarksTitleTextView.setText(currentBookmarksFolder);