2 * Copyright © 2015-2019 Soren Stoutner <soren@stoutner.com>.
4 * Download cookie code contributed 2017 Hendrik Knackstedt. Copyright assigned to Soren Stoutner <soren@stoutner.com>.
6 * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
8 * Privacy Browser is free software: you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation, either version 3 of the License, or
11 * (at your option) any later version.
13 * Privacy Browser is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with Privacy Browser. If not, see <http://www.gnu.org/licenses/>.
22 package com.stoutner.privacybrowser.activities;
24 import android.Manifest;
25 import android.annotation.SuppressLint;
26 import android.app.Activity;
27 import android.app.DownloadManager;
28 import android.app.SearchManager;
29 import android.content.ActivityNotFoundException;
30 import android.content.BroadcastReceiver;
31 import android.content.ClipData;
32 import android.content.ClipboardManager;
33 import android.content.Context;
34 import android.content.Intent;
35 import android.content.IntentFilter;
36 import android.content.SharedPreferences;
37 import android.content.pm.PackageManager;
38 import android.content.res.Configuration;
39 import android.content.res.Resources;
40 import android.database.Cursor;
41 import android.graphics.Bitmap;
42 import android.graphics.BitmapFactory;
43 import android.graphics.Typeface;
44 import android.graphics.drawable.BitmapDrawable;
45 import android.graphics.drawable.Drawable;
46 import android.net.Uri;
47 import android.net.http.SslCertificate;
48 import android.net.http.SslError;
49 import android.os.AsyncTask;
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.text.Editable;
58 import android.text.Spanned;
59 import android.text.TextWatcher;
60 import android.text.style.ForegroundColorSpan;
61 import android.util.Patterns;
62 import android.view.ContextMenu;
63 import android.view.GestureDetector;
64 import android.view.KeyEvent;
65 import android.view.Menu;
66 import android.view.MenuItem;
67 import android.view.MotionEvent;
68 import android.view.View;
69 import android.view.ViewGroup;
70 import android.view.WindowManager;
71 import android.view.inputmethod.InputMethodManager;
72 import android.webkit.CookieManager;
73 import android.webkit.HttpAuthHandler;
74 import android.webkit.SslErrorHandler;
75 import android.webkit.ValueCallback;
76 import android.webkit.WebBackForwardList;
77 import android.webkit.WebChromeClient;
78 import android.webkit.WebResourceResponse;
79 import android.webkit.WebSettings;
80 import android.webkit.WebStorage;
81 import android.webkit.WebView;
82 import android.webkit.WebViewClient;
83 import android.webkit.WebViewDatabase;
84 import android.widget.ArrayAdapter;
85 import android.widget.CursorAdapter;
86 import android.widget.EditText;
87 import android.widget.FrameLayout;
88 import android.widget.ImageView;
89 import android.widget.LinearLayout;
90 import android.widget.ListView;
91 import android.widget.ProgressBar;
92 import android.widget.RadioButton;
93 import android.widget.RelativeLayout;
94 import android.widget.TextView;
96 import androidx.annotation.NonNull;
97 import androidx.appcompat.app.ActionBar;
98 import androidx.appcompat.app.ActionBarDrawerToggle;
99 import androidx.appcompat.app.AppCompatActivity;
100 import androidx.appcompat.widget.Toolbar;
101 import androidx.core.app.ActivityCompat;
102 import androidx.core.content.ContextCompat;
103 // `ShortcutInfoCompat`, `ShortcutManagerCompat`, and `IconCompat` can be switched to the non-compat versions once API >= 26.
104 import androidx.core.content.pm.ShortcutInfoCompat;
105 import androidx.core.content.pm.ShortcutManagerCompat;
106 import androidx.core.graphics.drawable.IconCompat;
107 import androidx.core.view.GravityCompat;
108 import androidx.drawerlayout.widget.DrawerLayout;
109 import androidx.fragment.app.DialogFragment;
110 import androidx.fragment.app.FragmentManager;
111 import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
113 import com.google.android.material.floatingactionbutton.FloatingActionButton;
114 import com.google.android.material.navigation.NavigationView;
115 import com.google.android.material.snackbar.Snackbar;
117 import com.stoutner.privacybrowser.BuildConfig;
118 import com.stoutner.privacybrowser.R;
119 import com.stoutner.privacybrowser.dialogs.AdConsentDialog;
120 import com.stoutner.privacybrowser.dialogs.CreateBookmarkDialog;
121 import com.stoutner.privacybrowser.dialogs.CreateBookmarkFolderDialog;
122 import com.stoutner.privacybrowser.dialogs.CreateHomeScreenShortcutDialog;
123 import com.stoutner.privacybrowser.dialogs.DownloadImageDialog;
124 import com.stoutner.privacybrowser.dialogs.DownloadLocationPermissionDialog;
125 import com.stoutner.privacybrowser.dialogs.EditBookmarkDialog;
126 import com.stoutner.privacybrowser.dialogs.EditBookmarkFolderDialog;
127 import com.stoutner.privacybrowser.dialogs.HttpAuthenticationDialog;
128 import com.stoutner.privacybrowser.dialogs.PinnedMismatchDialog;
129 import com.stoutner.privacybrowser.dialogs.UrlHistoryDialog;
130 import com.stoutner.privacybrowser.dialogs.ViewSslCertificateDialog;
131 import com.stoutner.privacybrowser.helpers.AdHelper;
132 import com.stoutner.privacybrowser.helpers.BlockListHelper;
133 import com.stoutner.privacybrowser.helpers.BookmarksDatabaseHelper;
134 import com.stoutner.privacybrowser.helpers.DomainsDatabaseHelper;
135 import com.stoutner.privacybrowser.helpers.OrbotProxyHelper;
136 import com.stoutner.privacybrowser.dialogs.DownloadFileDialog;
137 import com.stoutner.privacybrowser.dialogs.SslCertificateErrorDialog;
138 import com.stoutner.privacybrowser.views.NestedScrollWebView;
140 import java.io.ByteArrayInputStream;
141 import java.io.ByteArrayOutputStream;
143 import java.io.IOException;
144 import java.io.UnsupportedEncodingException;
145 import java.lang.ref.WeakReference;
146 import java.net.InetAddress;
147 import java.net.MalformedURLException;
149 import java.net.URLDecoder;
150 import java.net.URLEncoder;
151 import java.net.UnknownHostException;
152 import java.util.ArrayList;
153 import java.util.Date;
154 import java.util.HashMap;
155 import java.util.HashSet;
156 import java.util.List;
157 import java.util.Map;
158 import java.util.Set;
160 // AppCompatActivity from android.support.v7.app.AppCompatActivity must be used to have access to the SupportActionBar until the minimum API is >= 21.
161 public class MainWebViewActivity extends AppCompatActivity implements CreateBookmarkDialog.CreateBookmarkListener, CreateBookmarkFolderDialog.CreateBookmarkFolderListener,
162 CreateHomeScreenShortcutDialog.CreateHomeScreenShortcutListener, DownloadFileDialog.DownloadFileListener, DownloadImageDialog.DownloadImageListener,
163 DownloadLocationPermissionDialog.DownloadLocationPermissionDialogListener, EditBookmarkDialog.EditBookmarkListener, EditBookmarkFolderDialog.EditBookmarkFolderListener,
164 HttpAuthenticationDialog.HttpAuthenticationListener, NavigationView.OnNavigationItemSelectedListener, PinnedMismatchDialog.PinnedMismatchListener,
165 SslCertificateErrorDialog.SslCertificateErrorListener, UrlHistoryDialog.UrlHistoryListener {
167 // `darkTheme` is public static so it can be accessed from everywhere.
168 public static boolean darkTheme;
170 // `allowScreenshots` is public static so it can be accessed from everywhere. It is also used in `onCreate()`.
171 public static boolean allowScreenshots;
173 // `favoriteIconBitmap` is public static so it can be accessed from `CreateHomeScreenShortcutDialog`, `BookmarksActivity`, `BookmarksDatabaseViewActivity`, `CreateBookmarkDialog`,
174 // `CreateBookmarkFolderDialog`, `EditBookmarkDialog`, `EditBookmarkFolderDialog`, `EditBookmarkDatabaseViewDialog`, and `ViewSslCertificateDialog`. It is also used in `onCreate()`,
175 // `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onCreateHomeScreenShortcutCreate()`, `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and `applyDomainSettings()`.
176 public static Bitmap favoriteIconBitmap;
178 // `favoriteIconDefaultBitmap` public static so it can be accessed from `PinnedMismatchDialog`. It is also used in `onCreate()` and `applyDomainSettings`.
179 public static Bitmap favoriteIconDefaultBitmap;
181 // `formattedUrlString` is public static so it can be accessed from `AddDomainDialog`, `BookmarksActivity`, `DomainSettingsFragment`, `CreateBookmarkDialog`, and `PinnedMismatchDialog`.
182 // It is also used in `onCreate()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onCreateHomeScreenShortcutCreate()`, `loadUrlFromTextBox()`, and `applyProxyThroughOrbot()`.
183 public static String formattedUrlString;
185 // `sslCertificate` is public static so it can be accessed from `DomainsActivity`, `DomainsListFragment`, `DomainSettingsFragment`, `PinnedMismatchDialog`, and `ViewSslCertificateDialog`.
186 // It is also used in `onCreate()` and `checkPinnedMismatch()`.
187 public static SslCertificate sslCertificate;
189 // `currentHostIpAddresses` is public static so it can be accessed from `DomainSettingsFragment` and `ViewSslCertificateDialog`.
190 // It is also used in `onCreate()` and `GetHostIpAddresses()`.
191 public static String currentHostIpAddresses;
193 // `orbotStatus` is public static so it can be accessed from `OrbotProxyHelper`. It is also used in `onCreate()`, `onResume()`, and `applyProxyThroughOrbot()`.
194 public static String orbotStatus;
196 // `webViewTitle` is public static so it can be accessed from `CreateBookmarkDialog` and `CreateHomeScreenShortcutDialog`. It is also used in `onCreate()`.
197 public static String webViewTitle;
199 // `appliedUserAgentString` is public static so it can be accessed from `ViewSourceActivity`. It is also used in `applyDomainSettings()`.
200 public static String appliedUserAgentString;
202 // `reloadOnRestart` is public static so it can be accessed from `SettingsFragment`. It is also used in `onRestart()`
203 public static boolean reloadOnRestart;
205 // `reloadUrlOnRestart` is public static so it can be accessed from `SettingsFragment` and `BookmarksActivity`. It is also used in `onRestart()`.
206 public static boolean loadUrlOnRestart;
208 // `restartFromBookmarksActivity` is public static so it can be accessed from `BookmarksActivity`. It is also used in `onRestart()`.
209 public static boolean restartFromBookmarksActivity;
211 // The block list versions are public static so they can be accessed from `AboutTabFragment`. They are also used in `onCreate()`.
212 public static String easyListVersion;
213 public static String easyPrivacyVersion;
214 public static String fanboysAnnoyanceVersion;
215 public static String fanboysSocialVersion;
216 public static String ultraPrivacyVersion;
218 // The request items are public static so they can be accessed by `BlockListHelper`, `RequestsArrayAdapter`, and `ViewRequestsDialog`. They are also used in `onCreate()` and `onPrepareOptionsMenu()`.
219 public static List<String[]> resourceRequests;
220 public static String[] whiteListResultStringArray;
221 private int blockedRequests;
222 private int easyListBlockedRequests;
223 private int easyPrivacyBlockedRequests;
224 private int fanboysAnnoyanceListBlockedRequests;
225 private int fanboysSocialBlockingListBlockedRequests;
226 private int ultraPrivacyBlockedRequests;
227 private int thirdPartyBlockedRequests;
229 public final static int REQUEST_DISPOSITION = 0;
230 public final static int REQUEST_URL = 1;
231 public final static int REQUEST_BLOCKLIST = 2;
232 public final static int REQUEST_SUBLIST = 3;
233 public final static int REQUEST_BLOCKLIST_ENTRIES = 4;
234 public final static int REQUEST_BLOCKLIST_ORIGINAL_ENTRY = 5;
236 public final static int REQUEST_DEFAULT = 0;
237 public final static int REQUEST_ALLOWED = 1;
238 public final static int REQUEST_THIRD_PARTY = 2;
239 public final static int REQUEST_BLOCKED = 3;
241 public final static int MAIN_WHITELIST = 1;
242 public final static int FINAL_WHITELIST = 2;
243 public final static int DOMAIN_WHITELIST = 3;
244 public final static int DOMAIN_INITIAL_WHITELIST = 4;
245 public final static int DOMAIN_FINAL_WHITELIST = 5;
246 public final static int THIRD_PARTY_WHITELIST = 6;
247 public final static int THIRD_PARTY_DOMAIN_WHITELIST = 7;
248 public final static int THIRD_PARTY_DOMAIN_INITIAL_WHITELIST = 8;
250 public final static int MAIN_BLACKLIST = 9;
251 public final static int INITIAL_BLACKLIST = 10;
252 public final static int FINAL_BLACKLIST = 11;
253 public final static int DOMAIN_BLACKLIST = 12;
254 public final static int DOMAIN_INITIAL_BLACKLIST = 13;
255 public final static int DOMAIN_FINAL_BLACKLIST = 14;
256 public final static int DOMAIN_REGULAR_EXPRESSION_BLACKLIST = 15;
257 public final static int THIRD_PARTY_BLACKLIST = 16;
258 public final static int THIRD_PARTY_INITIAL_BLACKLIST = 17;
259 public final static int THIRD_PARTY_DOMAIN_BLACKLIST = 18;
260 public final static int THIRD_PARTY_DOMAIN_INITIAL_BLACKLIST = 19;
261 public final static int THIRD_PARTY_REGULAR_EXPRESSION_BLACKLIST = 20;
262 public final static int THIRD_PARTY_DOMAIN_REGULAR_EXPRESSION_BLACKLIST = 21;
263 public final static int REGULAR_EXPRESSION_BLACKLIST = 22;
265 // `blockAllThirdPartyRequests` is public static so it can be accessed from `RequestsActivity`.
266 // It is also used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, and `applyAppSettings()`
267 public static boolean blockAllThirdPartyRequests;
269 // `currentBookmarksFolder` is public static so it can be accessed from `BookmarksActivity`. It is also used in `onCreate()`, `onBackPressed()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`,
270 // `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and `loadBookmarksFolder()`.
271 public static String currentBookmarksFolder;
273 // `domainSettingsDatabaseId` is public static so it can be accessed from `PinnedMismatchDialog`. It is also used in `onCreate()`, `onOptionsItemSelected()`, and `applyDomainSettings()`.
274 public static int domainSettingsDatabaseId;
276 // The pinned variables are public static so they can be accessed from `PinnedMismatchDialog`. They are also used in `onCreate()`, `applyDomainSettings()`, and `checkPinnedMismatch()`.
277 public static String pinnedSslIssuedToCName;
278 public static String pinnedSslIssuedToOName;
279 public static String pinnedSslIssuedToUName;
280 public static String pinnedSslIssuedByCName;
281 public static String pinnedSslIssuedByOName;
282 public static String pinnedSslIssuedByUName;
283 public static Date pinnedSslStartDate;
284 public static Date pinnedSslEndDate;
285 public static String pinnedHostIpAddresses;
287 // The user agent constants are public static so they can be accessed from `SettingsFragment`, `DomainsActivity`, and `DomainSettingsFragment`.
288 public final static int UNRECOGNIZED_USER_AGENT = -1;
289 public final static int SETTINGS_WEBVIEW_DEFAULT_USER_AGENT = 1;
290 public final static int SETTINGS_CUSTOM_USER_AGENT = 12;
291 public final static int DOMAINS_SYSTEM_DEFAULT_USER_AGENT = 0;
292 public final static int DOMAINS_WEBVIEW_DEFAULT_USER_AGENT = 2;
293 public final static int DOMAINS_CUSTOM_USER_AGENT = 13;
297 // `urlIsLoading` is used in `onCreate()`, `onCreateOptionsMenu()`, `loadUrl()`, `applyDomainSettings()`, and `GetHostIpAddresses`.
298 private static boolean urlIsLoading;
300 // `gettingIpAddresses` is used in `onCreate() and `GetHostIpAddresses`.
301 private static boolean gettingIpAddresses;
303 // `pinnedDomainSslCertificate` is used in `onCreate()`, `applyDomainSettings()`, and `checkPinnedMismatch()`.
304 private static boolean pinnedSslCertificate;
306 // `pinnedIpAddress` is used in `applyDomainSettings()` and `checkPinnedMismatch()`.
307 private static boolean pinnedIpAddresses;
309 // `ignorePinnedDomainInformation` is used in `onSslMismatchProceed()`, `applyDomainSettings()`, and `checkPinnedMismatch()`.
310 private static boolean ignorePinnedDomainInformation;
312 // The fragment manager is initialized in `onCreate()` and accessed from the static `checkPinnedMismatch()`.
313 private static FragmentManager fragmentManager;
316 // `navigatingHistory` is used in `onCreate()`, `onNavigationItemSelected()`, `onSslMismatchBack()`, and `applyDomainSettings()`.
317 private boolean navigatingHistory;
319 // `mainWebView` is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onRestart()`, `onCreateContextMenu()`, `findPreviousOnPage()`,
320 // `findNextOnPage()`, `closeFindOnPage()`, `loadUrlFromTextBox()`, `onSslMismatchBack()`, and `applyProxyThroughOrbot()`.
321 private NestedScrollWebView mainWebView;
323 // `fullScreenVideoFrameLayout` is used in `onCreate()` and `onConfigurationChanged()`.
324 private FrameLayout fullScreenVideoFrameLayout;
326 // `urlAppBarRelativeLayout` is used in `onCreate()` and `applyDomainSettings()`.
327 private RelativeLayout urlAppBarRelativeLayout;
329 // `favoriteIconImageView` is used in `onCreate()` and `applyDomainSettings()`
330 private ImageView favoriteIconImageView;
332 // `cookieManager` is used in `onCreate()`, `onOptionsItemSelected()`, and `onNavigationItemSelected()`, `loadUrlFromTextBox()`, `onDownloadImage()`, `onDownloadFile()`, and `onRestart()`.
333 private CookieManager cookieManager;
335 // `customHeader` is used in `onCreate()`, `onOptionsItemSelected()`, `onCreateContextMenu()`, and `loadUrl()`.
336 private final Map<String, String> customHeaders = new HashMap<>();
338 // `javaScriptEnabled` is also used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, `applyDomainSettings()`, and `updatePrivacyIcons()`.
339 private boolean javaScriptEnabled;
341 // `firstPartyCookiesEnabled` is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, `onDownloadImage()`, `onDownloadFile()`, and `applyDomainSettings()`.
342 private boolean firstPartyCookiesEnabled;
344 // `thirdPartyCookiesEnabled` used in `onCreate()`, `onPrepareOptionsMenu()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, and `applyDomainSettings()`.
345 private boolean thirdPartyCookiesEnabled;
347 // `domStorageEnabled` is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, and `applyDomainSettings()`.
348 private boolean domStorageEnabled;
350 // `saveFormDataEnabled` is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, and `applyDomainSettings()`. It can be removed once the minimum API >= 26.
351 private boolean saveFormDataEnabled;
353 // `nightMode` is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, and `applyDomainSettings()`.
354 private boolean nightMode;
356 // 'homepage' is used in `onCreate()`, `onNavigationItemSelected()`, and `applyProxyThroughOrbot()`.
357 private String homepage;
359 // `searchURL` is used in `loadURLFromTextBox()` and `applyProxyThroughOrbot()`.
360 private String searchURL;
362 // `mainMenu` is used in `onCreateOptionsMenu()` and `updatePrivacyIcons()`.
363 private Menu mainMenu;
365 // `refreshMenuItem` is used in `onCreate()` and `onCreateOptionsMenu()`.
366 private MenuItem refreshMenuItem;
368 // The blocklist menu items are used in `onCreate()`, `onCreateOptionsMenu()`, and `onPrepareOptionsMenu()`.
369 private MenuItem blocklistsMenuItem;
370 private MenuItem easyListMenuItem;
371 private MenuItem easyPrivacyMenuItem;
372 private MenuItem fanboysAnnoyanceListMenuItem;
373 private MenuItem fanboysSocialBlockingListMenuItem;
374 private MenuItem ultraPrivacyMenuItem;
375 private MenuItem blockAllThirdPartyRequestsMenuItem;
377 // The blocklist variables are used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, and `applyAppSettings()`.
378 private boolean easyListEnabled;
379 private boolean easyPrivacyEnabled;
380 private boolean fanboysAnnoyanceListEnabled;
381 private boolean fanboysSocialBlockingListEnabled;
382 private boolean ultraPrivacyEnabled;
384 // `webViewDefaultUserAgent` is used in `onCreate()` and `onPrepareOptionsMenu()`.
385 private String webViewDefaultUserAgent;
387 // `defaultCustomUserAgentString` is used in `onPrepareOptionsMenu()` and `applyDomainSettings()`.
388 private String defaultCustomUserAgentString;
390 // `privacyBrowserRuntime` is used in `onCreate()`, `onOptionsItemSelected()`, and `applyAppSettings()`.
391 private Runtime privacyBrowserRuntime;
393 // `proxyThroughOrbot` is used in `onRestart()`, `onOptionsItemSelected()`, `applyAppSettings()`, and `applyProxyThroughOrbot()`.
394 private boolean proxyThroughOrbot;
396 // `incognitoModeEnabled` is used in `onCreate()` and `applyAppSettings()`.
397 private boolean incognitoModeEnabled;
399 // `fullScreenBrowsingModeEnabled` is used in `onCreate()` and `applyAppSettings()`.
400 private boolean fullScreenBrowsingModeEnabled;
402 // `inFullScreenBrowsingMode` is used in `onCreate()`, `onConfigurationChanged()`, and `applyAppSettings()`.
403 private boolean inFullScreenBrowsingMode;
405 // Hide app bar is used in `onCreate()` and `applyAppSettings()`.
406 private boolean hideAppBar;
408 // `reapplyDomainSettingsOnRestart` is used in `onCreate()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onRestart()`, and `onAddDomain()`, .
409 private boolean reapplyDomainSettingsOnRestart;
411 // `reapplyAppSettingsOnRestart` is used in `onNavigationItemSelected()` and `onRestart()`.
412 private boolean reapplyAppSettingsOnRestart;
414 // `displayingFullScreenVideo` is used in `onCreate()` and `onResume()`.
415 private boolean displayingFullScreenVideo;
417 // `downloadWithExternalApp` is used in `onCreate()`, `onCreateContextMenu()`, and `applyDomainSettings()`.
418 private boolean downloadWithExternalApp;
420 // `currentDomainName` is used in `onCreate()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onAddDomain()`, and `applyDomainSettings()`.
421 private String currentDomainName;
423 // `orbotStatusBroadcastReceiver` is used in `onCreate()` and `onDestroy()`.
424 private BroadcastReceiver orbotStatusBroadcastReceiver;
426 // `waitingForOrbot` is used in `onCreate()`, `onResume()`, and `applyProxyThroughOrbot()`.
427 private boolean waitingForOrbot;
429 // `domainSettingsApplied` is used in `prepareOptionsMenu()` and `applyDomainSettings()`.
430 private boolean domainSettingsApplied;
432 // `domainSettingsJavaScriptEnabled` is used in `onOptionsItemSelected()` and `applyDomainSettings()`.
433 private Boolean domainSettingsJavaScriptEnabled;
435 // `waitingForOrbotHtmlString` is used in `onCreate()` and `applyProxyThroughOrbot()`.
436 private String waitingForOrbotHtmlString;
438 // `privateDataDirectoryString` is used in `onCreate()`, `onOptionsItemSelected()`, and `onNavigationItemSelected()`.
439 private String privateDataDirectoryString;
441 // `findOnPageEditText` is used in `onCreate()`, `onOptionsItemSelected()`, and `closeFindOnPage()`.
442 private EditText findOnPageEditText;
444 // `displayAdditionalAppBarIcons` is used in `onCreate()` and `onCreateOptionsMenu()`.
445 private boolean displayAdditionalAppBarIcons;
447 // The action bar drawer toggle is initialized in `onCreate()` and used in `onResume()`.
448 private ActionBarDrawerToggle actionBarDrawerToggle;
450 // `urlTextBox` is used in `onCreate()`, `onOptionsItemSelected()`, `loadUrlFromTextBox()`, `loadUrl()`, and `highlightUrlText()`.
451 private EditText urlTextBox;
453 // The color spans are used in `onCreate()` and `highlightUrlText()`.
454 private ForegroundColorSpan redColorSpan;
455 private ForegroundColorSpan initialGrayColorSpan;
456 private ForegroundColorSpan finalGrayColorSpan;
458 // The drawer header padding variables are used in `onCreate()` and `onConfigurationChanged()`.
459 private int drawerHeaderPaddingLeftAndRight;
460 private int drawerHeaderPaddingTop;
461 private int drawerHeaderPaddingBottom;
463 // `sslErrorHandler` is used in `onCreate()`, `onSslErrorCancel()`, and `onSslErrorProceed`.
464 private SslErrorHandler sslErrorHandler;
466 // `httpAuthHandler` is used in `onCreate()`, `onHttpAuthenticationCancel()`, and `onHttpAuthenticationProceed()`.
467 private static HttpAuthHandler httpAuthHandler;
469 // `inputMethodManager` is used in `onOptionsItemSelected()`, `loadUrlFromTextBox()`, and `closeFindOnPage()`.
470 private InputMethodManager inputMethodManager;
472 // `bookmarksDatabaseHelper` is used in `onCreate()`, `onDestroy`, `onOptionsItemSelected()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`,
473 // and `loadBookmarksFolder()`.
474 private BookmarksDatabaseHelper bookmarksDatabaseHelper;
476 // `bookmarksListView` is used in `onCreate()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, and `loadBookmarksFolder()`.
477 private ListView bookmarksListView;
479 // `bookmarksTitleTextView` is used in `onCreate()` and `loadBookmarksFolder()`.
480 private TextView bookmarksTitleTextView;
482 // `bookmarksCursor` is used in `onDestroy()`, `onOptionsItemSelected()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and `loadBookmarksFolder()`.
483 private Cursor bookmarksCursor;
485 // `bookmarksCursorAdapter` is used in `onCreateBookmark()`, `onCreateBookmarkFolder()` `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and `loadBookmarksFolder()`.
486 private CursorAdapter bookmarksCursorAdapter;
488 // `oldFolderNameString` is used in `onCreate()` and `onSaveEditBookmarkFolder()`.
489 private String oldFolderNameString;
491 // `fileChooserCallback` is used in `onCreate()` and `onActivityResult()`.
492 private ValueCallback<Uri[]> fileChooserCallback;
494 // The download strings are used in `onCreate()` and `onRequestPermissionResult()`.
495 private String downloadUrl;
496 private String downloadContentDisposition;
497 private long downloadContentLength;
499 // `downloadImageUrl` is used in `onCreateContextMenu()` and `onRequestPermissionResult()`.
500 private String downloadImageUrl;
502 // The user agent variables are used in `onCreate()` and `applyDomainSettings()`.
503 private ArrayAdapter<CharSequence> userAgentNamesArray;
504 private String[] userAgentDataArray;
506 // The request codes are used in `onCreate()`, `onCreateContextMenu()`, `onCloseDownloadLocationPermissionDialog()`, and `onRequestPermissionResult()`.
507 private final int DOWNLOAD_FILE_REQUEST_CODE = 1;
508 private final int DOWNLOAD_IMAGE_REQUEST_CODE = 2;
511 // 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.
512 // Also, remove the warning about needing to override `performClick()` when using an `OnTouchListener` with `WebView`.
513 @SuppressLint({"SetJavaScriptEnabled", "ClickableViewAccessibility"})
514 // Remove Android Studio's warning about deprecations. The deprecated `getColor()` must be used until API >= 23.
515 @SuppressWarnings("deprecation")
516 protected void onCreate(Bundle savedInstanceState) {
517 // Get a handle for the shared preferences.
518 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
520 // Get the theme and screenshot preferences.
521 darkTheme = sharedPreferences.getBoolean("dark_theme", false);
522 allowScreenshots = sharedPreferences.getBoolean("allow_screenshots", false);
524 // Disable screenshots if not allowed.
525 if (!allowScreenshots) {
526 getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE);
529 // Set the activity theme.
531 setTheme(R.style.PrivacyBrowserDark);
533 setTheme(R.style.PrivacyBrowserLight);
536 // Run the default commands.
537 super.onCreate(savedInstanceState);
539 // Set the content view.
540 setContentView(R.layout.main_framelayout);
542 // Get handles for views, resources, and managers.
543 Resources resources = getResources();
544 fragmentManager = getSupportFragmentManager();
545 inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
546 Toolbar toolbar = findViewById(R.id.toolbar);
548 // Set the action bar. `SupportActionBar` must be used until the minimum API is >= 21.
549 setSupportActionBar(toolbar);
550 ActionBar actionBar = getSupportActionBar();
552 // This is needed to get rid of the Android Studio warning that the action bar might be null.
553 assert actionBar != null;
555 // Add the custom `url_app_bar` layout, which shows the favorite icon and the URL text bar.
556 actionBar.setCustomView(R.layout.url_app_bar);
557 actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM);
559 // Initialize the foreground color spans for highlighting the URLs. We have to use the deprecated `getColor()` until API >= 23.
560 redColorSpan = new ForegroundColorSpan(resources.getColor(R.color.red_a700));
561 initialGrayColorSpan = new ForegroundColorSpan(resources.getColor(R.color.gray_500));
562 finalGrayColorSpan = new ForegroundColorSpan(resources.getColor(R.color.gray_500));
564 // Get a handle for `urlTextBox`.
565 urlTextBox = findViewById(R.id.url_edittext);
567 // Remove the formatting from `urlTextBar` when the user is editing the text.
568 urlTextBox.setOnFocusChangeListener((View v, boolean hasFocus) -> {
569 if (hasFocus) { // The user is editing the URL text box.
570 // Remove the highlighting.
571 urlTextBox.getText().removeSpan(redColorSpan);
572 urlTextBox.getText().removeSpan(initialGrayColorSpan);
573 urlTextBox.getText().removeSpan(finalGrayColorSpan);
574 } else { // The user has stopped editing the URL text box.
575 // Move to the beginning of the string.
576 urlTextBox.setSelection(0);
578 // Reapply the highlighting.
583 // Set the go button on the keyboard to load the URL in `urlTextBox`.
584 urlTextBox.setOnKeyListener((View v, int keyCode, KeyEvent event) -> {
585 // If the event is a key-down event on the `enter` button, load the URL.
586 if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) {
587 // Load the URL into the mainWebView and consume the event.
588 loadUrlFromTextBox();
590 // If the enter key was pressed, consume the event.
593 // If any other key was pressed, do not consume the event.
598 // Set `waitingForOrbotHTMLString`.
599 waitingForOrbotHtmlString = "<html><body><br/><center><h1>" + getString(R.string.waiting_for_orbot) + "</h1></center></body></html>";
601 // Initialize `currentDomainName`, `orbotStatus`, and `waitingForOrbot`.
602 currentDomainName = "";
603 orbotStatus = "unknown";
604 waitingForOrbot = false;
606 // Create an Orbot status `BroadcastReceiver`.
607 orbotStatusBroadcastReceiver = new BroadcastReceiver() {
609 public void onReceive(Context context, Intent intent) {
610 // Store the content of the status message in `orbotStatus`.
611 orbotStatus = intent.getStringExtra("org.torproject.android.intent.extra.STATUS");
613 // If Privacy Browser is waiting on Orbot, load the website now that Orbot is connected.
614 if (orbotStatus.equals("ON") && waitingForOrbot) {
615 // Reset `waitingForOrbot`.
616 waitingForOrbot = false;
618 // Load `formattedUrlString
619 loadUrl(formattedUrlString);
624 // Register `orbotStatusBroadcastReceiver` on `this` context.
625 this.registerReceiver(orbotStatusBroadcastReceiver, new IntentFilter("org.torproject.android.intent.action.STATUS"));
627 // Get handles for views that need to be modified.
628 FrameLayout rootFrameLayout = findViewById(R.id.root_framelayout);
629 DrawerLayout drawerLayout = findViewById(R.id.drawerlayout);
630 RelativeLayout mainContentRelativeLayout = findViewById(R.id.main_content_relativelayout);
631 SwipeRefreshLayout swipeRefreshLayout = findViewById(R.id.swiperefreshlayout);
632 mainWebView = findViewById(R.id.main_webview);
633 bookmarksListView = findViewById(R.id.bookmarks_drawer_listview);
634 bookmarksTitleTextView = findViewById(R.id.bookmarks_title_textview);
635 FloatingActionButton launchBookmarksActivityFab = findViewById(R.id.launch_bookmarks_activity_fab);
636 FloatingActionButton createBookmarkFolderFab = findViewById(R.id.create_bookmark_folder_fab);
637 FloatingActionButton createBookmarkFab = findViewById(R.id.create_bookmark_fab);
638 findOnPageEditText = findViewById(R.id.find_on_page_edittext);
639 fullScreenVideoFrameLayout = findViewById(R.id.full_screen_video_framelayout);
640 urlAppBarRelativeLayout = findViewById(R.id.url_app_bar_relativelayout);
641 favoriteIconImageView = findViewById(R.id.favorite_icon);
643 // 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.
645 launchBookmarksActivityFab.setImageDrawable(resources.getDrawable(R.drawable.bookmarks_dark));
646 createBookmarkFolderFab.setImageDrawable(resources.getDrawable(R.drawable.create_folder_dark));
647 createBookmarkFab.setImageDrawable(resources.getDrawable(R.drawable.create_bookmark_dark));
648 bookmarksListView.setBackgroundColor(resources.getColor(R.color.gray_850));
650 launchBookmarksActivityFab.setImageDrawable(resources.getDrawable(R.drawable.bookmarks_light));
651 createBookmarkFolderFab.setImageDrawable(resources.getDrawable(R.drawable.create_folder_light));
652 createBookmarkFab.setImageDrawable(resources.getDrawable(R.drawable.create_bookmark_light));
653 bookmarksListView.setBackgroundColor(resources.getColor(R.color.white));
656 // Set the launch bookmarks activity FAB to launch the bookmarks activity.
657 launchBookmarksActivityFab.setOnClickListener(v -> {
658 // Create an intent to launch the bookmarks activity.
659 Intent bookmarksIntent = new Intent(getApplicationContext(), BookmarksActivity.class);
661 // Include the current folder with the `Intent`.
662 bookmarksIntent.putExtra("Current Folder", currentBookmarksFolder);
665 startActivity(bookmarksIntent);
668 // Set the create new bookmark folder FAB to display an alert dialog.
669 createBookmarkFolderFab.setOnClickListener(v -> {
670 // Show the create bookmark folder dialog and name the instance `@string/create_folder`.
671 DialogFragment createBookmarkFolderDialog = new CreateBookmarkFolderDialog();
672 createBookmarkFolderDialog.show(fragmentManager, resources.getString(R.string.create_folder));
675 // Set the create new bookmark FAB to display an alert dialog.
676 createBookmarkFab.setOnClickListener(view -> {
677 // Show the create bookmark dialog and name the instance `@string/create_bookmark`.
678 DialogFragment createBookmarkDialog = new CreateBookmarkDialog();
679 createBookmarkDialog.show(fragmentManager, resources.getString(R.string.create_bookmark));
682 // Create a double-tap listener to toggle full-screen mode.
683 final GestureDetector gestureDetector = new GestureDetector(this, new GestureDetector.SimpleOnGestureListener() {
684 // Override `onDoubleTap()`. All other events are handled using the default settings.
686 public boolean onDoubleTap(MotionEvent event) {
687 if (fullScreenBrowsingModeEnabled) { // Only process the double-tap if full screen browsing mode is enabled.
688 // Toggle the full screen browsing mode tracker.
689 inFullScreenBrowsingMode = !inFullScreenBrowsingMode;
691 // Toggle the full screen browsing mode.
692 if (inFullScreenBrowsingMode) { // Switch to full screen mode.
693 // Hide the app bar if specified.
698 // Hide the banner ad in the free flavor.
699 if (BuildConfig.FLAVOR.contentEquals("free")) {
700 AdHelper.hideAd(findViewById(R.id.adview));
703 // Remove the translucent status flag. This is necessary so the root frame layout can fill the entire screen.
704 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
706 /* Hide the system bars.
707 * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
708 * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
709 * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
710 * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
712 rootFrameLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
713 View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
714 } else { // Switch to normal viewing mode.
718 // Show the banner ad in the free flavor.
719 if (BuildConfig.FLAVOR.contentEquals("free")) {
721 AdHelper.loadAd(findViewById(R.id.adview), getApplicationContext(), getString(R.string.ad_unit_id));
724 // Remove the `SYSTEM_UI` flags from the root frame layout.
725 rootFrameLayout.setSystemUiVisibility(0);
727 // Add the translucent status flag.
728 getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
731 // Consume the double-tap.
733 } else { // Do not consume the double-tap because full screen browsing mode is disabled.
739 // Pass all touch events on `mainWebView` through `gestureDetector` to check for double-taps.
740 mainWebView.setOnTouchListener((View view, MotionEvent event) -> {
741 // Call `performClick()` on the view, which is required for accessibility.
744 // Send the `event` to `gestureDetector`.
745 return gestureDetector.onTouchEvent(event);
748 // Update `findOnPageCountTextView`.
749 mainWebView.setFindListener(new WebView.FindListener() {
750 // Get a handle for `findOnPageCountTextView`.
751 final TextView findOnPageCountTextView = findViewById(R.id.find_on_page_count_textview);
754 public void onFindResultReceived(int activeMatchOrdinal, int numberOfMatches, boolean isDoneCounting) {
755 if ((isDoneCounting) && (numberOfMatches == 0)) { // There are no matches.
756 // Set `findOnPageCountTextView` to `0/0`.
757 findOnPageCountTextView.setText(R.string.zero_of_zero);
758 } else if (isDoneCounting) { // There are matches.
759 // `activeMatchOrdinal` is zero-based.
760 int activeMatch = activeMatchOrdinal + 1;
762 // Build the match string.
763 String matchString = activeMatch + "/" + numberOfMatches;
765 // Set `findOnPageCountTextView`.
766 findOnPageCountTextView.setText(matchString);
771 // Search for the string on the page whenever a character changes in the `findOnPageEditText`.
772 findOnPageEditText.addTextChangedListener(new TextWatcher() {
774 public void beforeTextChanged(CharSequence s, int start, int count, int after) {
779 public void onTextChanged(CharSequence s, int start, int before, int count) {
784 public void afterTextChanged(Editable s) {
785 // Search for the text in `mainWebView`.
786 mainWebView.findAllAsync(findOnPageEditText.getText().toString());
790 // Set the `check mark` button for the `findOnPageEditText` keyboard to close the soft keyboard.
791 findOnPageEditText.setOnKeyListener((v, keyCode, event) -> {
792 if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) { // The `enter` key was pressed.
793 // Hide the soft keyboard. `0` indicates no additional flags.
794 inputMethodManager.hideSoftInputFromWindow(mainWebView.getWindowToken(), 0);
796 // Consume the event.
798 } else { // A different key was pressed.
799 // Do not consume the event.
804 // Implement swipe to refresh.
805 swipeRefreshLayout.setOnRefreshListener(() -> mainWebView.reload());
807 // The swipe to refresh circle doesn't always hide itself completely unless it is moved up 10 pixels.
808 swipeRefreshLayout.setProgressViewOffset(false, swipeRefreshLayout.getProgressViewStartOffset() - 10, swipeRefreshLayout.getProgressViewEndOffset());
810 // Set the swipe to refresh color according to the theme.
812 swipeRefreshLayout.setColorSchemeResources(R.color.blue_600);
813 swipeRefreshLayout.setProgressBackgroundColorSchemeResource(R.color.gray_850);
815 swipeRefreshLayout.setColorSchemeResources(R.color.blue_700);
818 // `DrawerTitle` identifies the `DrawerLayouts` in accessibility mode.
819 drawerLayout.setDrawerTitle(GravityCompat.START, getString(R.string.navigation_drawer));
820 drawerLayout.setDrawerTitle(GravityCompat.END, getString(R.string.bookmarks));
822 // Listen for touches on the navigation menu.
823 final NavigationView navigationView = findViewById(R.id.navigationview);
824 navigationView.setNavigationItemSelectedListener(this);
826 // 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.
827 final Menu navigationMenu = navigationView.getMenu();
828 final MenuItem navigationBackMenuItem = navigationMenu.getItem(1);
829 final MenuItem navigationForwardMenuItem = navigationMenu.getItem(2);
830 final MenuItem navigationHistoryMenuItem = navigationMenu.getItem(3);
831 final MenuItem navigationRequestsMenuItem = navigationMenu.getItem(4);
833 // Initialize the bookmarks database helper. The `0` specifies a database version, but that is ignored and set instead using a constant in `BookmarksDatabaseHelper`.
834 bookmarksDatabaseHelper = new BookmarksDatabaseHelper(this, null, null, 0);
836 // Initialize `currentBookmarksFolder`. `""` is the home folder in the database.
837 currentBookmarksFolder = "";
839 // Load the home folder, which is `""` in the database.
840 loadBookmarksFolder();
842 bookmarksListView.setOnItemClickListener((parent, view, position, id) -> {
843 // Convert the id from long to int to match the format of the bookmarks database.
844 int databaseID = (int) id;
846 // Get the bookmark cursor for this ID and move it to the first row.
847 Cursor bookmarkCursor = bookmarksDatabaseHelper.getBookmark(databaseID);
848 bookmarkCursor.moveToFirst();
850 // Act upon the bookmark according to the type.
851 if (bookmarkCursor.getInt(bookmarkCursor.getColumnIndex(BookmarksDatabaseHelper.IS_FOLDER)) == 1) { // The selected bookmark is a folder.
852 // Store the new folder name in `currentBookmarksFolder`.
853 currentBookmarksFolder = bookmarkCursor.getString(bookmarkCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME));
855 // Load the new folder.
856 loadBookmarksFolder();
857 } else { // The selected bookmark is not a folder.
858 // Load the bookmark URL.
859 loadUrl(bookmarkCursor.getString(bookmarkCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_URL)));
861 // Close the bookmarks drawer.
862 drawerLayout.closeDrawer(GravityCompat.END);
865 // Close the `Cursor`.
866 bookmarkCursor.close();
869 bookmarksListView.setOnItemLongClickListener((parent, view, position, id) -> {
870 // Convert the database ID from `long` to `int`.
871 int databaseId = (int) id;
873 // Find out if the selected bookmark is a folder.
874 boolean isFolder = bookmarksDatabaseHelper.isFolder(databaseId);
877 // Save the current folder name, which is used in `onSaveEditBookmarkFolder()`.
878 oldFolderNameString = bookmarksCursor.getString(bookmarksCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME));
880 // Show the edit bookmark folder `AlertDialog` and name the instance `@string/edit_folder`.
881 DialogFragment editBookmarkFolderDialog = EditBookmarkFolderDialog.folderDatabaseId(databaseId);
882 editBookmarkFolderDialog.show(fragmentManager, resources.getString(R.string.edit_folder));
884 // Show the edit bookmark `AlertDialog` and name the instance `@string/edit_bookmark`.
885 DialogFragment editBookmarkDialog = EditBookmarkDialog.bookmarkDatabaseId(databaseId);
886 editBookmarkDialog.show(fragmentManager, resources.getString(R.string.edit_bookmark));
889 // Consume the event.
893 // Get the status bar pixel size.
894 int statusBarResourceId = resources.getIdentifier("status_bar_height", "dimen", "android");
895 int statusBarPixelSize = resources.getDimensionPixelSize(statusBarResourceId);
897 // Get the resource density.
898 float screenDensity = resources.getDisplayMetrics().density;
900 // Calculate the drawer header padding. This is used to move the text in the drawer headers below any cutouts.
901 drawerHeaderPaddingLeftAndRight = (int) (15 * screenDensity);
902 drawerHeaderPaddingTop = statusBarPixelSize + (int) (4 * screenDensity);
903 drawerHeaderPaddingBottom = (int) (8 * screenDensity);
905 // The drawer listener is used to update the navigation menu.`
906 drawerLayout.addDrawerListener(new DrawerLayout.DrawerListener() {
908 public void onDrawerSlide(@NonNull View drawerView, float slideOffset) {
912 public void onDrawerOpened(@NonNull View drawerView) {
916 public void onDrawerClosed(@NonNull View drawerView) {
920 public void onDrawerStateChanged(int newState) {
921 if ((newState == DrawerLayout.STATE_SETTLING) || (newState == DrawerLayout.STATE_DRAGGING)) { // A drawer is opening or closing.
922 // Get handles for the drawer headers.
923 TextView navigationHeaderTextView = findViewById(R.id.navigationText);
924 TextView bookmarksHeaderTextView = findViewById(R.id.bookmarks_title_textview);
926 // 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.
927 if (navigationHeaderTextView != null) {
928 navigationHeaderTextView.setPadding(drawerHeaderPaddingLeftAndRight, drawerHeaderPaddingTop, drawerHeaderPaddingLeftAndRight, drawerHeaderPaddingBottom);
931 // 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.
932 if (bookmarksHeaderTextView != null) {
933 bookmarksHeaderTextView.setPadding(drawerHeaderPaddingLeftAndRight, drawerHeaderPaddingTop, drawerHeaderPaddingLeftAndRight, drawerHeaderPaddingBottom);
936 // Update the back, forward, history, and requests menu items.
937 navigationBackMenuItem.setEnabled(mainWebView.canGoBack());
938 navigationForwardMenuItem.setEnabled(mainWebView.canGoForward());
939 navigationHistoryMenuItem.setEnabled((mainWebView.canGoBack() || mainWebView.canGoForward()));
940 navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
942 // Hide the keyboard (if displayed).
943 inputMethodManager.hideSoftInputFromWindow(mainWebView.getWindowToken(), 0);
945 // 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.
946 urlTextBox.clearFocus();
947 mainWebView.clearFocus();
952 // Create the hamburger icon at the start of the AppBar.
953 actionBarDrawerToggle = new ActionBarDrawerToggle(this, drawerLayout, toolbar, R.string.open_navigation_drawer, R.string.close_navigation_drawer);
955 // Get a handle for the progress bar.
956 final ProgressBar progressBar = findViewById(R.id.progress_bar);
958 mainWebView.setWebChromeClient(new WebChromeClient() {
959 // Update the progress bar when a page is loading.
961 public void onProgressChanged(WebView view, int progress) {
962 // Inject the night mode CSS if night mode is enabled.
964 // `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
965 // used by WordPress. `text-decoration: none` removes all text underlines. `text-shadow: none` removes text shadows, which usually have a hard coded color.
966 // `border: none` removes all borders, which can also be used to underline text. `a {color: #1565C0}` sets links to be a dark blue.
967 // `::selection {background: #0D47A1}' sets the text selection highlight color to be a dark blue. `!important` takes precedent over any existing sub-settings.
968 mainWebView.evaluateJavascript("(function() {var parent = document.getElementsByTagName('head').item(0); var style = document.createElement('style'); style.type = 'text/css'; " +
969 "style.innerHTML = '* {background-color: #212121 !important; color: #BDBDBD !important; box-shadow: none !important; text-decoration: none !important;" +
970 "text-shadow: none !important; border: none !important;} a {color: #1565C0 !important;} ::selection {background: #0D47A1 !important;}'; parent.appendChild(style)})()", value -> {
971 // Initialize a handler to display `mainWebView`.
972 Handler displayWebViewHandler = new Handler();
974 // Setup a runnable to display `mainWebView` after a delay to allow the CSS to be applied.
975 Runnable displayWebViewRunnable = () -> {
976 // Only display `mainWebView` if the progress bar is gone. This prevents the display of the `WebView` while it is still loading.
977 if (progressBar.getVisibility() == View.GONE) {
978 mainWebView.setVisibility(View.VISIBLE);
982 // Displaying of `mainWebView` after 500 milliseconds.
983 displayWebViewHandler.postDelayed(displayWebViewRunnable, 500);
987 // Update the progress bar.
988 progressBar.setProgress(progress);
990 // Set the visibility of the progress bar.
991 if (progress < 100) {
992 // Show the progress bar.
993 progressBar.setVisibility(View.VISIBLE);
995 // Hide the progress bar.
996 progressBar.setVisibility(View.GONE);
998 // Display `mainWebView` if night mode is disabled.
999 // 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
1000 // currently enabled.
1002 mainWebView.setVisibility(View.VISIBLE);
1005 //Stop the swipe to refresh indicator if it is running
1006 swipeRefreshLayout.setRefreshing(false);
1010 // Set the favorite icon when it changes.
1012 public void onReceivedIcon(WebView view, Bitmap icon) {
1013 // Only update the favorite icon if the website has finished loading.
1014 if (progressBar.getVisibility() == View.GONE) {
1015 // Save a copy of the favorite icon.
1016 favoriteIconBitmap = icon;
1018 // Place the favorite icon in the appBar.
1019 favoriteIconImageView.setImageBitmap(Bitmap.createScaledBitmap(icon, 64, 64, true));
1023 // Save a copy of the title when it changes.
1025 public void onReceivedTitle(WebView view, String title) {
1026 // Save a copy of the title.
1027 webViewTitle = title;
1030 // Enter full screen video.
1032 public void onShowCustomView(View video, CustomViewCallback callback) {
1033 // Set the full screen video flag.
1034 displayingFullScreenVideo = true;
1036 // Pause the ad if this is the free flavor.
1037 if (BuildConfig.FLAVOR.contentEquals("free")) {
1038 // The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations.
1039 AdHelper.pauseAd(findViewById(R.id.adview));
1042 // Hide the keyboard.
1043 inputMethodManager.hideSoftInputFromWindow(mainWebView.getWindowToken(), 0);
1045 // Hide the main content relative layout.
1046 mainContentRelativeLayout.setVisibility(View.GONE);
1048 // Remove the translucent status bar overlay on the `Drawer Layout`, which is special and needs its own command.
1049 drawerLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
1051 // Remove the translucent status flag. This is necessary so the root frame layout can fill the entire screen.
1052 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
1054 /* Hide the system bars.
1055 * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
1056 * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
1057 * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
1058 * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
1060 rootFrameLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
1061 View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
1063 // Disable the sliding drawers.
1064 drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED);
1066 // Add the video view to the full screen video frame layout.
1067 fullScreenVideoFrameLayout.addView(video);
1069 // Show the full screen video frame layout.
1070 fullScreenVideoFrameLayout.setVisibility(View.VISIBLE);
1073 // Exit full screen video.
1075 public void onHideCustomView() {
1076 // Unset the full screen video flag.
1077 displayingFullScreenVideo = false;
1079 // Remove all the views from the full screen video frame layout.
1080 fullScreenVideoFrameLayout.removeAllViews();
1082 // Hide the full screen video frame layout.
1083 fullScreenVideoFrameLayout.setVisibility(View.GONE);
1085 // Enable the sliding drawers.
1086 drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED);
1088 // Show the main content relative layout.
1089 mainContentRelativeLayout.setVisibility(View.VISIBLE);
1091 // Apply the appropriate full screen mode the `SYSTEM_UI` flags.
1092 if (fullScreenBrowsingModeEnabled && inFullScreenBrowsingMode) { // Privacy Browser is currently in full screen browsing mode.
1093 // Hide the app bar if specified.
1098 // Hide the banner ad in the free flavor.
1099 if (BuildConfig.FLAVOR.contentEquals("free")) {
1100 AdHelper.hideAd(findViewById(R.id.adview));
1103 // Remove the translucent status flag. This is necessary so the root frame layout can fill the entire screen.
1104 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
1106 /* Hide the system bars.
1107 * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
1108 * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
1109 * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
1110 * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
1112 rootFrameLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
1113 View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
1114 } else { // Switch to normal viewing mode.
1115 // Remove the `SYSTEM_UI` flags from the root frame layout.
1116 rootFrameLayout.setSystemUiVisibility(0);
1118 // Add the translucent status flag.
1119 getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
1122 // Reload the ad for the free flavor if not in full screen mode.
1123 if (BuildConfig.FLAVOR.contentEquals("free") && !inFullScreenBrowsingMode) {
1125 AdHelper.loadAd(findViewById(R.id.adview), getApplicationContext(), getString(R.string.ad_unit_id));
1131 public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) {
1132 // Show the file chooser if the device is running API >= 21.
1133 if (Build.VERSION.SDK_INT >= 21) {
1134 // Store the file path callback.
1135 fileChooserCallback = filePathCallback;
1137 // Create an intent to open a chooser based ont the file chooser parameters.
1138 Intent fileChooserIntent = fileChooserParams.createIntent();
1140 // Open the file chooser. Currently only one `startActivityForResult` exists in this activity, so the request code, used to differentiate them, is simply `0`.
1141 startActivityForResult(fileChooserIntent, 0);
1147 // Register `mainWebView` for a context menu. This is used to see link targets and download images.
1148 registerForContextMenu(mainWebView);
1150 // Allow the downloading of files.
1151 mainWebView.setDownloadListener((String url, String userAgent, String contentDisposition, String mimetype, long contentLength) -> {
1152 // Check if the download should be processed by an external app.
1153 if (downloadWithExternalApp) { // Download with an external app.
1154 openUrlWithExternalApp(url);
1155 } else { // Download with Android's download manager.
1156 // Check to see if the WRITE_EXTERNAL_STORAGE permission has already been granted.
1157 if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) { // The storage permission has not been granted.
1158 // The WRITE_EXTERNAL_STORAGE permission needs to be requested.
1160 // Store the variables for future use by `onRequestPermissionsResult()`.
1162 downloadContentDisposition = contentDisposition;
1163 downloadContentLength = contentLength;
1165 // Show a dialog if the user has previously denied the permission.
1166 if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) { // Show a dialog explaining the request first.
1167 // Instantiate the download location permission alert dialog and set the download type to DOWNLOAD_FILE.
1168 DialogFragment downloadLocationPermissionDialogFragment = DownloadLocationPermissionDialog.downloadType(DownloadLocationPermissionDialog.DOWNLOAD_FILE);
1170 // Show the download location permission alert dialog. The permission will be requested when the the dialog is closed.
1171 downloadLocationPermissionDialogFragment.show(fragmentManager, getString(R.string.download_location));
1172 } else { // Show the permission request directly.
1173 // Request the permission. The download dialog will be launched by `onRequestPermissionResult()`.
1174 ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_FILE_REQUEST_CODE);
1176 } else { // The storage permission has already been granted.
1177 // Get a handle for the download file alert dialog.
1178 DialogFragment downloadFileDialogFragment = DownloadFileDialog.fromUrl(url, contentDisposition, contentLength);
1180 // Show the download file alert dialog.
1181 downloadFileDialogFragment.show(fragmentManager, getString(R.string.download));
1186 // Allow pinch to zoom.
1187 mainWebView.getSettings().setBuiltInZoomControls(true);
1189 // Hide zoom controls.
1190 mainWebView.getSettings().setDisplayZoomControls(false);
1192 // Don't allow mixed content (HTTP and HTTPS) on the same website.
1193 if (Build.VERSION.SDK_INT >= 21) {
1194 mainWebView.getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_NEVER_ALLOW);
1197 // Set the WebView to use a wide viewport. Otherwise, some web pages will be scrunched and some content will render outside the screen.
1198 mainWebView.getSettings().setUseWideViewPort(true);
1200 // Set the WebView to load in overview mode (zoomed out to the maximum width).
1201 mainWebView.getSettings().setLoadWithOverviewMode(true);
1203 // Explicitly disable geolocation.
1204 mainWebView.getSettings().setGeolocationEnabled(false);
1206 // Initialize cookieManager.
1207 cookieManager = CookieManager.getInstance();
1209 // 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).
1210 customHeaders.put("X-Requested-With", "");
1212 // Initialize the default preference values the first time the program is run. `false` keeps this command from resetting any current preferences back to default.
1213 PreferenceManager.setDefaultValues(this, R.xml.preferences, false);
1215 // Get a handle for the `Runtime`.
1216 privacyBrowserRuntime = Runtime.getRuntime();
1218 // Store the application's private data directory.
1219 privateDataDirectoryString = getApplicationInfo().dataDir;
1220 // `dataDir` will vary, but will be something like `/data/user/0/com.stoutner.privacybrowser.standard`, which links to `/data/data/com.stoutner.privacybrowser.standard`.
1222 // Initialize `inFullScreenBrowsingMode`, which is always false at this point because Privacy Browser never starts in full screen browsing mode.
1223 inFullScreenBrowsingMode = false;
1225 // Initialize the privacy settings variables.
1226 javaScriptEnabled = false;
1227 firstPartyCookiesEnabled = false;
1228 thirdPartyCookiesEnabled = false;
1229 domStorageEnabled = false;
1230 saveFormDataEnabled = false; // Form data can be removed once the minimum API >= 26.
1233 // Store the default user agent.
1234 webViewDefaultUserAgent = mainWebView.getSettings().getUserAgentString();
1236 // Initialize the WebView title.
1237 webViewTitle = getString(R.string.no_title);
1239 // Initialize the favorite icon bitmap. `ContextCompat` must be used until API >= 21.
1240 Drawable favoriteIconDrawable = ContextCompat.getDrawable(getApplicationContext(), R.drawable.world);
1241 BitmapDrawable favoriteIconBitmapDrawable = (BitmapDrawable) favoriteIconDrawable;
1242 assert favoriteIconBitmapDrawable != null;
1243 favoriteIconDefaultBitmap = favoriteIconBitmapDrawable.getBitmap();
1245 // If the favorite icon is null, load the default.
1246 if (favoriteIconBitmap == null) {
1247 favoriteIconBitmap = favoriteIconDefaultBitmap;
1250 // Initialize the user agent array adapter and string array.
1251 userAgentNamesArray = ArrayAdapter.createFromResource(this, R.array.user_agent_names, R.layout.spinner_item);
1252 userAgentDataArray = resources.getStringArray(R.array.user_agent_data);
1254 // Apply the app settings from the shared preferences.
1257 // Instantiate the block list helper.
1258 BlockListHelper blockListHelper = new BlockListHelper();
1260 // Initialize the list of resource requests.
1261 resourceRequests = new ArrayList<>();
1263 // Parse the block lists.
1264 final ArrayList<List<String[]>> easyList = blockListHelper.parseBlockList(getAssets(), "blocklists/easylist.txt");
1265 final ArrayList<List<String[]>> easyPrivacy = blockListHelper.parseBlockList(getAssets(), "blocklists/easyprivacy.txt");
1266 final ArrayList<List<String[]>> fanboysAnnoyanceList = blockListHelper.parseBlockList(getAssets(), "blocklists/fanboy-annoyance.txt");
1267 final ArrayList<List<String[]>> fanboysSocialList = blockListHelper.parseBlockList(getAssets(), "blocklists/fanboy-social.txt");
1268 final ArrayList<List<String[]>> ultraPrivacy = blockListHelper.parseBlockList(getAssets(), "blocklists/ultraprivacy.txt");
1270 // Store the list versions.
1271 easyListVersion = easyList.get(0).get(0)[0];
1272 easyPrivacyVersion = easyPrivacy.get(0).get(0)[0];
1273 fanboysAnnoyanceVersion = fanboysAnnoyanceList.get(0).get(0)[0];
1274 fanboysSocialVersion = fanboysSocialList.get(0).get(0)[0];
1275 ultraPrivacyVersion = ultraPrivacy.get(0).get(0)[0];
1277 // Get a handle for the activity. This is used to update the requests counter while the navigation menu is open.
1278 Activity activity = this;
1280 mainWebView.setWebViewClient(new WebViewClient() {
1281 // `shouldOverrideUrlLoading` makes this `WebView` the default handler for URLs inside the app, so that links are not kicked out to other apps.
1282 // The deprecated `shouldOverrideUrlLoading` must be used until API >= 24.
1283 @SuppressWarnings("deprecation")
1285 public boolean shouldOverrideUrlLoading(WebView view, String url) {
1286 if (url.startsWith("http")) { // Load the URL in Privacy Browser.
1287 // Reset the formatted URL string so the page will load correctly if blocking of third-party requests is enabled.
1288 formattedUrlString = "";
1290 // Apply the domain settings for the new URL. `applyDomainSettings` doesn't do anything if the domain has not changed.
1291 boolean userAgentChanged = applyDomainSettings(url, true, false);
1293 // Check if the user agent has changed.
1294 if (userAgentChanged) {
1295 // Manually load the URL. The changing of the user agent will cause WebView to reload the previous URL.
1296 mainWebView.loadUrl(url, customHeaders);
1298 // Returning true indicates that Privacy Browser is manually handling the loading of the URL.
1301 // Returning false causes the current WebView to handle the URL and prevents it from adding redirects to the history list.
1304 } else if (url.startsWith("mailto:")) { // Load the email address in an external email program.
1305 // Use `ACTION_SENDTO` instead of `ACTION_SEND` so that only email programs are launched.
1306 Intent emailIntent = new Intent(Intent.ACTION_SENDTO);
1308 // Parse the url and set it as the data for the intent.
1309 emailIntent.setData(Uri.parse(url));
1311 // Open the email program in a new task instead of as part of Privacy Browser.
1312 emailIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
1315 startActivity(emailIntent);
1317 // Returning true indicates Privacy Browser is handling the URL by creating an intent.
1319 } else if (url.startsWith("tel:")) { // Load the phone number in the dialer.
1320 // Open the dialer and load the phone number, but wait for the user to place the call.
1321 Intent dialIntent = new Intent(Intent.ACTION_DIAL);
1323 // Add the phone number to the intent.
1324 dialIntent.setData(Uri.parse(url));
1326 // Open the dialer in a new task instead of as part of Privacy Browser.
1327 dialIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
1330 startActivity(dialIntent);
1332 // Returning true indicates Privacy Browser is handling the URL by creating an intent.
1334 } else { // Load a system chooser to select an app that can handle the URL.
1335 // Open an app that can handle the URL.
1336 Intent genericIntent = new Intent(Intent.ACTION_VIEW);
1338 // Add the URL to the intent.
1339 genericIntent.setData(Uri.parse(url));
1341 // List all apps that can handle the URL instead of just opening the first one.
1342 genericIntent.addCategory(Intent.CATEGORY_BROWSABLE);
1344 // Open the app in a new task instead of as part of Privacy Browser.
1345 genericIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
1347 // Start the app or display a snackbar if no app is available to handle the URL.
1349 startActivity(genericIntent);
1350 } catch (ActivityNotFoundException exception) {
1351 Snackbar.make(mainWebView, getString(R.string.unrecognized_url) + " " + url, Snackbar.LENGTH_SHORT).show();
1354 // Returning true indicates Privacy Browser is handling the URL by creating an intent.
1359 // Check requests against the block lists. The deprecated `shouldInterceptRequest()` must be used until minimum API >= 21.
1360 @SuppressWarnings("deprecation")
1362 public WebResourceResponse shouldInterceptRequest(WebView view, String url){
1363 // Create an empty web resource response to be used if the resource request is blocked.
1364 WebResourceResponse emptyWebResourceResponse = new WebResourceResponse("text/plain", "utf8", new ByteArrayInputStream("".getBytes()));
1366 // Reset the whitelist results tracker.
1367 whiteListResultStringArray = null;
1369 // Initialize the third party request tracker.
1370 boolean isThirdPartyRequest = false;
1372 // Initialize the current domain string.
1373 String currentDomain = "";
1375 // Nobody is happy when comparing null strings.
1376 if (!(formattedUrlString == null) && !(url == null)) {
1377 // Get the domain strings to URIs.
1378 Uri currentDomainUri = Uri.parse(formattedUrlString);
1379 Uri requestDomainUri = Uri.parse(url);
1381 // Get the domain host names.
1382 String currentBaseDomain = currentDomainUri.getHost();
1383 String requestBaseDomain = requestDomainUri.getHost();
1385 // Update the current domain variable.
1386 currentDomain = currentBaseDomain;
1388 // Only compare the current base domain and the request base domain if neither is null.
1389 if (!(currentBaseDomain == null) && !(requestBaseDomain == null)) {
1390 // Determine the current base domain.
1391 while (currentBaseDomain.indexOf(".", currentBaseDomain.indexOf(".") + 1) > 0) { // There is at least one subdomain.
1392 // Remove the first subdomain.
1393 currentBaseDomain = currentBaseDomain.substring(currentBaseDomain.indexOf(".") + 1);
1396 // Determine the request base domain.
1397 while (requestBaseDomain.indexOf(".", requestBaseDomain.indexOf(".") + 1) > 0) { // There is at least one subdomain.
1398 // Remove the first subdomain.
1399 requestBaseDomain = requestBaseDomain.substring(requestBaseDomain.indexOf(".") + 1);
1402 // Update the third party request tracker.
1403 isThirdPartyRequest = !currentBaseDomain.equals(requestBaseDomain);
1407 // Block third-party requests if enabled.
1408 if (isThirdPartyRequest && blockAllThirdPartyRequests) {
1409 // Increment the blocked requests counters.
1411 thirdPartyBlockedRequests++;
1413 // Update the titles of the blocklist menu items. This must be run from the UI thread.
1414 activity.runOnUiThread(() -> {
1415 navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
1416 blocklistsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
1417 blockAllThirdPartyRequestsMenuItem.setTitle(thirdPartyBlockedRequests + " - " + getString(R.string.block_all_third_party_requests));
1420 // Add the request to the log.
1421 resourceRequests.add(new String[]{String.valueOf(REQUEST_THIRD_PARTY), url});
1423 // Return an empty web resource response.
1424 return emptyWebResourceResponse;
1427 // Check UltraPrivacy if it is enabled.
1428 if (ultraPrivacyEnabled) {
1429 if (blockListHelper.isBlocked(currentDomain, url, isThirdPartyRequest, ultraPrivacy)) {
1430 // Increment the blocked requests counters.
1432 ultraPrivacyBlockedRequests++;
1434 // Update the titles of the blocklist menu items. This must be run from the UI thread.
1435 activity.runOnUiThread(() -> {
1436 navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
1437 blocklistsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
1438 ultraPrivacyMenuItem.setTitle(ultraPrivacyBlockedRequests + " - " + getString(R.string.ultraprivacy));
1441 // The resource request was blocked. Return an empty web resource response.
1442 return emptyWebResourceResponse;
1445 // If the whitelist result is not null, the request has been allowed by UltraPrivacy.
1446 if (whiteListResultStringArray != null) {
1447 // Add a whitelist entry to the resource requests array.
1448 resourceRequests.add(whiteListResultStringArray);
1450 // The resource request has been allowed by UltraPrivacy. `return null` loads the requested resource.
1455 // Check EasyList if it is enabled.
1456 if (easyListEnabled) {
1457 if (blockListHelper.isBlocked(currentDomain, url, isThirdPartyRequest, easyList)) {
1458 // Increment the blocked requests counters.
1460 easyListBlockedRequests++;
1462 // Update the titles of the blocklist menu items. This must be run from the UI thread.
1463 activity.runOnUiThread(() -> {
1464 navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
1465 blocklistsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
1466 easyListMenuItem.setTitle(easyListBlockedRequests + " - " + getString(R.string.easylist));
1469 // Reset the whitelist results tracker (because otherwise it will sometimes add results to the list due to a race condition).
1470 whiteListResultStringArray = null;
1472 // The resource request was blocked. Return an empty web resource response.
1473 return emptyWebResourceResponse;
1477 // Check EasyPrivacy if it is enabled.
1478 if (easyPrivacyEnabled) {
1479 if (blockListHelper.isBlocked(currentDomain, url, isThirdPartyRequest, easyPrivacy)) {
1480 // Increment the blocked requests counters.
1482 easyPrivacyBlockedRequests++;
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 easyPrivacyMenuItem.setTitle(easyPrivacyBlockedRequests + " - " + getString(R.string.easyprivacy));
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 Fanboy’s Annoyance List if it is enabled.
1500 if (fanboysAnnoyanceListEnabled) {
1501 if (blockListHelper.isBlocked(currentDomain, url, isThirdPartyRequest, fanboysAnnoyanceList)) {
1502 // Increment the blocked requests counters.
1504 fanboysAnnoyanceListBlockedRequests++;
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 fanboysAnnoyanceListMenuItem.setTitle(fanboysAnnoyanceListBlockedRequests + " - " + getString(R.string.fanboys_annoyance_list));
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;
1519 } else if (fanboysSocialBlockingListEnabled){ // Only check Fanboy’s Social Blocking List if Fanboy’s Annoyance List is disabled.
1520 if (blockListHelper.isBlocked(currentDomain, url, isThirdPartyRequest, fanboysSocialList)) {
1521 // Increment the blocked requests counters.
1523 fanboysSocialBlockingListBlockedRequests++;
1525 // Update the titles of the blocklist menu items. This must be run from the UI thread.
1526 activity.runOnUiThread(() -> {
1527 navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
1528 blocklistsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
1529 fanboysSocialBlockingListMenuItem.setTitle(fanboysSocialBlockingListBlockedRequests + " - " + getString(R.string.fanboys_social_blocking_list));
1532 // Reset the whitelist results tracker (because otherwise it will sometimes add results to the list due to a race condition).
1533 whiteListResultStringArray = null;
1535 // The resource request was blocked. Return an empty web resource response.
1536 return emptyWebResourceResponse;
1540 // Add the request to the log because it hasn't been processed by any of the previous checks.
1541 if (whiteListResultStringArray != null ) { // The request was processed by a whitelist.
1542 resourceRequests.add(whiteListResultStringArray);
1543 } else { // The request didn't match any blocklist entry. Log it as a default request.
1544 resourceRequests.add(new String[]{String.valueOf(REQUEST_DEFAULT), url});
1547 // The resource request has not been blocked. `return null` loads the requested resource.
1551 // Handle HTTP authentication requests.
1553 public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, String host, String realm) {
1554 // Store `handler` so it can be accessed from `onHttpAuthenticationCancel()` and `onHttpAuthenticationProceed()`.
1555 httpAuthHandler = handler;
1557 // Display the HTTP authentication dialog.
1558 DialogFragment httpAuthenticationDialogFragment = HttpAuthenticationDialog.displayDialog(host, realm);
1559 httpAuthenticationDialogFragment.show(fragmentManager, getString(R.string.http_authentication));
1562 // Update the URL in urlTextBox when the page starts to load.
1564 public void onPageStarted(WebView view, String url, Bitmap favicon) {
1565 // 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.
1566 // This is also used to determine when to check for pinned mismatches.
1567 urlIsLoading = true;
1569 // Reset the list of host IP addresses.
1570 currentHostIpAddresses = "";
1572 // Reset the list of resource requests.
1573 resourceRequests.clear();
1575 // Initialize the counters for requests blocked by each blocklist.
1576 blockedRequests = 0;
1577 easyListBlockedRequests = 0;
1578 easyPrivacyBlockedRequests = 0;
1579 fanboysAnnoyanceListBlockedRequests = 0;
1580 fanboysSocialBlockingListBlockedRequests = 0;
1581 ultraPrivacyBlockedRequests = 0;
1582 thirdPartyBlockedRequests = 0;
1584 // If night mode is enabled, hide `mainWebView` until after the night mode CSS is applied.
1586 mainWebView.setVisibility(View.INVISIBLE);
1589 // Hide the keyboard.
1590 inputMethodManager.hideSoftInputFromWindow(mainWebView.getWindowToken(), 0);
1592 // Check to see if Privacy Browser is waiting on Orbot.
1593 if (!waitingForOrbot) { // Process the URL.
1594 // The formatted URL string must be updated at the beginning of the load, so that if the user toggles JavaScript during the load the new website is reloaded.
1595 formattedUrlString = url;
1597 // Display the formatted URL text.
1598 urlTextBox.setText(formattedUrlString);
1600 // Apply text highlighting to `urlTextBox`.
1603 // Get a URI for the current URL.
1604 Uri currentUri = Uri.parse(formattedUrlString);
1606 // Get the IP addresses for the host.
1607 new GetHostIpAddresses(activity).execute(currentUri.getHost());
1609 // Apply any custom domain settings if the URL was loaded by navigating history.
1610 if (navigatingHistory) {
1611 // Apply the domain settings.
1612 boolean userAgentChanged = applyDomainSettings(url, true, false);
1614 // Reset `navigatingHistory`.
1615 navigatingHistory = false;
1617 // Manually load the URL if the user agent has changed, which will have caused the previous URL to be reloaded.
1618 if (userAgentChanged) {
1619 loadUrl(formattedUrlString);
1623 // Replace Refresh with Stop if the menu item has been created. (The WebView typically begins loading before the menu items are instantiated.)
1624 if (refreshMenuItem != null) {
1626 refreshMenuItem.setTitle(R.string.stop);
1628 // If the icon is displayed in the AppBar, set it according to the theme.
1629 if (displayAdditionalAppBarIcons) {
1631 refreshMenuItem.setIcon(R.drawable.close_dark);
1633 refreshMenuItem.setIcon(R.drawable.close_light);
1640 // It is necessary to update `formattedUrlString` and `urlTextBox` after the page finishes loading because the final URL can change during load.
1642 public void onPageFinished(WebView view, String url) {
1643 // Reset the wide view port if it has been turned off by the waiting for Orbot message.
1644 if (!waitingForOrbot) {
1645 // Only use a wide view port if the URL starts with `http`, not for `file://` and `content://`.
1646 mainWebView.getSettings().setUseWideViewPort(url.startsWith("http"));
1649 // Flush any cookies to persistent storage. `CookieManager` has become very lazy about flushing cookies in recent versions.
1650 if (firstPartyCookiesEnabled && Build.VERSION.SDK_INT >= 21) {
1651 cookieManager.flush();
1654 // Update the Refresh menu item if it has been created.
1655 if (refreshMenuItem != null) {
1656 // Reset the Refresh title.
1657 refreshMenuItem.setTitle(R.string.refresh);
1659 // If the icon is displayed in the AppBar, reset it according to the theme.
1660 if (displayAdditionalAppBarIcons) {
1662 refreshMenuItem.setIcon(R.drawable.refresh_enabled_dark);
1664 refreshMenuItem.setIcon(R.drawable.refresh_enabled_light);
1671 // Clear the cache and history if Incognito Mode is enabled.
1672 if (incognitoModeEnabled) {
1673 // Clear the cache. `true` includes disk files.
1674 mainWebView.clearCache(true);
1676 // Clear the back/forward history.
1677 mainWebView.clearHistory();
1679 // Manually delete cache folders.
1681 // Delete the main cache directory.
1682 privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/cache");
1684 // Delete the secondary `Service Worker` cache directory.
1685 // A `String[]` must be used because the directory contains a space and `Runtime.exec` will not escape the string correctly otherwise.
1686 privacyBrowserRuntime.exec(new String[] {"rm", "-rf", privateDataDirectoryString + "/app_webview/Service Worker/"});
1687 } catch (IOException e) {
1688 // Do nothing if an error is thrown.
1692 // Update the URL text box and apply domain settings if not waiting on Orbot.
1693 if (!waitingForOrbot) {
1694 // Check to see if `WebView` has set `url` to be `about:blank`.
1695 if (url.equals("about:blank")) { // `WebView` is blank, so `formattedUrlString` should be `""` and `urlTextBox` should display a hint.
1696 // Set `formattedUrlString` to `""`.
1697 formattedUrlString = "";
1699 urlTextBox.setText(formattedUrlString);
1701 // Request focus for `urlTextBox`.
1702 urlTextBox.requestFocus();
1704 // Display the keyboard.
1705 inputMethodManager.showSoftInput(urlTextBox, 0);
1707 // Apply the domain settings. This clears any settings from the previous domain.
1708 applyDomainSettings(formattedUrlString, true, false);
1709 } else { // `WebView` has loaded a webpage.
1710 // Set the formatted URL string. Getting the URL from the WebView instead of using the one provided by `onPageFinished` makes websites like YouTube function correctly.
1711 formattedUrlString = mainWebView.getUrl();
1713 // Only update the URL text box if the user is not typing in it.
1714 if (!urlTextBox.hasFocus()) {
1715 // Display the formatted URL text.
1716 urlTextBox.setText(formattedUrlString);
1718 // Apply text highlighting to `urlTextBox`.
1723 // Store the SSL certificate so it can be accessed from `ViewSslCertificateDialog` and `PinnedMismatchDialog`.
1724 sslCertificate = mainWebView.getCertificate();
1726 // Check the current website information against any pinned domain information if the current IP addresses have been loaded.
1727 if (!gettingIpAddresses) {
1728 checkPinnedMismatch();
1732 // Reset `urlIsLoading`, which is used to prevent reloads on redirect if the user agent changes. It is also used to determine when to check for pinned mismatches.
1733 urlIsLoading = false;
1736 // Handle SSL Certificate errors.
1738 public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
1739 // Get the current website SSL certificate.
1740 SslCertificate currentWebsiteSslCertificate = error.getCertificate();
1742 // Extract the individual pieces of information from the current website SSL certificate.
1743 String currentWebsiteIssuedToCName = currentWebsiteSslCertificate.getIssuedTo().getCName();
1744 String currentWebsiteIssuedToOName = currentWebsiteSslCertificate.getIssuedTo().getOName();
1745 String currentWebsiteIssuedToUName = currentWebsiteSslCertificate.getIssuedTo().getUName();
1746 String currentWebsiteIssuedByCName = currentWebsiteSslCertificate.getIssuedBy().getCName();
1747 String currentWebsiteIssuedByOName = currentWebsiteSslCertificate.getIssuedBy().getOName();
1748 String currentWebsiteIssuedByUName = currentWebsiteSslCertificate.getIssuedBy().getUName();
1749 Date currentWebsiteSslStartDate = currentWebsiteSslCertificate.getValidNotBeforeDate();
1750 Date currentWebsiteSslEndDate = currentWebsiteSslCertificate.getValidNotAfterDate();
1752 // Proceed to the website if the current SSL website certificate matches the pinned domain certificate.
1753 if (pinnedSslCertificate &&
1754 currentWebsiteIssuedToCName.equals(pinnedSslIssuedToCName) && currentWebsiteIssuedToOName.equals(pinnedSslIssuedToOName) &&
1755 currentWebsiteIssuedToUName.equals(pinnedSslIssuedToUName) && currentWebsiteIssuedByCName.equals(pinnedSslIssuedByCName) &&
1756 currentWebsiteIssuedByOName.equals(pinnedSslIssuedByOName) && currentWebsiteIssuedByUName.equals(pinnedSslIssuedByUName) &&
1757 currentWebsiteSslStartDate.equals(pinnedSslStartDate) && currentWebsiteSslEndDate.equals(pinnedSslEndDate)) {
1759 // An SSL certificate is pinned and matches the current domain certificate. Proceed to the website without displaying an error.
1761 } else { // Either there isn't a pinned SSL certificate or it doesn't match the current website certificate.
1762 // Store `handler` so it can be accesses from `onSslErrorCancel()` and `onSslErrorProceed()`.
1763 sslErrorHandler = handler;
1765 // Display the SSL error `AlertDialog`.
1766 DialogFragment sslCertificateErrorDialogFragment = SslCertificateErrorDialog.displayDialog(error);
1767 sslCertificateErrorDialogFragment.show(fragmentManager, getString(R.string.ssl_certificate_error));
1772 // Get the intent that started the app.
1773 Intent launchingIntent = getIntent();
1775 // Get the information from the intent.
1776 String launchingIntentAction = launchingIntent.getAction();
1777 Uri launchingIntentUriData = launchingIntent.getData();
1779 // If the intent action is a web search, perform the search.
1780 if ((launchingIntentAction != null) && launchingIntentAction.equals(Intent.ACTION_WEB_SEARCH)) {
1781 // Create an encoded URL string.
1782 String encodedUrlString;
1784 // Sanitize the search input and convert it to a search.
1786 encodedUrlString = URLEncoder.encode(launchingIntent.getStringExtra(SearchManager.QUERY), "UTF-8");
1787 } catch (UnsupportedEncodingException exception) {
1788 encodedUrlString = "";
1791 // Add the base search URL.
1792 formattedUrlString = searchURL + encodedUrlString;
1793 } else if (launchingIntentUriData != null){ // Check to see if the intent contains a new URL.
1794 // Set the formatted URL string.
1795 formattedUrlString = launchingIntentUriData.toString();
1798 // Load the website if not waiting for Orbot to connect.
1799 if (!waitingForOrbot) {
1800 loadUrl(formattedUrlString);
1805 protected void onNewIntent(Intent intent) {
1806 // Sets the new intent as the activity intent, so that any future `getIntent()`s pick up this one instead of creating a new activity.
1809 // Get the information from the intent.
1810 String intentAction = intent.getAction();
1811 Uri intentUriData = intent.getData();
1813 // If the intent action is a web search, perform the search.
1814 if ((intentAction != null) && intentAction.equals(Intent.ACTION_WEB_SEARCH)) {
1815 // Create an encoded URL string.
1816 String encodedUrlString;
1818 // Sanitize the search input and convert it to a search.
1820 encodedUrlString = URLEncoder.encode(intent.getStringExtra(SearchManager.QUERY), "UTF-8");
1821 } catch (UnsupportedEncodingException exception) {
1822 encodedUrlString = "";
1825 // Add the base search URL.
1826 formattedUrlString = searchURL + encodedUrlString;
1827 } else if (intentUriData != null){ // Check to see if the intent contains a new URL.
1828 // Set the formatted URL string.
1829 formattedUrlString = intentUriData.toString();
1833 loadUrl(formattedUrlString);
1835 // Get a handle for the drawer layout.
1836 DrawerLayout drawerLayout = findViewById(R.id.drawerlayout);
1838 // Close the navigation drawer if it is open.
1839 if (drawerLayout.isDrawerVisible(GravityCompat.START)) {
1840 drawerLayout.closeDrawer(GravityCompat.START);
1843 // Close the bookmarks drawer if it is open.
1844 if (drawerLayout.isDrawerVisible(GravityCompat.END)) {
1845 drawerLayout.closeDrawer(GravityCompat.END);
1848 // Clear the keyboard if displayed and remove the focus on the urlTextBar if it has it.
1849 mainWebView.requestFocus();
1853 public void onRestart() {
1854 // Run the default commands.
1857 // Make sure Orbot is running if Privacy Browser is proxying through Orbot.
1858 if (proxyThroughOrbot) {
1859 // Request Orbot to start. If Orbot is already running no hard will be caused by this request.
1860 Intent orbotIntent = new Intent("org.torproject.android.intent.action.START");
1862 // Send the intent to the Orbot package.
1863 orbotIntent.setPackage("org.torproject.android");
1866 sendBroadcast(orbotIntent);
1869 // Apply the app settings if returning from the Settings activity..
1870 if (reapplyAppSettingsOnRestart) {
1871 // Apply the app settings.
1874 // Reload the webpage if displaying of images has been disabled in the Settings activity.
1875 if (reloadOnRestart) {
1876 // Reload `mainWebView`.
1877 mainWebView.reload();
1879 // Reset `reloadOnRestartBoolean`.
1880 reloadOnRestart = false;
1883 // Reset the return from settings flag.
1884 reapplyAppSettingsOnRestart = false;
1887 // Apply the domain settings if returning from the Domains activity.
1888 if (reapplyDomainSettingsOnRestart) {
1889 // Reapply the domain settings.
1890 applyDomainSettings(formattedUrlString, false, true);
1892 // Reset `reapplyDomainSettingsOnRestart`.
1893 reapplyDomainSettingsOnRestart = false;
1896 // Load the URL on restart to apply changes to night mode.
1897 if (loadUrlOnRestart) {
1898 // Load the current `formattedUrlString`.
1899 loadUrl(formattedUrlString);
1901 // Reset `loadUrlOnRestart.
1902 loadUrlOnRestart = false;
1905 // Update the bookmarks drawer if returning from the Bookmarks activity.
1906 if (restartFromBookmarksActivity) {
1907 // Get a handle for the drawer layout.
1908 DrawerLayout drawerLayout = findViewById(R.id.drawerlayout);
1910 // Close the bookmarks drawer.
1911 drawerLayout.closeDrawer(GravityCompat.END);
1913 // Reload the bookmarks drawer.
1914 loadBookmarksFolder();
1916 // Reset `restartFromBookmarksActivity`.
1917 restartFromBookmarksActivity = false;
1920 // Update the privacy icon. `true` runs `invalidateOptionsMenu` as the last step. This can be important if the screen was rotated.
1921 updatePrivacyIcons(true);
1924 // `onResume()` runs after `onStart()`, which runs after `onCreate()` and `onRestart()`.
1926 public void onResume() {
1927 // Run the default commands.
1930 // Resume JavaScript (if enabled).
1931 mainWebView.resumeTimers();
1933 // Resume `mainWebView`.
1934 mainWebView.onResume();
1936 // Display a message to the user if waiting for Orbot.
1937 if (waitingForOrbot && !orbotStatus.equals("ON")) {
1938 // Disable the wide view port so that the waiting for Orbot text is displayed correctly.
1939 mainWebView.getSettings().setUseWideViewPort(false);
1941 // Load a waiting page. `null` specifies no encoding, which defaults to ASCII.
1942 mainWebView.loadData(waitingForOrbotHtmlString, "text/html", null);
1945 if (displayingFullScreenVideo || inFullScreenBrowsingMode) {
1946 // Get a handle for the root frame layouts.
1947 FrameLayout rootFrameLayout = findViewById(R.id.root_framelayout);
1949 // Remove the translucent status flag. This is necessary so the root frame layout can fill the entire screen.
1950 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
1952 /* Hide the system bars.
1953 * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
1954 * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
1955 * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
1956 * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
1958 rootFrameLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
1959 View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
1960 } else if (BuildConfig.FLAVOR.contentEquals("free")) { // Resume the adView for the free flavor.
1962 AdHelper.resumeAd(findViewById(R.id.adview));
1967 public void onPause() {
1968 // Run the default commands.
1971 // Pause `mainWebView`.
1972 mainWebView.onPause();
1974 // Stop all JavaScript.
1975 mainWebView.pauseTimers();
1977 // Pause the ad or it will continue to consume resources in the background on the free flavor.
1978 if (BuildConfig.FLAVOR.contentEquals("free")) {
1980 AdHelper.pauseAd(findViewById(R.id.adview));
1985 public void onDestroy() {
1986 // Unregister the Orbot status broadcast receiver.
1987 this.unregisterReceiver(orbotStatusBroadcastReceiver);
1989 // Close the bookmarks cursor and database.
1990 bookmarksCursor.close();
1991 bookmarksDatabaseHelper.close();
1993 // Run the default commands.
1998 public boolean onCreateOptionsMenu(Menu menu) {
1999 // Inflate the menu. This adds items to the action bar if it is present.
2000 getMenuInflater().inflate(R.menu.webview_options_menu, menu);
2002 // Set mainMenu so it can be used by `onOptionsItemSelected()` and `updatePrivacyIcons`.
2005 // Set the initial status of the privacy icons. `false` does not call `invalidateOptionsMenu` as the last step.
2006 updatePrivacyIcons(false);
2008 // Get handles for the menu items.
2009 MenuItem toggleFirstPartyCookiesMenuItem = menu.findItem(R.id.toggle_first_party_cookies);
2010 MenuItem toggleThirdPartyCookiesMenuItem = menu.findItem(R.id.toggle_third_party_cookies);
2011 MenuItem toggleDomStorageMenuItem = menu.findItem(R.id.toggle_dom_storage);
2012 MenuItem toggleSaveFormDataMenuItem = menu.findItem(R.id.toggle_save_form_data); // Form data can be removed once the minimum API >= 26.
2013 MenuItem clearFormDataMenuItem = menu.findItem(R.id.clear_form_data); // Form data can be removed once the minimum API >= 26.
2014 refreshMenuItem = menu.findItem(R.id.refresh);
2015 blocklistsMenuItem = menu.findItem(R.id.blocklists);
2016 easyListMenuItem = menu.findItem(R.id.easylist);
2017 easyPrivacyMenuItem = menu.findItem(R.id.easyprivacy);
2018 fanboysAnnoyanceListMenuItem = menu.findItem(R.id.fanboys_annoyance_list);
2019 fanboysSocialBlockingListMenuItem = menu.findItem(R.id.fanboys_social_blocking_list);
2020 ultraPrivacyMenuItem = menu.findItem(R.id.ultraprivacy);
2021 blockAllThirdPartyRequestsMenuItem = menu.findItem(R.id.block_all_third_party_requests);
2022 MenuItem adConsentMenuItem = menu.findItem(R.id.ad_consent);
2024 // Only display third-party cookies if API >= 21
2025 toggleThirdPartyCookiesMenuItem.setVisible(Build.VERSION.SDK_INT >= 21);
2027 // Only display the form data menu items if the API < 26.
2028 toggleSaveFormDataMenuItem.setVisible(Build.VERSION.SDK_INT < 26);
2029 clearFormDataMenuItem.setVisible(Build.VERSION.SDK_INT < 26);
2031 // Only show Ad Consent if this is the free flavor.
2032 adConsentMenuItem.setVisible(BuildConfig.FLAVOR.contentEquals("free"));
2034 // Get the shared preference values.
2035 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
2037 // Get the status of the additional AppBar icons.
2038 displayAdditionalAppBarIcons = sharedPreferences.getBoolean("display_additional_app_bar_icons", false);
2040 // 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.
2041 if (displayAdditionalAppBarIcons) {
2042 toggleFirstPartyCookiesMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
2043 toggleDomStorageMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
2044 refreshMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
2045 } else { //Do not display the additional icons.
2046 toggleFirstPartyCookiesMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
2047 toggleDomStorageMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
2048 refreshMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
2051 // Replace Refresh with Stop if a URL is already loading.
2054 refreshMenuItem.setTitle(R.string.stop);
2056 // If the icon is displayed in the AppBar, set it according to the theme.
2057 if (displayAdditionalAppBarIcons) {
2059 refreshMenuItem.setIcon(R.drawable.close_dark);
2061 refreshMenuItem.setIcon(R.drawable.close_light);
2070 public boolean onPrepareOptionsMenu(Menu menu) {
2071 // Get a handle for the swipe refresh layout.
2072 SwipeRefreshLayout swipeRefreshLayout = findViewById(R.id.swiperefreshlayout);
2074 // Get handles for the menu items.
2075 MenuItem addOrEditDomain = menu.findItem(R.id.add_or_edit_domain);
2076 MenuItem toggleFirstPartyCookiesMenuItem = menu.findItem(R.id.toggle_first_party_cookies);
2077 MenuItem toggleThirdPartyCookiesMenuItem = menu.findItem(R.id.toggle_third_party_cookies);
2078 MenuItem toggleDomStorageMenuItem = menu.findItem(R.id.toggle_dom_storage);
2079 MenuItem toggleSaveFormDataMenuItem = menu.findItem(R.id.toggle_save_form_data); // Form data can be removed once the minimum API >= 26.
2080 MenuItem clearDataMenuItem = menu.findItem(R.id.clear_data);
2081 MenuItem clearCookiesMenuItem = menu.findItem(R.id.clear_cookies);
2082 MenuItem clearDOMStorageMenuItem = menu.findItem(R.id.clear_dom_storage);
2083 MenuItem clearFormDataMenuItem = menu.findItem(R.id.clear_form_data); // Form data can be removed once the minimum API >= 26.
2084 MenuItem fontSizeMenuItem = menu.findItem(R.id.font_size);
2085 MenuItem swipeToRefreshMenuItem = menu.findItem(R.id.swipe_to_refresh);
2086 MenuItem displayImagesMenuItem = menu.findItem(R.id.display_images);
2087 MenuItem nightModeMenuItem = menu.findItem(R.id.night_mode);
2088 MenuItem proxyThroughOrbotMenuItem = menu.findItem(R.id.proxy_through_orbot);
2090 // Set the text for the domain menu item.
2091 if (domainSettingsApplied) {
2092 addOrEditDomain.setTitle(R.string.edit_domain_settings);
2094 addOrEditDomain.setTitle(R.string.add_domain_settings);
2097 // Set the status of the menu item checkboxes.
2098 toggleFirstPartyCookiesMenuItem.setChecked(firstPartyCookiesEnabled);
2099 toggleThirdPartyCookiesMenuItem.setChecked(thirdPartyCookiesEnabled);
2100 toggleDomStorageMenuItem.setChecked(domStorageEnabled);
2101 toggleSaveFormDataMenuItem.setChecked(saveFormDataEnabled); // Form data can be removed once the minimum API >= 26.
2102 easyListMenuItem.setChecked(easyListEnabled);
2103 easyPrivacyMenuItem.setChecked(easyPrivacyEnabled);
2104 fanboysAnnoyanceListMenuItem.setChecked(fanboysAnnoyanceListEnabled);
2105 fanboysSocialBlockingListMenuItem.setChecked(fanboysSocialBlockingListEnabled);
2106 ultraPrivacyMenuItem.setChecked(ultraPrivacyEnabled);
2107 blockAllThirdPartyRequestsMenuItem.setChecked(blockAllThirdPartyRequests);
2108 swipeToRefreshMenuItem.setChecked(swipeRefreshLayout.isEnabled());
2109 displayImagesMenuItem.setChecked(mainWebView.getSettings().getLoadsImagesAutomatically());
2110 nightModeMenuItem.setChecked(nightMode);
2111 proxyThroughOrbotMenuItem.setChecked(proxyThroughOrbot);
2113 // Enable third-party cookies if first-party cookies are enabled.
2114 toggleThirdPartyCookiesMenuItem.setEnabled(firstPartyCookiesEnabled);
2116 // Enable DOM Storage if JavaScript is enabled.
2117 toggleDomStorageMenuItem.setEnabled(javaScriptEnabled);
2119 // Enable Clear Cookies if there are any.
2120 clearCookiesMenuItem.setEnabled(cookieManager.hasCookies());
2122 // Get a count of the number of files in the Local Storage directory.
2123 File localStorageDirectory = new File (privateDataDirectoryString + "/app_webview/Local Storage/");
2124 int localStorageDirectoryNumberOfFiles = 0;
2125 if (localStorageDirectory.exists()) {
2126 localStorageDirectoryNumberOfFiles = localStorageDirectory.list().length;
2129 // Get a count of the number of files in the IndexedDB directory.
2130 File indexedDBDirectory = new File (privateDataDirectoryString + "/app_webview/IndexedDB");
2131 int indexedDBDirectoryNumberOfFiles = 0;
2132 if (indexedDBDirectory.exists()) {
2133 indexedDBDirectoryNumberOfFiles = indexedDBDirectory.list().length;
2136 // Enable Clear DOM Storage if there is any.
2137 clearDOMStorageMenuItem.setEnabled(localStorageDirectoryNumberOfFiles > 0 || indexedDBDirectoryNumberOfFiles > 0);
2139 // Enable Clear Form Data is there is any. This can be removed once the minimum API >= 26.
2140 if (Build.VERSION.SDK_INT < 26) {
2141 WebViewDatabase mainWebViewDatabase = WebViewDatabase.getInstance(this);
2142 clearFormDataMenuItem.setEnabled(mainWebViewDatabase.hasFormData());
2144 // Disable clear form data because it is not supported on current version of Android.
2145 clearFormDataMenuItem.setEnabled(false);
2148 // Enable Clear Data if any of the submenu items are enabled.
2149 clearDataMenuItem.setEnabled(clearCookiesMenuItem.isEnabled() || clearDOMStorageMenuItem.isEnabled() || clearFormDataMenuItem.isEnabled());
2151 // Disable Fanboy's Social Blocking List if Fanboy's Annoyance List is checked.
2152 fanboysSocialBlockingListMenuItem.setEnabled(!fanboysAnnoyanceListEnabled);
2154 // Initialize the display names for the blocklists with the number of blocked requests.
2155 blocklistsMenuItem.setTitle(getString(R.string.blocklists) + " - " + blockedRequests);
2156 easyListMenuItem.setTitle(easyListBlockedRequests + " - " + getString(R.string.easylist));
2157 easyPrivacyMenuItem.setTitle(easyPrivacyBlockedRequests + " - " + getString(R.string.easyprivacy));
2158 fanboysAnnoyanceListMenuItem.setTitle(fanboysAnnoyanceListBlockedRequests + " - " + getString(R.string.fanboys_annoyance_list));
2159 fanboysSocialBlockingListMenuItem.setTitle(fanboysSocialBlockingListBlockedRequests + " - " + getString(R.string.fanboys_social_blocking_list));
2160 ultraPrivacyMenuItem.setTitle(ultraPrivacyBlockedRequests + " - " + getString(R.string.ultraprivacy));
2161 blockAllThirdPartyRequestsMenuItem.setTitle(thirdPartyBlockedRequests + " - " + getString(R.string.block_all_third_party_requests));
2163 // Get the current user agent.
2164 String currentUserAgent = mainWebView.getSettings().getUserAgentString();
2166 // Select the current user agent menu item. A switch statement cannot be used because the user agents are not compile time constants.
2167 if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[0])) { // Privacy Browser.
2168 menu.findItem(R.id.user_agent_privacy_browser).setChecked(true);
2169 } else if (currentUserAgent.equals(webViewDefaultUserAgent)) { // WebView Default.
2170 menu.findItem(R.id.user_agent_webview_default).setChecked(true);
2171 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[2])) { // Firefox on Android.
2172 menu.findItem(R.id.user_agent_firefox_on_android).setChecked(true);
2173 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[3])) { // Chrome on Android.
2174 menu.findItem(R.id.user_agent_chrome_on_android).setChecked(true);
2175 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[4])) { // Safari on iOS.
2176 menu.findItem(R.id.user_agent_safari_on_ios).setChecked(true);
2177 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[5])) { // Firefox on Linux.
2178 menu.findItem(R.id.user_agent_firefox_on_linux).setChecked(true);
2179 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[6])) { // Chromium on Linux.
2180 menu.findItem(R.id.user_agent_chromium_on_linux).setChecked(true);
2181 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[7])) { // Firefox on Windows.
2182 menu.findItem(R.id.user_agent_firefox_on_windows).setChecked(true);
2183 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[8])) { // Chrome on Windows.
2184 menu.findItem(R.id.user_agent_chrome_on_windows).setChecked(true);
2185 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[9])) { // Edge on Windows.
2186 menu.findItem(R.id.user_agent_edge_on_windows).setChecked(true);
2187 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[10])) { // Internet Explorer on Windows.
2188 menu.findItem(R.id.user_agent_internet_explorer_on_windows).setChecked(true);
2189 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[11])) { // Safari on macOS.
2190 menu.findItem(R.id.user_agent_safari_on_macos).setChecked(true);
2191 } else { // Custom user agent.
2192 menu.findItem(R.id.user_agent_custom).setChecked(true);
2195 // Initialize font size variables.
2196 int fontSize = mainWebView.getSettings().getTextZoom();
2197 String fontSizeTitle;
2198 MenuItem selectedFontSizeMenuItem;
2200 // Prepare the font size title and current size menu item.
2203 fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.twenty_five_percent);
2204 selectedFontSizeMenuItem = menu.findItem(R.id.font_size_twenty_five_percent);
2208 fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.fifty_percent);
2209 selectedFontSizeMenuItem = menu.findItem(R.id.font_size_fifty_percent);
2213 fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.seventy_five_percent);
2214 selectedFontSizeMenuItem = menu.findItem(R.id.font_size_seventy_five_percent);
2218 fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.one_hundred_percent);
2219 selectedFontSizeMenuItem = menu.findItem(R.id.font_size_one_hundred_percent);
2223 fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.one_hundred_twenty_five_percent);
2224 selectedFontSizeMenuItem = menu.findItem(R.id.font_size_one_hundred_twenty_five_percent);
2228 fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.one_hundred_fifty_percent);
2229 selectedFontSizeMenuItem = menu.findItem(R.id.font_size_one_hundred_fifty_percent);
2233 fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.one_hundred_seventy_five_percent);
2234 selectedFontSizeMenuItem = menu.findItem(R.id.font_size_one_hundred_seventy_five_percent);
2238 fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.two_hundred_percent);
2239 selectedFontSizeMenuItem = menu.findItem(R.id.font_size_two_hundred_percent);
2243 fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.one_hundred_percent);
2244 selectedFontSizeMenuItem = menu.findItem(R.id.font_size_one_hundred_percent);
2248 // Set the font size title and select the current size menu item.
2249 fontSizeMenuItem.setTitle(fontSizeTitle);
2250 selectedFontSizeMenuItem.setChecked(true);
2252 // Run all the other default commands.
2253 super.onPrepareOptionsMenu(menu);
2255 // Display the menu.
2260 // Remove Android Studio's warning about the dangers of using SetJavaScriptEnabled.
2261 @SuppressLint("SetJavaScriptEnabled")
2262 // removeAllCookies is deprecated, but it is required for API < 21.
2263 @SuppressWarnings("deprecation")
2264 public boolean onOptionsItemSelected(MenuItem menuItem) {
2265 // Reenter full screen browsing mode if it was interrupted by the options menu. <https://redmine.stoutner.com/issues/389>
2266 if (inFullScreenBrowsingMode) {
2267 // Remove the translucent status flag. This is necessary so the root frame layout can fill the entire screen.
2268 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
2270 FrameLayout rootFrameLayout = findViewById(R.id.root_framelayout);
2272 /* Hide the system bars.
2273 * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
2274 * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
2275 * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
2276 * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
2278 rootFrameLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
2279 View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
2282 // Get the selected menu item ID.
2283 int menuItemId = menuItem.getItemId();
2285 // Run the commands that correlate to the selected menu item.
2286 switch (menuItemId) {
2287 case R.id.toggle_javascript:
2288 // Switch the status of javaScriptEnabled.
2289 javaScriptEnabled = !javaScriptEnabled;
2291 // Apply the new JavaScript status.
2292 mainWebView.getSettings().setJavaScriptEnabled(javaScriptEnabled);
2294 // Update the privacy icon. `true` runs `invalidateOptionsMenu` as the last step.
2295 updatePrivacyIcons(true);
2297 // Display a `Snackbar`.
2298 if (javaScriptEnabled) { // JavaScrip is enabled.
2299 Snackbar.make(findViewById(R.id.main_webview), R.string.javascript_enabled, Snackbar.LENGTH_SHORT).show();
2300 } else if (firstPartyCookiesEnabled) { // JavaScript is disabled, but first-party cookies are enabled.
2301 Snackbar.make(findViewById(R.id.main_webview), R.string.javascript_disabled, Snackbar.LENGTH_SHORT).show();
2302 } else { // Privacy mode.
2303 Snackbar.make(findViewById(R.id.main_webview), R.string.privacy_mode, Snackbar.LENGTH_SHORT).show();
2306 // Reload the WebView.
2307 mainWebView.reload();
2310 case R.id.add_or_edit_domain:
2311 if (domainSettingsApplied) { // Edit the current domain settings.
2312 // Reapply the domain settings on returning to `MainWebViewActivity`.
2313 reapplyDomainSettingsOnRestart = true;
2314 currentDomainName = "";
2316 // Create an intent to launch the domains activity.
2317 Intent domainsIntent = new Intent(this, DomainsActivity.class);
2319 // Put extra information instructing the domains activity to directly load the current domain and close on back instead of returning to the domains list.
2320 domainsIntent.putExtra("loadDomain", domainSettingsDatabaseId);
2321 domainsIntent.putExtra("closeOnBack", true);
2324 startActivity(domainsIntent);
2325 } else { // Add a new domain.
2326 // Apply the new domain settings on returning to `MainWebViewActivity`.
2327 reapplyDomainSettingsOnRestart = true;
2328 currentDomainName = "";
2330 // Get the current domain
2331 Uri currentUri = Uri.parse(formattedUrlString);
2332 String currentDomain = currentUri.getHost();
2334 // Initialize the database handler. The `0` specifies the database version, but that is ignored and set instead using a constant in `DomainsDatabaseHelper`.
2335 DomainsDatabaseHelper domainsDatabaseHelper = new DomainsDatabaseHelper(this, null, null, 0);
2337 // Create the domain and store the database ID.
2338 int newDomainDatabaseId = domainsDatabaseHelper.addDomain(currentDomain);
2340 // Create an intent to launch the domains activity.
2341 Intent domainsIntent = new Intent(this, DomainsActivity.class);
2343 // Put extra information instructing the domains activity to directly load the new domain and close on back instead of returning to the domains list.
2344 domainsIntent.putExtra("loadDomain", newDomainDatabaseId);
2345 domainsIntent.putExtra("closeOnBack", true);
2348 startActivity(domainsIntent);
2352 case R.id.toggle_first_party_cookies:
2353 // Switch the status of firstPartyCookiesEnabled.
2354 firstPartyCookiesEnabled = !firstPartyCookiesEnabled;
2356 // Update the menu checkbox.
2357 menuItem.setChecked(firstPartyCookiesEnabled);
2359 // Apply the new cookie status.
2360 cookieManager.setAcceptCookie(firstPartyCookiesEnabled);
2362 // Update the privacy icon. `true` runs `invalidateOptionsMenu` as the last step.
2363 updatePrivacyIcons(true);
2365 // Display a `Snackbar`.
2366 if (firstPartyCookiesEnabled) { // First-party cookies are enabled.
2367 Snackbar.make(findViewById(R.id.main_webview), R.string.first_party_cookies_enabled, Snackbar.LENGTH_SHORT).show();
2368 } else if (javaScriptEnabled) { // JavaScript is still enabled.
2369 Snackbar.make(findViewById(R.id.main_webview), R.string.first_party_cookies_disabled, Snackbar.LENGTH_SHORT).show();
2370 } else { // Privacy mode.
2371 Snackbar.make(findViewById(R.id.main_webview), R.string.privacy_mode, Snackbar.LENGTH_SHORT).show();
2374 // Reload the WebView.
2375 mainWebView.reload();
2378 case R.id.toggle_third_party_cookies:
2379 if (Build.VERSION.SDK_INT >= 21) {
2380 // Switch the status of thirdPartyCookiesEnabled.
2381 thirdPartyCookiesEnabled = !thirdPartyCookiesEnabled;
2383 // Update the menu checkbox.
2384 menuItem.setChecked(thirdPartyCookiesEnabled);
2386 // Apply the new cookie status.
2387 cookieManager.setAcceptThirdPartyCookies(mainWebView, thirdPartyCookiesEnabled);
2389 // Display a `Snackbar`.
2390 if (thirdPartyCookiesEnabled) {
2391 Snackbar.make(findViewById(R.id.main_webview), R.string.third_party_cookies_enabled, Snackbar.LENGTH_SHORT).show();
2393 Snackbar.make(findViewById(R.id.main_webview), R.string.third_party_cookies_disabled, Snackbar.LENGTH_SHORT).show();
2396 // Reload the WebView.
2397 mainWebView.reload();
2398 } // Else do nothing because SDK < 21.
2401 case R.id.toggle_dom_storage:
2402 // Switch the status of domStorageEnabled.
2403 domStorageEnabled = !domStorageEnabled;
2405 // Update the menu checkbox.
2406 menuItem.setChecked(domStorageEnabled);
2408 // Apply the new DOM Storage status.
2409 mainWebView.getSettings().setDomStorageEnabled(domStorageEnabled);
2411 // Update the privacy icon. `true` runs `invalidateOptionsMenu` as the last step.
2412 updatePrivacyIcons(true);
2414 // Display a `Snackbar`.
2415 if (domStorageEnabled) {
2416 Snackbar.make(findViewById(R.id.main_webview), R.string.dom_storage_enabled, Snackbar.LENGTH_SHORT).show();
2418 Snackbar.make(findViewById(R.id.main_webview), R.string.dom_storage_disabled, Snackbar.LENGTH_SHORT).show();
2421 // Reload the WebView.
2422 mainWebView.reload();
2425 // Form data can be removed once the minimum API >= 26.
2426 case R.id.toggle_save_form_data:
2427 // Switch the status of saveFormDataEnabled.
2428 saveFormDataEnabled = !saveFormDataEnabled;
2430 // Update the menu checkbox.
2431 menuItem.setChecked(saveFormDataEnabled);
2433 // Apply the new form data status.
2434 mainWebView.getSettings().setSaveFormData(saveFormDataEnabled);
2436 // Display a `Snackbar`.
2437 if (saveFormDataEnabled) {
2438 Snackbar.make(findViewById(R.id.main_webview), R.string.form_data_enabled, Snackbar.LENGTH_SHORT).show();
2440 Snackbar.make(findViewById(R.id.main_webview), R.string.form_data_disabled, Snackbar.LENGTH_SHORT).show();
2443 // Update the privacy icon. `true` runs `invalidateOptionsMenu` as the last step.
2444 updatePrivacyIcons(true);
2446 // Reload the WebView.
2447 mainWebView.reload();
2450 case R.id.clear_cookies:
2451 Snackbar.make(findViewById(R.id.main_webview), R.string.cookies_deleted, Snackbar.LENGTH_LONG)
2452 .setAction(R.string.undo, v -> {
2453 // Do nothing because everything will be handled by `onDismissed()` below.
2455 .addCallback(new Snackbar.Callback() {
2456 @SuppressLint("SwitchIntDef") // Ignore the lint warning about not handling the other possible events as they are covered by `default:`.
2458 public void onDismissed(Snackbar snackbar, int event) {
2460 // The user pushed the undo button.
2461 case Snackbar.Callback.DISMISS_EVENT_ACTION:
2465 // The snackbar was dismissed without the undo button being pushed.
2467 // `cookieManager.removeAllCookie()` varies by SDK.
2468 if (Build.VERSION.SDK_INT < 21) {
2469 cookieManager.removeAllCookie();
2471 cookieManager.removeAllCookies(null);
2479 case R.id.clear_dom_storage:
2480 Snackbar.make(findViewById(R.id.main_webview), R.string.dom_storage_deleted, Snackbar.LENGTH_LONG)
2481 .setAction(R.string.undo, v -> {
2482 // Do nothing because everything will be handled by `onDismissed()` below.
2484 .addCallback(new Snackbar.Callback() {
2485 @SuppressLint("SwitchIntDef") // Ignore the lint warning about not handling the other possible events as they are covered by `default:`.
2487 public void onDismissed(Snackbar snackbar, int event) {
2489 // The user pushed the undo button.
2490 case Snackbar.Callback.DISMISS_EVENT_ACTION:
2494 // The snackbar was dismissed without the undo button being pushed.
2496 // Delete the DOM Storage.
2497 WebStorage webStorage = WebStorage.getInstance();
2498 webStorage.deleteAllData();
2500 // Initialize a handler to manually delete the DOM storage files and directories.
2501 Handler deleteDomStorageHandler = new Handler();
2503 // Setup a runnable to manually delete the DOM storage files and directories.
2504 Runnable deleteDomStorageRunnable = () -> {
2506 // A string array must be used because the directory contains a space and `Runtime.exec` will otherwise not escape the string correctly.
2507 Process deleteLocalStorageProcess = privacyBrowserRuntime.exec(new String[]{"rm", "-rf", privateDataDirectoryString + "/app_webview/Local Storage/"});
2509 // Multiple commands must be used because `Runtime.exec()` does not like `*`.
2510 Process deleteIndexProcess = privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/IndexedDB");
2511 Process deleteQuotaManagerProcess = privacyBrowserRuntime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager");
2512 Process deleteQuotaManagerJournalProcess = privacyBrowserRuntime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager-journal");
2513 Process deleteDatabasesProcess = privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/databases");
2515 // Wait for the processes to finish.
2516 deleteLocalStorageProcess.waitFor();
2517 deleteIndexProcess.waitFor();
2518 deleteQuotaManagerProcess.waitFor();
2519 deleteQuotaManagerJournalProcess.waitFor();
2520 deleteDatabasesProcess.waitFor();
2521 } catch (Exception exception) {
2522 // Do nothing if an error is thrown.
2526 // Manually delete the DOM storage files after 200 milliseconds.
2527 deleteDomStorageHandler.postDelayed(deleteDomStorageRunnable, 200);
2534 // Form data can be remove once the minimum API >= 26.
2535 case R.id.clear_form_data:
2536 Snackbar.make(findViewById(R.id.main_webview), R.string.form_data_deleted, Snackbar.LENGTH_LONG)
2537 .setAction(R.string.undo, v -> {
2538 // Do nothing because everything will be handled by `onDismissed()` below.
2540 .addCallback(new Snackbar.Callback() {
2541 @SuppressLint("SwitchIntDef") // Ignore the lint warning about not handling the other possible events as they are covered by `default:`.
2543 public void onDismissed(Snackbar snackbar, int event) {
2545 // The user pushed the undo button.
2546 case Snackbar.Callback.DISMISS_EVENT_ACTION:
2550 // The snackbar was dismissed without the `Undo` button being pushed.
2552 // Delete the form data.
2553 WebViewDatabase mainWebViewDatabase = WebViewDatabase.getInstance(getApplicationContext());
2554 mainWebViewDatabase.clearFormData();
2562 // Toggle the EasyList status.
2563 easyListEnabled = !easyListEnabled;
2565 // Update the menu checkbox.
2566 menuItem.setChecked(easyListEnabled);
2568 // Reload the main WebView.
2569 mainWebView.reload();
2572 case R.id.easyprivacy:
2573 // Toggle the EasyPrivacy status.
2574 easyPrivacyEnabled = !easyPrivacyEnabled;
2576 // Update the menu checkbox.
2577 menuItem.setChecked(easyPrivacyEnabled);
2579 // Reload the main WebView.
2580 mainWebView.reload();
2583 case R.id.fanboys_annoyance_list:
2584 // Toggle Fanboy's Annoyance List status.
2585 fanboysAnnoyanceListEnabled = !fanboysAnnoyanceListEnabled;
2587 // Update the menu checkbox.
2588 menuItem.setChecked(fanboysAnnoyanceListEnabled);
2590 // Update the staus of Fanboy's Social Blocking List.
2591 MenuItem fanboysSocialBlockingListMenuItem = mainMenu.findItem(R.id.fanboys_social_blocking_list);
2592 fanboysSocialBlockingListMenuItem.setEnabled(!fanboysAnnoyanceListEnabled);
2594 // Reload the main WebView.
2595 mainWebView.reload();
2598 case R.id.fanboys_social_blocking_list:
2599 // Toggle Fanboy's Social Blocking List status.
2600 fanboysSocialBlockingListEnabled = !fanboysSocialBlockingListEnabled;
2602 // Update the menu checkbox.
2603 menuItem.setChecked(fanboysSocialBlockingListEnabled);
2605 // Reload the main WebView.
2606 mainWebView.reload();
2609 case R.id.ultraprivacy:
2610 // Toggle the UltraPrivacy status.
2611 ultraPrivacyEnabled = !ultraPrivacyEnabled;
2613 // Update the menu checkbox.
2614 menuItem.setChecked(ultraPrivacyEnabled);
2616 // Reload the main WebView.
2617 mainWebView.reload();
2620 case R.id.block_all_third_party_requests:
2621 //Toggle the third-party requests blocker status.
2622 blockAllThirdPartyRequests = !blockAllThirdPartyRequests;
2624 // Update the menu checkbox.
2625 menuItem.setChecked(blockAllThirdPartyRequests);
2627 // Reload the main WebView.
2628 mainWebView.reload();
2631 case R.id.user_agent_privacy_browser:
2632 // Update the user agent.
2633 mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[0]);
2635 // Reload the WebView.
2636 mainWebView.reload();
2639 case R.id.user_agent_webview_default:
2640 // Update the user agent.
2641 mainWebView.getSettings().setUserAgentString("");
2643 // Reload the WebView.
2644 mainWebView.reload();
2647 case R.id.user_agent_firefox_on_android:
2648 // Update the user agent.
2649 mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[2]);
2651 // Reload the WebView.
2652 mainWebView.reload();
2655 case R.id.user_agent_chrome_on_android:
2656 // Update the user agent.
2657 mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[3]);
2659 // Reload the WebView.
2660 mainWebView.reload();
2663 case R.id.user_agent_safari_on_ios:
2664 // Update the user agent.
2665 mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[4]);
2667 // Reload the WebView.
2668 mainWebView.reload();
2671 case R.id.user_agent_firefox_on_linux:
2672 // Update the user agent.
2673 mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[5]);
2675 // Reload the WebView.
2676 mainWebView.reload();
2679 case R.id.user_agent_chromium_on_linux:
2680 // Update the user agent.
2681 mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[6]);
2683 // Reload the WebView.
2684 mainWebView.reload();
2687 case R.id.user_agent_firefox_on_windows:
2688 // Update the user agent.
2689 mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[7]);
2691 // Reload the WebView.
2692 mainWebView.reload();
2695 case R.id.user_agent_chrome_on_windows:
2696 // Update the user agent.
2697 mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[8]);
2699 // Reload the WebView.
2700 mainWebView.reload();
2703 case R.id.user_agent_edge_on_windows:
2704 // Update the user agent.
2705 mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[9]);
2707 // Reload the WebView.
2708 mainWebView.reload();
2711 case R.id.user_agent_internet_explorer_on_windows:
2712 // Update the user agent.
2713 mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[10]);
2715 // Reload the WebView.
2716 mainWebView.reload();
2719 case R.id.user_agent_safari_on_macos:
2720 // Update the user agent.
2721 mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[11]);
2723 // Reload the WebView.
2724 mainWebView.reload();
2727 case R.id.user_agent_custom:
2728 // Update the user agent.
2729 mainWebView.getSettings().setUserAgentString(defaultCustomUserAgentString);
2731 // Reload the WebView.
2732 mainWebView.reload();
2735 case R.id.font_size_twenty_five_percent:
2736 mainWebView.getSettings().setTextZoom(25);
2739 case R.id.font_size_fifty_percent:
2740 mainWebView.getSettings().setTextZoom(50);
2743 case R.id.font_size_seventy_five_percent:
2744 mainWebView.getSettings().setTextZoom(75);
2747 case R.id.font_size_one_hundred_percent:
2748 mainWebView.getSettings().setTextZoom(100);
2751 case R.id.font_size_one_hundred_twenty_five_percent:
2752 mainWebView.getSettings().setTextZoom(125);
2755 case R.id.font_size_one_hundred_fifty_percent:
2756 mainWebView.getSettings().setTextZoom(150);
2759 case R.id.font_size_one_hundred_seventy_five_percent:
2760 mainWebView.getSettings().setTextZoom(175);
2763 case R.id.font_size_two_hundred_percent:
2764 mainWebView.getSettings().setTextZoom(200);
2767 case R.id.swipe_to_refresh:
2768 // Get a handle for the swipe refresh layout.
2769 SwipeRefreshLayout swipeRefreshLayout = findViewById(R.id.swiperefreshlayout);
2771 // Toggle swipe to refresh.
2772 swipeRefreshLayout.setEnabled(!swipeRefreshLayout.isEnabled());
2775 case R.id.display_images:
2776 if (mainWebView.getSettings().getLoadsImagesAutomatically()) { // Images are currently loaded automatically.
2777 mainWebView.getSettings().setLoadsImagesAutomatically(false);
2778 mainWebView.reload();
2779 } else { // Images are not currently loaded automatically.
2780 mainWebView.getSettings().setLoadsImagesAutomatically(true);
2784 case R.id.night_mode:
2785 // Toggle night mode.
2786 nightMode = !nightMode;
2788 // Enable or disable JavaScript according to night mode, the global preference, and any domain settings.
2789 if (nightMode) { // Night mode is enabled. Enable JavaScript.
2790 // Update the global variable.
2791 javaScriptEnabled = true;
2792 } else if (domainSettingsApplied) { // Night mode is disabled and domain settings are applied. Set JavaScript according to the domain settings.
2793 // Get the JavaScript preference that was stored the last time domain settings were loaded.
2794 javaScriptEnabled = domainSettingsJavaScriptEnabled;
2795 } else { // Night mode is disabled and domain settings are not applied. Set JavaScript according to the global preference.
2796 // Get a handle for the shared preference.
2797 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
2799 // Get the JavaScript preference.
2800 javaScriptEnabled = sharedPreferences.getBoolean("javascript", false);
2803 // Apply the JavaScript setting to the WebView.
2804 mainWebView.getSettings().setJavaScriptEnabled(javaScriptEnabled);
2806 // Update the privacy icons.
2807 updatePrivacyIcons(false);
2809 // Reload the website.
2810 mainWebView.reload();
2813 case R.id.find_on_page:
2814 // Get a handle for the views.
2815 Toolbar toolbar = findViewById(R.id.toolbar);
2816 LinearLayout findOnPageLinearLayout = findViewById(R.id.find_on_page_linearlayout);
2818 // Hide the toolbar.
2819 toolbar.setVisibility(View.GONE);
2821 // Show the find on page linear layout.
2822 findOnPageLinearLayout.setVisibility(View.VISIBLE);
2824 // Display the keyboard. The app must wait 200 ms before running the command to work around a bug in Android.
2825 // http://stackoverflow.com/questions/5520085/android-show-softkeyboard-with-showsoftinput-is-not-working
2826 findOnPageEditText.postDelayed(() -> {
2827 // Set the focus on `findOnPageEditText`.
2828 findOnPageEditText.requestFocus();
2830 // Display the keyboard. `0` sets no input flags.
2831 inputMethodManager.showSoftInput(findOnPageEditText, 0);
2835 case R.id.view_source:
2836 // Launch the View Source activity.
2837 Intent viewSourceIntent = new Intent(this, ViewSourceActivity.class);
2838 startActivity(viewSourceIntent);
2841 case R.id.share_url:
2842 // Setup the share string.
2843 String shareString = webViewTitle + " – " + urlTextBox.getText().toString();
2845 // Create the share intent.
2846 Intent shareIntent = new Intent(Intent.ACTION_SEND);
2847 shareIntent.putExtra(Intent.EXTRA_TEXT, shareString);
2848 shareIntent.setType("text/plain");
2851 startActivity(Intent.createChooser(shareIntent, getString(R.string.share_url)));
2855 // Get a `PrintManager` instance.
2856 PrintManager printManager = (PrintManager) getSystemService(Context.PRINT_SERVICE);
2858 // Convert `mainWebView` to `printDocumentAdapter`.
2859 PrintDocumentAdapter printDocumentAdapter = mainWebView.createPrintDocumentAdapter();
2861 // Remove the lint error below that `printManager` might be `null`.
2862 assert printManager != null;
2864 // Print the document. The print attributes are `null`.
2865 printManager.print(getString(R.string.privacy_browser_web_page), printDocumentAdapter, null);
2868 case R.id.open_with_app:
2869 openWithApp(formattedUrlString);
2872 case R.id.open_with_browser:
2873 openWithBrowser(formattedUrlString);
2876 case R.id.add_to_homescreen:
2877 // Show the alert dialog.
2878 DialogFragment createHomeScreenShortcutDialogFragment = new CreateHomeScreenShortcutDialog();
2879 createHomeScreenShortcutDialogFragment.show(getSupportFragmentManager(), getString(R.string.create_shortcut));
2881 //Everything else will be handled by the alert dialog and the associated listener below.
2884 case R.id.proxy_through_orbot:
2885 // Toggle the proxy through Orbot variable.
2886 proxyThroughOrbot = !proxyThroughOrbot;
2888 // Apply the proxy through Orbot settings.
2889 applyProxyThroughOrbot(true);
2893 if (menuItem.getTitle().equals(getString(R.string.refresh))) { // The refresh button was pushed.
2894 // Reload the WebView.
2895 mainWebView.reload();
2896 } else { // The stop button was pushed.
2897 // Stop the loading of the WebView.
2898 mainWebView.stopLoading();
2902 case R.id.ad_consent:
2903 // Display the ad consent dialog.
2904 DialogFragment adConsentDialogFragment = new AdConsentDialog();
2905 adConsentDialogFragment.show(getSupportFragmentManager(), getString(R.string.ad_consent));
2909 // Don't consume the event.
2910 return super.onOptionsItemSelected(menuItem);
2914 // removeAllCookies is deprecated, but it is required for API < 21.
2915 @SuppressWarnings("deprecation")
2917 public boolean onNavigationItemSelected(@NonNull MenuItem menuItem) {
2918 int menuItemId = menuItem.getItemId();
2920 switch (menuItemId) {
2926 if (mainWebView.canGoBack()) {
2927 // Reset the formatted URL string so the page will load correctly if blocking of third-party requests is enabled.
2928 formattedUrlString = "";
2930 // Set `navigatingHistory` so that the domain settings are applied when the new URL is loaded.
2931 navigatingHistory = true;
2933 // Load the previous website in the history.
2934 mainWebView.goBack();
2939 if (mainWebView.canGoForward()) {
2940 // Reset the formatted URL string so the page will load correctly if blocking of third-party requests is enabled.
2941 formattedUrlString = "";
2943 // Set `navigatingHistory` so that the domain settings are applied when the new URL is loaded.
2944 navigatingHistory = true;
2946 // Load the next website in the history.
2947 mainWebView.goForward();
2952 // Get the `WebBackForwardList`.
2953 WebBackForwardList webBackForwardList = mainWebView.copyBackForwardList();
2955 // Show the URL history dialog and name this instance `R.string.history`.
2956 DialogFragment urlHistoryDialogFragment = UrlHistoryDialog.loadBackForwardList(this, webBackForwardList);
2957 urlHistoryDialogFragment.show(getSupportFragmentManager(), getString(R.string.history));
2961 // Launch the requests activity.
2962 Intent requestsIntent = new Intent(this, RequestsActivity.class);
2963 startActivity(requestsIntent);
2966 case R.id.downloads:
2967 // Launch the system Download Manager.
2968 Intent downloadManagerIntent = new Intent(DownloadManager.ACTION_VIEW_DOWNLOADS);
2970 // Launch as a new task so that Download Manager and Privacy Browser show as separate windows in the recent tasks list.
2971 downloadManagerIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
2973 startActivity(downloadManagerIntent);
2977 // Set the flag to reapply the domain settings on restart when returning from Domain Settings.
2978 reapplyDomainSettingsOnRestart = true;
2979 currentDomainName = "";
2981 // Launch the domains activity.
2982 Intent domainsIntent = new Intent(this, DomainsActivity.class);
2983 startActivity(domainsIntent);
2987 // Set the flag to reapply app settings on restart when returning from Settings.
2988 reapplyAppSettingsOnRestart = true;
2990 // Set the flag to reapply the domain settings on restart when returning from Settings.
2991 reapplyDomainSettingsOnRestart = true;
2992 currentDomainName = "";
2994 // Launch the settings activity.
2995 Intent settingsIntent = new Intent(this, SettingsActivity.class);
2996 startActivity(settingsIntent);
2999 case R.id.import_export:
3000 // Launch the import/export activity.
3001 Intent importExportIntent = new Intent (this, ImportExportActivity.class);
3002 startActivity(importExportIntent);
3006 // Launch the logcat activity.
3007 Intent logcatIntent = new Intent(this, LogcatActivity.class);
3008 startActivity(logcatIntent);
3012 // Launch `GuideActivity`.
3013 Intent guideIntent = new Intent(this, GuideActivity.class);
3014 startActivity(guideIntent);
3018 // Launch `AboutActivity`.
3019 Intent aboutIntent = new Intent(this, AboutActivity.class);
3020 startActivity(aboutIntent);
3023 case R.id.clear_and_exit:
3024 // Close the bookmarks cursor and database.
3025 bookmarksCursor.close();
3026 bookmarksDatabaseHelper.close();
3028 // Get a handle for the shared preferences.
3029 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
3031 // Get the status of the clear everything preference.
3032 boolean clearEverything = sharedPreferences.getBoolean("clear_everything", true);
3035 if (clearEverything || sharedPreferences.getBoolean("clear_cookies", true)) {
3036 // The command to remove cookies changed slightly in API 21.
3037 if (Build.VERSION.SDK_INT >= 21) {
3038 cookieManager.removeAllCookies(null);
3040 cookieManager.removeAllCookie();
3043 // Manually delete the cookies database, as `CookieManager` sometimes will not flush its changes to disk before `System.exit(0)` is run.
3045 // Two commands must be used because `Runtime.exec()` does not like `*`.
3046 Process deleteCookiesProcess = privacyBrowserRuntime.exec("rm -f " + privateDataDirectoryString + "/app_webview/Cookies");
3047 Process deleteCookiesJournalProcess = privacyBrowserRuntime.exec("rm -f " + privateDataDirectoryString + "/app_webview/Cookies-journal");
3049 // Wait until the processes have finished.
3050 deleteCookiesProcess.waitFor();
3051 deleteCookiesJournalProcess.waitFor();
3052 } catch (Exception exception) {
3053 // Do nothing if an error is thrown.
3057 // Clear DOM storage.
3058 if (clearEverything || sharedPreferences.getBoolean("clear_dom_storage", true)) {
3059 // Ask `WebStorage` to clear the DOM storage.
3060 WebStorage webStorage = WebStorage.getInstance();
3061 webStorage.deleteAllData();
3063 // Manually delete the DOM storage files and directories, as `WebStorage` sometimes will not flush its changes to disk before `System.exit(0)` is run.
3065 // A `String[]` must be used because the directory contains a space and `Runtime.exec` will otherwise not escape the string correctly.
3066 Process deleteLocalStorageProcess = privacyBrowserRuntime.exec(new String[] {"rm", "-rf", privateDataDirectoryString + "/app_webview/Local Storage/"});
3068 // Multiple commands must be used because `Runtime.exec()` does not like `*`.
3069 Process deleteIndexProcess = privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/IndexedDB");
3070 Process deleteQuotaManagerProcess = privacyBrowserRuntime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager");
3071 Process deleteQuotaManagerJournalProcess = privacyBrowserRuntime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager-journal");
3072 Process deleteDatabaseProcess = privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/databases");
3074 // Wait until the processes have finished.
3075 deleteLocalStorageProcess.waitFor();
3076 deleteIndexProcess.waitFor();
3077 deleteQuotaManagerProcess.waitFor();
3078 deleteQuotaManagerJournalProcess.waitFor();
3079 deleteDatabaseProcess.waitFor();
3080 } catch (Exception exception) {
3081 // Do nothing if an error is thrown.
3085 // Clear form data if the API < 26.
3086 if ((Build.VERSION.SDK_INT < 26) && (clearEverything || sharedPreferences.getBoolean("clear_form_data", true))) {
3087 WebViewDatabase webViewDatabase = WebViewDatabase.getInstance(this);
3088 webViewDatabase.clearFormData();
3090 // Manually delete the form data database, as `WebViewDatabase` sometimes will not flush its changes to disk before `System.exit(0)` is run.
3092 // A string array must be used because the database contains a space and `Runtime.exec` will not otherwise escape the string correctly.
3093 Process deleteWebDataProcess = privacyBrowserRuntime.exec(new String[] {"rm", "-f", privateDataDirectoryString + "/app_webview/Web Data"});
3094 Process deleteWebDataJournalProcess = privacyBrowserRuntime.exec(new String[] {"rm", "-f", privateDataDirectoryString + "/app_webview/Web Data-journal"});
3096 // Wait until the processes have finished.
3097 deleteWebDataProcess.waitFor();
3098 deleteWebDataJournalProcess.waitFor();
3099 } catch (Exception exception) {
3100 // Do nothing if an error is thrown.
3105 if (clearEverything || sharedPreferences.getBoolean("clear_cache", true)) {
3106 // `true` includes disk files.
3107 mainWebView.clearCache(true);
3109 // Manually delete the cache directories.
3111 // Delete the main cache directory.
3112 Process deleteCacheProcess = privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/cache");
3114 // Delete the secondary `Service Worker` cache directory.
3115 // A string array must be used because the directory contains a space and `Runtime.exec` will otherwise not escape the string correctly.
3116 Process deleteServiceWorkerProcess = privacyBrowserRuntime.exec(new String[] {"rm", "-rf", privateDataDirectoryString + "/app_webview/Service Worker/"});
3118 // Wait until the processes have finished.
3119 deleteCacheProcess.waitFor();
3120 deleteServiceWorkerProcess.waitFor();
3121 } catch (Exception exception) {
3122 // Do nothing if an error is thrown.
3126 // Clear SSL certificate preferences.
3127 mainWebView.clearSslPreferences();
3129 // Clear the back/forward history.
3130 mainWebView.clearHistory();
3132 // Clear `formattedUrlString`.
3133 formattedUrlString = null;
3135 // Clear `customHeaders`.
3136 customHeaders.clear();
3138 // Destroy the internal state of `mainWebView`.
3139 mainWebView.destroy();
3141 // Manually delete the `app_webview` folder, which contains the cookies, DOM storage, form data, and `Service Worker` cache.
3142 // See `https://code.google.com/p/android/issues/detail?id=233826&thanks=233826&ts=1486670530`.
3143 if (clearEverything) {
3145 // Delete the folder.
3146 Process deleteAppWebviewProcess = privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/app_webview");
3148 // Wait until the process has finished.
3149 deleteAppWebviewProcess.waitFor();
3150 } catch (Exception exception) {
3151 // Do nothing if an error is thrown.
3155 // Close Privacy Browser. `finishAndRemoveTask` also removes Privacy Browser from the recent app list.
3156 if (Build.VERSION.SDK_INT >= 21) {
3157 finishAndRemoveTask();
3162 // Remove the terminated program from RAM. The status code is `0`.
3167 // Get a handle for the drawer layout.
3168 DrawerLayout drawerLayout = findViewById(R.id.drawerlayout);
3170 // Close the navigation drawer.
3171 drawerLayout.closeDrawer(GravityCompat.START);
3176 public void onPostCreate(Bundle savedInstanceState) {
3177 // Run the default commands.
3178 super.onPostCreate(savedInstanceState);
3180 // Sync the state of the DrawerToggle after the default `onRestoreInstanceState()` has finished. This creates the navigation drawer icon.
3181 actionBarDrawerToggle.syncState();
3185 public void onConfigurationChanged(Configuration newConfig) {
3186 // Run the default commands.
3187 super.onConfigurationChanged(newConfig);
3189 // Get the status bar pixel size.
3190 int statusBarResourceId = getResources().getIdentifier("status_bar_height", "dimen", "android");
3191 int statusBarPixelSize = getResources().getDimensionPixelSize(statusBarResourceId);
3193 // Get the resource density.
3194 float screenDensity = getResources().getDisplayMetrics().density;
3196 // Recalculate the drawer header padding.
3197 drawerHeaderPaddingLeftAndRight = (int) (15 * screenDensity);
3198 drawerHeaderPaddingTop = statusBarPixelSize + (int) (4 * screenDensity);
3199 drawerHeaderPaddingBottom = (int) (8 * screenDensity);
3201 // Reload the ad for the free flavor if not in full screen mode.
3202 if (BuildConfig.FLAVOR.contentEquals("free") && !inFullScreenBrowsingMode) {
3203 // Reload the ad. The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations.
3204 AdHelper.loadAd(findViewById(R.id.adview), getApplicationContext(), getString(R.string.ad_unit_id));
3207 // `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:
3208 // https://code.google.com/p/android/issues/detail?id=20493#c8
3209 // ActivityCompat.invalidateOptionsMenu(this);
3213 public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.ContextMenuInfo menuInfo) {
3214 // Store the `HitTestResult`.
3215 final WebView.HitTestResult hitTestResult = mainWebView.getHitTestResult();
3218 final String imageUrl;
3219 final String linkUrl;
3221 // Get a handle for the the clipboard and fragment managers.
3222 final ClipboardManager clipboardManager = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
3223 FragmentManager fragmentManager = getSupportFragmentManager();
3225 // Remove the lint errors below that `clipboardManager` might be `null`.
3226 assert clipboardManager != null;
3228 switch (hitTestResult.getType()) {
3229 // `SRC_ANCHOR_TYPE` is a link.
3230 case WebView.HitTestResult.SRC_ANCHOR_TYPE:
3231 // Get the target URL.
3232 linkUrl = hitTestResult.getExtra();
3234 // Set the target URL as the title of the `ContextMenu`.
3235 menu.setHeaderTitle(linkUrl);
3237 // Add a Load URL entry.
3238 menu.add(R.string.load_url).setOnMenuItemClickListener((MenuItem item) -> {
3243 // Add a Copy URL entry.
3244 menu.add(R.string.copy_url).setOnMenuItemClickListener((MenuItem item) -> {
3245 // Save the link URL in a `ClipData`.
3246 ClipData srcAnchorTypeClipData = ClipData.newPlainText(getString(R.string.url), linkUrl);
3248 // Set the `ClipData` as the clipboard's primary clip.
3249 clipboardManager.setPrimaryClip(srcAnchorTypeClipData);
3253 // Add a Download URL entry.
3254 menu.add(R.string.download_url).setOnMenuItemClickListener((MenuItem item) -> {
3255 // Check if the download should be processed by an external app.
3256 if (downloadWithExternalApp) { // Download with an external app.
3257 openUrlWithExternalApp(linkUrl);
3258 } else { // Download with Android's download manager.
3259 // Check to see if the storage permission has already been granted.
3260 if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) { // The storage permission needs to be requested.
3261 // Store the variables for future use by `onRequestPermissionsResult()`.
3262 downloadUrl = linkUrl;
3263 downloadContentDisposition = "none";
3264 downloadContentLength = -1;
3266 // Show a dialog if the user has previously denied the permission.
3267 if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) { // Show a dialog explaining the request first.
3268 // Instantiate the download location permission alert dialog and set the download type to DOWNLOAD_FILE.
3269 DialogFragment downloadLocationPermissionDialogFragment = DownloadLocationPermissionDialog.downloadType(DownloadLocationPermissionDialog.DOWNLOAD_FILE);
3271 // Show the download location permission alert dialog. The permission will be requested when the the dialog is closed.
3272 downloadLocationPermissionDialogFragment.show(fragmentManager, getString(R.string.download_location));
3273 } else { // Show the permission request directly.
3274 // Request the permission. The download dialog will be launched by `onRequestPermissionResult()`.
3275 ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_FILE_REQUEST_CODE);
3277 } else { // The storage permission has already been granted.
3278 // Get a handle for the download file alert dialog.
3279 DialogFragment downloadFileDialogFragment = DownloadFileDialog.fromUrl(linkUrl, "none", -1);
3281 // Show the download file alert dialog.
3282 downloadFileDialogFragment.show(fragmentManager, getString(R.string.download));
3288 // Add an Open with App entry.
3289 menu.add(R.string.open_with_app).setOnMenuItemClickListener((MenuItem item) -> {
3290 openWithApp(linkUrl);
3294 // Add an Open with Browser entry.
3295 menu.add(R.string.open_with_browser).setOnMenuItemClickListener((MenuItem item) -> {
3296 openWithBrowser(linkUrl);
3300 // Add a Cancel entry, which by default closes the context menu.
3301 menu.add(R.string.cancel);
3304 case WebView.HitTestResult.EMAIL_TYPE:
3305 // Get the target URL.
3306 linkUrl = hitTestResult.getExtra();
3308 // Set the target URL as the title of the `ContextMenu`.
3309 menu.setHeaderTitle(linkUrl);
3311 // Add a Write Email entry.
3312 menu.add(R.string.write_email).setOnMenuItemClickListener(item -> {
3313 // Use `ACTION_SENDTO` instead of `ACTION_SEND` so that only email programs are launched.
3314 Intent emailIntent = new Intent(Intent.ACTION_SENDTO);
3316 // Parse the url and set it as the data for the `Intent`.
3317 emailIntent.setData(Uri.parse("mailto:" + linkUrl));
3319 // `FLAG_ACTIVITY_NEW_TASK` opens the email program in a new task instead as part of Privacy Browser.
3320 emailIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
3323 startActivity(emailIntent);
3327 // Add a Copy Email Address entry.
3328 menu.add(R.string.copy_email_address).setOnMenuItemClickListener(item -> {
3329 // Save the email address in a `ClipData`.
3330 ClipData srcEmailTypeClipData = ClipData.newPlainText(getString(R.string.email_address), linkUrl);
3332 // Set the `ClipData` as the clipboard's primary clip.
3333 clipboardManager.setPrimaryClip(srcEmailTypeClipData);
3337 // Add a `Cancel` entry, which by default closes the `ContextMenu`.
3338 menu.add(R.string.cancel);
3341 // `IMAGE_TYPE` is an image.
3342 case WebView.HitTestResult.IMAGE_TYPE:
3343 // Get the image URL.
3344 imageUrl = hitTestResult.getExtra();
3346 // Set the image URL as the title of the `ContextMenu`.
3347 menu.setHeaderTitle(imageUrl);
3349 // Add a View Image entry.
3350 menu.add(R.string.view_image).setOnMenuItemClickListener(item -> {
3355 // Add a Download Image entry.
3356 menu.add(R.string.download_image).setOnMenuItemClickListener((MenuItem item) -> {
3357 // Check if the download should be processed by an external app.
3358 if (downloadWithExternalApp) { // Download with an external app.
3359 openUrlWithExternalApp(imageUrl);
3360 } else { // Download with Android's download manager.
3361 // Check to see if the storage permission has already been granted.
3362 if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) { // The storage permission needs to be requested.
3363 // Store the image URL for use by `onRequestPermissionResult()`.
3364 downloadImageUrl = imageUrl;
3366 // Show a dialog if the user has previously denied the permission.
3367 if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) { // Show a dialog explaining the request first.
3368 // Instantiate the download location permission alert dialog and set the download type to DOWNLOAD_IMAGE.
3369 DialogFragment downloadLocationPermissionDialogFragment = DownloadLocationPermissionDialog.downloadType(DownloadLocationPermissionDialog.DOWNLOAD_IMAGE);
3371 // Show the download location permission alert dialog. The permission will be requested when the dialog is closed.
3372 downloadLocationPermissionDialogFragment.show(fragmentManager, getString(R.string.download_location));
3373 } else { // Show the permission request directly.
3374 // Request the permission. The download dialog will be launched by `onRequestPermissionResult().
3375 ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_IMAGE_REQUEST_CODE);
3377 } else { // The storage permission has already been granted.
3378 // Get a handle for the download image alert dialog.
3379 DialogFragment downloadImageDialogFragment = DownloadImageDialog.imageUrl(imageUrl);
3381 // Show the download image alert dialog.
3382 downloadImageDialogFragment.show(fragmentManager, getString(R.string.download));
3388 // Add a Copy URL entry.
3389 menu.add(R.string.copy_url).setOnMenuItemClickListener(item -> {
3390 // Save the image URL in a `ClipData`.
3391 ClipData srcImageTypeClipData = ClipData.newPlainText(getString(R.string.url), imageUrl);
3393 // Set the `ClipData` as the clipboard's primary clip.
3394 clipboardManager.setPrimaryClip(srcImageTypeClipData);
3398 // Add an Open with App entry.
3399 menu.add(R.string.open_with_app).setOnMenuItemClickListener((MenuItem item) -> {
3400 openWithApp(imageUrl);
3404 // Add an Open with Browser entry.
3405 menu.add(R.string.open_with_browser).setOnMenuItemClickListener((MenuItem item) -> {
3406 openWithBrowser(imageUrl);
3410 // Add a `Cancel` entry, which by default closes the `ContextMenu`.
3411 menu.add(R.string.cancel);
3415 // `SRC_IMAGE_ANCHOR_TYPE` is an image that is also a link.
3416 case WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE:
3417 // Get the image URL.
3418 imageUrl = hitTestResult.getExtra();
3420 // Set the image URL as the title of the `ContextMenu`.
3421 menu.setHeaderTitle(imageUrl);
3423 // Add a `View Image` entry.
3424 menu.add(R.string.view_image).setOnMenuItemClickListener(item -> {
3429 // Add a `Download Image` entry.
3430 menu.add(R.string.download_image).setOnMenuItemClickListener((MenuItem item) -> {
3431 // Check if the download should be processed by an external app.
3432 if (downloadWithExternalApp) { // Download with an external app.
3433 openUrlWithExternalApp(imageUrl);
3434 } else { // Download with Android's download manager.
3435 // Check to see if the storage permission has already been granted.
3436 if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) { // The storage permission needs to be requested.
3437 // Store the image URL for use by `onRequestPermissionResult()`.
3438 downloadImageUrl = imageUrl;
3440 // Show a dialog if the user has previously denied the permission.
3441 if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) { // Show a dialog explaining the request first.
3442 // Instantiate the download location permission alert dialog and set the download type to DOWNLOAD_IMAGE.
3443 DialogFragment downloadLocationPermissionDialogFragment = DownloadLocationPermissionDialog.downloadType(DownloadLocationPermissionDialog.DOWNLOAD_IMAGE);
3445 // Show the download location permission alert dialog. The permission will be requested when the dialog is closed.
3446 downloadLocationPermissionDialogFragment.show(fragmentManager, getString(R.string.download_location));
3447 } else { // Show the permission request directly.
3448 // Request the permission. The download dialog will be launched by `onRequestPermissionResult().
3449 ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_IMAGE_REQUEST_CODE);
3451 } else { // The storage permission has already been granted.
3452 // Get a handle for the download image alert dialog.
3453 DialogFragment downloadImageDialogFragment = DownloadImageDialog.imageUrl(imageUrl);
3455 // Show the download image alert dialog.
3456 downloadImageDialogFragment.show(fragmentManager, getString(R.string.download));
3462 // Add a `Copy URL` entry.
3463 menu.add(R.string.copy_url).setOnMenuItemClickListener(item -> {
3464 // Save the image URL in a `ClipData`.
3465 ClipData srcImageAnchorTypeClipData = ClipData.newPlainText(getString(R.string.url), imageUrl);
3467 // Set the `ClipData` as the clipboard's primary clip.
3468 clipboardManager.setPrimaryClip(srcImageAnchorTypeClipData);
3472 // Add an Open with App entry.
3473 menu.add(R.string.open_with_app).setOnMenuItemClickListener((MenuItem item) -> {
3474 openWithApp(imageUrl);
3478 // Add an Open with Browser entry.
3479 menu.add(R.string.open_with_browser).setOnMenuItemClickListener((MenuItem item) -> {
3480 openWithBrowser(imageUrl);
3484 // Add a `Cancel` entry, which by default closes the `ContextMenu`.
3485 menu.add(R.string.cancel);
3491 public void onCreateBookmark(DialogFragment dialogFragment) {
3492 // Get the `EditTexts` from the `dialogFragment`.
3493 EditText createBookmarkNameEditText = dialogFragment.getDialog().findViewById(R.id.create_bookmark_name_edittext);
3494 EditText createBookmarkUrlEditText = dialogFragment.getDialog().findViewById(R.id.create_bookmark_url_edittext);
3496 // Extract the strings from the `EditTexts`.
3497 String bookmarkNameString = createBookmarkNameEditText.getText().toString();
3498 String bookmarkUrlString = createBookmarkUrlEditText.getText().toString();
3500 // Convert the favoriteIcon Bitmap to a byte array. `0` is for lossless compression (the only option for a PNG).
3501 ByteArrayOutputStream favoriteIconByteArrayOutputStream = new ByteArrayOutputStream();
3502 favoriteIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, favoriteIconByteArrayOutputStream);
3503 byte[] favoriteIconByteArray = favoriteIconByteArrayOutputStream.toByteArray();
3505 // Display the new bookmark below the current items in the (0 indexed) list.
3506 int newBookmarkDisplayOrder = bookmarksListView.getCount();
3508 // Create the bookmark.
3509 bookmarksDatabaseHelper.createBookmark(bookmarkNameString, bookmarkUrlString, currentBookmarksFolder, newBookmarkDisplayOrder, favoriteIconByteArray);
3511 // Update the bookmarks cursor with the current contents of this folder.
3512 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder);
3514 // Update the `ListView`.
3515 bookmarksCursorAdapter.changeCursor(bookmarksCursor);
3517 // Scroll to the new bookmark.
3518 bookmarksListView.setSelection(newBookmarkDisplayOrder);
3522 public void onCreateBookmarkFolder(DialogFragment dialogFragment) {
3523 // Get handles for the views in `dialogFragment`.
3524 EditText createFolderNameEditText = dialogFragment.getDialog().findViewById(R.id.create_folder_name_edittext);
3525 RadioButton defaultFolderIconRadioButton = dialogFragment.getDialog().findViewById(R.id.create_folder_default_icon_radiobutton);
3526 ImageView folderIconImageView = dialogFragment.getDialog().findViewById(R.id.create_folder_default_icon);
3528 // Get new folder name string.
3529 String folderNameString = createFolderNameEditText.getText().toString();
3531 // Get the new folder icon `Bitmap`.
3532 Bitmap folderIconBitmap;
3533 if (defaultFolderIconRadioButton.isChecked()) { // Use the default folder icon.
3534 // Get the default folder icon and convert it to a `Bitmap`.
3535 Drawable folderIconDrawable = folderIconImageView.getDrawable();
3536 BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable;
3537 folderIconBitmap = folderIconBitmapDrawable.getBitmap();
3538 } else { // Use the `WebView` favorite icon.
3539 folderIconBitmap = favoriteIconBitmap;
3542 // Convert `folderIconBitmap` to a byte array. `0` is for lossless compression (the only option for a PNG).
3543 ByteArrayOutputStream folderIconByteArrayOutputStream = new ByteArrayOutputStream();
3544 folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, folderIconByteArrayOutputStream);
3545 byte[] folderIconByteArray = folderIconByteArrayOutputStream.toByteArray();
3547 // Move all the bookmarks down one in the display order.
3548 for (int i = 0; i < bookmarksListView.getCount(); i++) {
3549 int databaseId = (int) bookmarksListView.getItemIdAtPosition(i);
3550 bookmarksDatabaseHelper.updateDisplayOrder(databaseId, i + 1);
3553 // Create the folder, which will be placed at the top of the `ListView`.
3554 bookmarksDatabaseHelper.createFolder(folderNameString, currentBookmarksFolder, folderIconByteArray);
3556 // Update the bookmarks cursor with the current contents of this folder.
3557 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder);
3559 // Update the `ListView`.
3560 bookmarksCursorAdapter.changeCursor(bookmarksCursor);
3562 // Scroll to the new folder.
3563 bookmarksListView.setSelection(0);
3567 public void onCreateHomeScreenShortcut(DialogFragment dialogFragment) {
3568 // Get the shortcut name.
3569 EditText shortcutNameEditText = dialogFragment.getDialog().findViewById(R.id.shortcut_name_edittext);
3570 String shortcutNameString = shortcutNameEditText.getText().toString();
3572 // Convert the favorite icon bitmap to an `Icon`. `IconCompat` is required until API >= 26.
3573 IconCompat favoriteIcon = IconCompat.createWithBitmap(favoriteIconBitmap);
3575 // Setup the shortcut intent.
3576 Intent shortcutIntent = new Intent(Intent.ACTION_VIEW);
3577 shortcutIntent.setData(Uri.parse(formattedUrlString));
3579 // Create a shortcut info builder. The shortcut name becomes the shortcut ID.
3580 ShortcutInfoCompat.Builder shortcutInfoBuilder = new ShortcutInfoCompat.Builder(this, shortcutNameString);
3582 // Add the required fields to the shortcut info builder.
3583 shortcutInfoBuilder.setIcon(favoriteIcon);
3584 shortcutInfoBuilder.setIntent(shortcutIntent);
3585 shortcutInfoBuilder.setShortLabel(shortcutNameString);
3587 // Request the pin. `ShortcutManagerCompat` can be switched to `ShortcutManager` once API >= 26.
3588 ShortcutManagerCompat.requestPinShortcut(this, shortcutInfoBuilder.build(), null);
3592 public void onCloseDownloadLocationPermissionDialog(int downloadType) {
3593 switch (downloadType) {
3594 case DownloadLocationPermissionDialog.DOWNLOAD_FILE:
3595 // Request the WRITE_EXTERNAL_STORAGE permission with a file request code.
3596 ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_FILE_REQUEST_CODE);
3599 case DownloadLocationPermissionDialog.DOWNLOAD_IMAGE:
3600 // Request the WRITE_EXTERNAL_STORAGE permission with an image request code.
3601 ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_IMAGE_REQUEST_CODE);
3607 public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
3608 // Get a handle for the fragment manager.
3609 FragmentManager fragmentManager = getSupportFragmentManager();
3611 switch (requestCode) {
3612 case DOWNLOAD_FILE_REQUEST_CODE:
3613 // Show the download file alert dialog. When the dialog closes, the correct command will be used based on the permission status.
3614 DialogFragment downloadFileDialogFragment = DownloadFileDialog.fromUrl(downloadUrl, downloadContentDisposition, downloadContentLength);
3616 // On API 23, displaying the fragment must be delayed or the app will crash.
3617 if (Build.VERSION.SDK_INT == 23) {
3618 new Handler().postDelayed(() -> downloadFileDialogFragment.show(fragmentManager, getString(R.string.download)), 500);
3620 downloadFileDialogFragment.show(fragmentManager, getString(R.string.download));
3623 // Reset the download variables.
3625 downloadContentDisposition = "";
3626 downloadContentLength = 0;
3629 case DOWNLOAD_IMAGE_REQUEST_CODE:
3630 // Show the download image alert dialog. When the dialog closes, the correct command will be used based on the permission status.
3631 DialogFragment downloadImageDialogFragment = DownloadImageDialog.imageUrl(downloadImageUrl);
3633 // On API 23, displaying the fragment must be delayed or the app will crash.
3634 if (Build.VERSION.SDK_INT == 23) {
3635 new Handler().postDelayed(() -> downloadImageDialogFragment.show(fragmentManager, getString(R.string.download)), 500);
3637 downloadImageDialogFragment.show(fragmentManager, getString(R.string.download));
3640 // Reset the image URL variable.
3641 downloadImageUrl = "";
3647 public void onDownloadImage(DialogFragment dialogFragment, String imageUrl) {
3648 // Download the image if it has an HTTP or HTTPS URI.
3649 if (imageUrl.startsWith("http")) {
3650 // Get a handle for the system `DOWNLOAD_SERVICE`.
3651 DownloadManager downloadManager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
3653 // Parse `imageUrl`.
3654 DownloadManager.Request downloadRequest = new DownloadManager.Request(Uri.parse(imageUrl));
3656 // Pass cookies to download manager if cookies are enabled. This is required to download images from websites that require a login.
3657 // Code contributed 2017 Hendrik Knackstedt. Copyright assigned to Soren Stoutner <soren@stoutner.com>.
3658 if (firstPartyCookiesEnabled) {
3659 // Get the cookies for `imageUrl`.
3660 String cookies = cookieManager.getCookie(imageUrl);
3662 // Add the cookies to `downloadRequest`. In the HTTP request header, cookies are named `Cookie`.
3663 downloadRequest.addRequestHeader("Cookie", cookies);
3666 // Get the file name from the dialog fragment.
3667 EditText downloadImageNameEditText = dialogFragment.getDialog().findViewById(R.id.download_image_name);
3668 String imageName = downloadImageNameEditText.getText().toString();
3670 // Specify the download location.
3671 if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { // External write permission granted.
3672 // Download to the public download directory.
3673 downloadRequest.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, imageName);
3674 } else { // External write permission denied.
3675 // Download to the app's external download directory.
3676 downloadRequest.setDestinationInExternalFilesDir(this, Environment.DIRECTORY_DOWNLOADS, imageName);
3679 // Allow `MediaScanner` to index the download if it is a media file.
3680 downloadRequest.allowScanningByMediaScanner();
3682 // Add the URL as the description for the download.
3683 downloadRequest.setDescription(imageUrl);
3685 // Show the download notification after the download is completed.
3686 downloadRequest.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
3688 // Remove the lint warning below that `downloadManager` might be `null`.
3689 assert downloadManager != null;
3691 // Initiate the download.
3692 downloadManager.enqueue(downloadRequest);
3693 } else { // The image is not an HTTP or HTTPS URI.
3694 Snackbar.make(mainWebView, R.string.cannot_download_image, Snackbar.LENGTH_INDEFINITE).show();
3699 public void onDownloadFile(DialogFragment dialogFragment, String downloadUrl) {
3700 // Download the file if it has an HTTP or HTTPS URI.
3701 if (downloadUrl.startsWith("http")) {
3702 // Get a handle for the system `DOWNLOAD_SERVICE`.
3703 DownloadManager downloadManager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
3705 // Parse `downloadUrl`.
3706 DownloadManager.Request downloadRequest = new DownloadManager.Request(Uri.parse(downloadUrl));
3708 // Pass cookies to download manager if cookies are enabled. This is required to download files from websites that require a login.
3709 // Code contributed 2017 Hendrik Knackstedt. Copyright assigned to Soren Stoutner <soren@stoutner.com>.
3710 if (firstPartyCookiesEnabled) {
3711 // Get the cookies for `downloadUrl`.
3712 String cookies = cookieManager.getCookie(downloadUrl);
3714 // Add the cookies to `downloadRequest`. In the HTTP request header, cookies are named `Cookie`.
3715 downloadRequest.addRequestHeader("Cookie", cookies);
3718 // Get the file name from the dialog fragment.
3719 EditText downloadFileNameEditText = dialogFragment.getDialog().findViewById(R.id.download_file_name);
3720 String fileName = downloadFileNameEditText.getText().toString();
3722 // Specify the download location.
3723 if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { // External write permission granted.
3724 // Download to the public download directory.
3725 downloadRequest.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, fileName);
3726 } else { // External write permission denied.
3727 // Download to the app's external download directory.
3728 downloadRequest.setDestinationInExternalFilesDir(this, Environment.DIRECTORY_DOWNLOADS, fileName);
3731 // Allow `MediaScanner` to index the download if it is a media file.
3732 downloadRequest.allowScanningByMediaScanner();
3734 // Add the URL as the description for the download.
3735 downloadRequest.setDescription(downloadUrl);
3737 // Show the download notification after the download is completed.
3738 downloadRequest.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
3740 // Remove the lint warning below that `downloadManager` might be `null`.
3741 assert downloadManager != null;
3743 // Initiate the download.
3744 downloadManager.enqueue(downloadRequest);
3745 } else { // The download is not an HTTP or HTTPS URI.
3746 Snackbar.make(mainWebView, R.string.cannot_download_file, Snackbar.LENGTH_INDEFINITE).show();
3751 public void onSaveBookmark(DialogFragment dialogFragment, int selectedBookmarkDatabaseId) {
3752 // Get handles for the views from `dialogFragment`.
3753 EditText editBookmarkNameEditText = dialogFragment.getDialog().findViewById(R.id.edit_bookmark_name_edittext);
3754 EditText editBookmarkUrlEditText = dialogFragment.getDialog().findViewById(R.id.edit_bookmark_url_edittext);
3755 RadioButton currentBookmarkIconRadioButton = dialogFragment.getDialog().findViewById(R.id.edit_bookmark_current_icon_radiobutton);
3757 // Store the bookmark strings.
3758 String bookmarkNameString = editBookmarkNameEditText.getText().toString();
3759 String bookmarkUrlString = editBookmarkUrlEditText.getText().toString();
3761 // Update the bookmark.
3762 if (currentBookmarkIconRadioButton.isChecked()) { // Update the bookmark without changing the favorite icon.
3763 bookmarksDatabaseHelper.updateBookmark(selectedBookmarkDatabaseId, bookmarkNameString, bookmarkUrlString);
3764 } else { // Update the bookmark using the `WebView` favorite icon.
3765 // Convert the favorite icon to a byte array. `0` is for lossless compression (the only option for a PNG).
3766 ByteArrayOutputStream newFavoriteIconByteArrayOutputStream = new ByteArrayOutputStream();
3767 favoriteIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, newFavoriteIconByteArrayOutputStream);
3768 byte[] newFavoriteIconByteArray = newFavoriteIconByteArrayOutputStream.toByteArray();
3770 // Update the bookmark and the favorite icon.
3771 bookmarksDatabaseHelper.updateBookmark(selectedBookmarkDatabaseId, bookmarkNameString, bookmarkUrlString, newFavoriteIconByteArray);
3774 // Update the bookmarks cursor with the current contents of this folder.
3775 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder);
3777 // Update the `ListView`.
3778 bookmarksCursorAdapter.changeCursor(bookmarksCursor);
3782 public void onSaveBookmarkFolder(DialogFragment dialogFragment, int selectedFolderDatabaseId) {
3783 // Get handles for the views from `dialogFragment`.
3784 EditText editFolderNameEditText = dialogFragment.getDialog().findViewById(R.id.edit_folder_name_edittext);
3785 RadioButton currentFolderIconRadioButton = dialogFragment.getDialog().findViewById(R.id.edit_folder_current_icon_radiobutton);
3786 RadioButton defaultFolderIconRadioButton = dialogFragment.getDialog().findViewById(R.id.edit_folder_default_icon_radiobutton);
3787 ImageView folderIconImageView = dialogFragment.getDialog().findViewById(R.id.edit_folder_default_icon_imageview);
3789 // Get the new folder name.
3790 String newFolderNameString = editFolderNameEditText.getText().toString();
3792 // Check if the favorite icon has changed.
3793 if (currentFolderIconRadioButton.isChecked()) { // Only the name has changed.
3794 // Update the name in the database.
3795 bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, oldFolderNameString, newFolderNameString);
3796 } else if (!currentFolderIconRadioButton.isChecked() && newFolderNameString.equals(oldFolderNameString)) { // Only the icon has changed.
3797 // Get the new folder icon `Bitmap`.
3798 Bitmap folderIconBitmap;
3799 if (defaultFolderIconRadioButton.isChecked()) {
3800 // Get the default folder icon and convert it to a `Bitmap`.
3801 Drawable folderIconDrawable = folderIconImageView.getDrawable();
3802 BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable;
3803 folderIconBitmap = folderIconBitmapDrawable.getBitmap();
3804 } else { // Use the `WebView` favorite icon.
3805 folderIconBitmap = favoriteIconBitmap;
3808 // Convert the folder `Bitmap` to a byte array. `0` is for lossless compression (the only option for a PNG).
3809 ByteArrayOutputStream folderIconByteArrayOutputStream = new ByteArrayOutputStream();
3810 folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, folderIconByteArrayOutputStream);
3811 byte[] folderIconByteArray = folderIconByteArrayOutputStream.toByteArray();
3813 // Update the folder icon in the database.
3814 bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, folderIconByteArray);
3815 } else { // The folder icon and the name have changed.
3816 // Get the new folder icon `Bitmap`.
3817 Bitmap folderIconBitmap;
3818 if (defaultFolderIconRadioButton.isChecked()) {
3819 // Get the default folder icon and convert it to a `Bitmap`.
3820 Drawable folderIconDrawable = folderIconImageView.getDrawable();
3821 BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable;
3822 folderIconBitmap = folderIconBitmapDrawable.getBitmap();
3823 } else { // Use the `WebView` favorite icon.
3824 folderIconBitmap = MainWebViewActivity.favoriteIconBitmap;
3827 // Convert the folder `Bitmap` to a byte array. `0` is for lossless compression (the only option for a PNG).
3828 ByteArrayOutputStream folderIconByteArrayOutputStream = new ByteArrayOutputStream();
3829 folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, folderIconByteArrayOutputStream);
3830 byte[] folderIconByteArray = folderIconByteArrayOutputStream.toByteArray();
3832 // Update the folder name and icon in the database.
3833 bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, oldFolderNameString, newFolderNameString, folderIconByteArray);
3836 // Update the bookmarks cursor with the current contents of this folder.
3837 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder);
3839 // Update the `ListView`.
3840 bookmarksCursorAdapter.changeCursor(bookmarksCursor);
3844 public void onHttpAuthenticationCancel() {
3845 // Cancel the `HttpAuthHandler`.
3846 httpAuthHandler.cancel();
3850 public void onHttpAuthenticationProceed(DialogFragment dialogFragment) {
3851 // Get handles for the `EditTexts`.
3852 EditText usernameEditText = dialogFragment.getDialog().findViewById(R.id.http_authentication_username);
3853 EditText passwordEditText = dialogFragment.getDialog().findViewById(R.id.http_authentication_password);
3855 // Proceed with the HTTP authentication.
3856 httpAuthHandler.proceed(usernameEditText.getText().toString(), passwordEditText.getText().toString());
3859 public void viewSslCertificate(View view) {
3860 // Show the `ViewSslCertificateDialog` `AlertDialog` and name this instance `@string/view_ssl_certificate`.
3861 DialogFragment viewSslCertificateDialogFragment = new ViewSslCertificateDialog();
3862 viewSslCertificateDialogFragment.show(getSupportFragmentManager(), getString(R.string.view_ssl_certificate));
3866 public void onSslErrorCancel() {
3867 sslErrorHandler.cancel();
3871 public void onSslErrorProceed() {
3872 sslErrorHandler.proceed();
3876 public void onPinnedMismatchBack() {
3877 if (mainWebView.canGoBack()) { // There is a back page in the history.
3878 // Reset the formatted URL string so the page will load correctly if blocking of third-party requests is enabled.
3879 formattedUrlString = "";
3881 // Set `navigatingHistory` so that the domain settings are applied when the new URL is loaded.
3882 navigatingHistory = true;
3885 mainWebView.goBack();
3886 } else { // There are no pages to go back to.
3887 // Load a blank page
3893 public void onPinnedMismatchProceed() {
3894 // Do not check the pinned information for this domain again until the domain changes.
3895 ignorePinnedDomainInformation = true;
3899 public void onUrlHistoryEntrySelected(int moveBackOrForwardSteps) {
3900 // Reset the formatted URL string so the page will load correctly if blocking of third-party requests is enabled.
3901 formattedUrlString = "";
3903 // Set `navigatingHistory` so that the domain settings are applied when the new URL is loaded.
3904 navigatingHistory = true;
3906 // Load the history entry.
3907 mainWebView.goBackOrForward(moveBackOrForwardSteps);
3911 public void onClearHistory() {
3912 // Clear the history.
3913 mainWebView.clearHistory();
3916 // Override `onBackPressed` to handle the navigation drawer and `mainWebView`.
3918 public void onBackPressed() {
3919 // Get a handle for the drawer layout.
3920 DrawerLayout drawerLayout = findViewById(R.id.drawerlayout);
3922 if (drawerLayout.isDrawerVisible(GravityCompat.START)) { // The navigation drawer is open.
3923 // Close the navigation drawer.
3924 drawerLayout.closeDrawer(GravityCompat.START);
3925 } else if (drawerLayout.isDrawerVisible(GravityCompat.END)){ // The bookmarks drawer is open.
3926 if (currentBookmarksFolder.isEmpty()) { // The home folder is displayed.
3927 // close the bookmarks drawer.
3928 drawerLayout.closeDrawer(GravityCompat.END);
3929 } else { // A subfolder is displayed.
3930 // Place the former parent folder in `currentFolder`.
3931 currentBookmarksFolder = bookmarksDatabaseHelper.getParentFolderName(currentBookmarksFolder);
3933 // Load the new folder.
3934 loadBookmarksFolder();
3937 } else if (mainWebView.canGoBack()) { // There is at least one item in the `WebView` history.
3938 // Reset the formatted URL string so the page will load correctly if blocking of third-party requests is enabled.
3939 formattedUrlString = "";
3941 // Set `navigatingHistory` so that the domain settings are applied when the new URL is loaded.
3942 navigatingHistory = true;
3945 mainWebView.goBack();
3946 } else { // There isn't anything to do in Privacy Browser.
3947 // Pass `onBackPressed()` to the system.
3948 super.onBackPressed();
3952 // 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.
3954 public void onActivityResult(int requestCode, int resultCode, Intent data) {
3955 // File uploads only work on API >= 21.
3956 if (Build.VERSION.SDK_INT >= 21) {
3957 // Pass the file to the WebView.
3958 fileChooserCallback.onReceiveValue(WebChromeClient.FileChooserParams.parseResult(resultCode, data));
3962 private void loadUrlFromTextBox() {
3963 // Get the text from urlTextBox and convert it to a string. trim() removes white spaces from the beginning and end of the string.
3964 String unformattedUrlString = urlTextBox.getText().toString().trim();
3966 // Check to see if `unformattedUrlString` is a valid URL. Otherwise, convert it into a search.
3967 if (unformattedUrlString.startsWith("content://")) {
3968 // Load the entire content URL.
3969 formattedUrlString = unformattedUrlString;
3970 } else if (Patterns.WEB_URL.matcher(unformattedUrlString).matches() || unformattedUrlString.startsWith("http://") || unformattedUrlString.startsWith("https://")
3971 || unformattedUrlString.startsWith("file://")) {
3972 // Add `https://` at the beginning if there is no protocol. Otherwise the app will segfault.
3973 if (!unformattedUrlString.startsWith("http") && !unformattedUrlString.startsWith("file://") && !unformattedUrlString.startsWith("content://")) {
3974 unformattedUrlString = "https://" + unformattedUrlString;
3977 // Initialize `unformattedUrl`.
3978 URL unformattedUrl = null;
3980 // 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.
3982 unformattedUrl = new URL(unformattedUrlString);
3983 } catch (MalformedURLException e) {
3984 e.printStackTrace();
3987 // The ternary operator (? :) makes sure that a null pointer exception is not thrown, which would happen if `.get` was called on a `null` value.
3988 String scheme = unformattedUrl != null ? unformattedUrl.getProtocol() : null;
3989 String authority = unformattedUrl != null ? unformattedUrl.getAuthority() : null;
3990 String path = unformattedUrl != null ? unformattedUrl.getPath() : null;
3991 String query = unformattedUrl != null ? unformattedUrl.getQuery() : null;
3992 String fragment = unformattedUrl != null ? unformattedUrl.getRef() : null;
3995 Uri.Builder formattedUri = new Uri.Builder();
3996 formattedUri.scheme(scheme).authority(authority).path(path).query(query).fragment(fragment);
3998 // Decode `formattedUri` as a `String` in `UTF-8`.
4000 formattedUrlString = URLDecoder.decode(formattedUri.build().toString(), "UTF-8");
4001 } catch (UnsupportedEncodingException exception) {
4002 // Load a blank string.
4003 formattedUrlString = "";
4005 } else if (unformattedUrlString.isEmpty()){ // Load a blank web site.
4006 // Load a blank string.
4007 formattedUrlString = "";
4008 } else { // Search for the contents of the URL box.
4009 // Create an encoded URL String.
4010 String encodedUrlString;
4012 // Sanitize the search input.
4014 encodedUrlString = URLEncoder.encode(unformattedUrlString, "UTF-8");
4015 } catch (UnsupportedEncodingException exception) {
4016 encodedUrlString = "";
4019 // Add the base search URL.
4020 formattedUrlString = searchURL + encodedUrlString;
4023 // 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.
4024 urlTextBox.clearFocus();
4027 loadUrl(formattedUrlString);
4030 private void loadUrl(String url) {// Apply any custom domain settings.
4031 // Set the URL as the formatted URL string so that checking third-party requests works correctly.
4032 formattedUrlString = url;
4034 // Apply the domain settings.
4035 applyDomainSettings(url, true, false);
4037 // If loading a website, set `urlIsLoading` to prevent changes in the user agent on websites with redirects from reloading the current website.
4038 urlIsLoading = !url.equals("");
4041 mainWebView.loadUrl(url, customHeaders);
4044 public void findPreviousOnPage(View view) {
4045 // Go to the previous highlighted phrase on the page. `false` goes backwards instead of forwards.
4046 mainWebView.findNext(false);
4049 public void findNextOnPage(View view) {
4050 // Go to the next highlighted phrase on the page. `true` goes forwards instead of backwards.
4051 mainWebView.findNext(true);
4054 public void closeFindOnPage(View view) {
4055 // Get a handle for the views.
4056 Toolbar toolbar = findViewById(R.id.toolbar);
4057 LinearLayout findOnPageLinearLayout = findViewById(R.id.find_on_page_linearlayout);
4059 // Delete the contents of `find_on_page_edittext`.
4060 findOnPageEditText.setText(null);
4062 // Clear the highlighted phrases.
4063 mainWebView.clearMatches();
4065 // Hide the find on page linear layout.
4066 findOnPageLinearLayout.setVisibility(View.GONE);
4068 // Show the toolbar.
4069 toolbar.setVisibility(View.VISIBLE);
4071 // Hide the keyboard.
4072 inputMethodManager.hideSoftInputFromWindow(mainWebView.getWindowToken(), 0);
4075 private void applyAppSettings() {
4076 // Get a handle for the shared preferences.
4077 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
4079 // Store the values from the shared preferences in variables.
4080 incognitoModeEnabled = sharedPreferences.getBoolean("incognito_mode", false);
4081 boolean doNotTrackEnabled = sharedPreferences.getBoolean("do_not_track", false);
4082 proxyThroughOrbot = sharedPreferences.getBoolean("proxy_through_orbot", false);
4083 fullScreenBrowsingModeEnabled = sharedPreferences.getBoolean("full_screen_browsing_mode", false);
4084 hideAppBar = sharedPreferences.getBoolean("hide_app_bar", true);
4085 downloadWithExternalApp = sharedPreferences.getBoolean("download_with_external_app", false);
4087 // Get handles for the views that need to be modified. `getSupportActionBar()` must be used until the minimum API >= 21.
4088 FrameLayout rootFrameLayout = findViewById(R.id.root_framelayout);
4089 ActionBar actionBar = getSupportActionBar();
4091 // Remove the incorrect lint warnings below that the action bar might be null.
4092 assert actionBar != null;
4094 // Apply the proxy through Orbot settings.
4095 applyProxyThroughOrbot(false);
4097 // Set Do Not Track status.
4098 if (doNotTrackEnabled) {
4099 customHeaders.put("DNT", "1");
4101 customHeaders.remove("DNT");
4104 // Set the app bar scrolling.
4105 mainWebView.setNestedScrollingEnabled(sharedPreferences.getBoolean("scroll_app_bar", true));
4107 // Update the full screen browsing mode settings.
4108 if (fullScreenBrowsingModeEnabled && inFullScreenBrowsingMode) { // Privacy Browser is currently in full screen browsing mode.
4109 // Update the visibility of the app bar, which might have changed in the settings.
4116 // Hide the banner ad in the free flavor.
4117 if (BuildConfig.FLAVOR.contentEquals("free")) {
4118 AdHelper.hideAd(findViewById(R.id.adview));
4121 // Remove the translucent status flag. This is necessary so the root frame layout can fill the entire screen.
4122 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
4124 /* Hide the system bars.
4125 * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
4126 * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
4127 * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
4128 * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
4130 rootFrameLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
4131 View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
4132 } else { // Privacy Browser is not in full screen browsing mode.
4133 // 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.
4134 inFullScreenBrowsingMode = false;
4136 // Show the app bar.
4139 // Show the banner ad in the free flavor.
4140 if (BuildConfig.FLAVOR.contentEquals("free")) {
4141 // Initialize the ads. If this isn't the first run, `loadAd()` will be automatically called instead.
4142 AdHelper.initializeAds(findViewById(R.id.adview), getApplicationContext(), fragmentManager, getString(R.string.google_app_id), getString(R.string.ad_unit_id));
4145 // Remove the `SYSTEM_UI` flags from the root frame layout.
4146 rootFrameLayout.setSystemUiVisibility(0);
4148 // Add the translucent status flag.
4149 getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
4154 // `reloadWebsite` is used if returning from the Domains activity. Otherwise JavaScript might not function correctly if it is newly enabled.
4155 // The deprecated `.getDrawable()` must be used until the minimum API >= 21.
4156 @SuppressWarnings("deprecation")
4157 private boolean applyDomainSettings(String url, boolean resetFavoriteIcon, boolean reloadWebsite) {
4158 // Get the current user agent.
4159 String initialUserAgent = mainWebView.getSettings().getUserAgentString();
4161 // Initialize a variable to track if the user agent changes.
4162 boolean userAgentChanged = false;
4164 // Parse the URL into a URI.
4165 Uri uri = Uri.parse(url);
4167 // Extract the domain from `uri`.
4168 String hostName = uri.getHost();
4170 // Initialize `loadingNewDomainName`.
4171 boolean loadingNewDomainName;
4173 // If either `hostName` or `currentDomainName` are `null`, run the options for loading a new domain name.
4174 // The lint suggestion to simplify the `if` statement is incorrect, because `hostName.equals(currentDomainName)` can produce a `null object reference.`
4175 //noinspection SimplifiableIfStatement
4176 if ((hostName == null) || (currentDomainName == null)) {
4177 loadingNewDomainName = true;
4178 } else { // Determine if `hostName` equals `currentDomainName`.
4179 loadingNewDomainName = !hostName.equals(currentDomainName);
4182 // Strings don't like to be null.
4183 if (hostName == null) {
4187 // 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.
4188 if (loadingNewDomainName) {
4189 // Set the new `hostname` as the `currentDomainName`.
4190 currentDomainName = hostName;
4192 // Reset the ignoring of pinned domain information.
4193 ignorePinnedDomainInformation = false;
4195 // Reset the favorite icon if specified.
4196 if (resetFavoriteIcon) {
4197 favoriteIconBitmap = favoriteIconDefaultBitmap;
4198 favoriteIconImageView.setImageBitmap(Bitmap.createScaledBitmap(favoriteIconBitmap, 64, 64, true));
4201 // Get a handle for the swipe refresh layout.
4202 SwipeRefreshLayout swipeRefreshLayout = findViewById(R.id.swiperefreshlayout);
4204 // Initialize the database handler. The `0` specifies the database version, but that is ignored and set instead using a constant in `DomainsDatabaseHelper`.
4205 DomainsDatabaseHelper domainsDatabaseHelper = new DomainsDatabaseHelper(this, null, null, 0);
4207 // Get a full cursor from `domainsDatabaseHelper`.
4208 Cursor domainNameCursor = domainsDatabaseHelper.getDomainNameCursorOrderedByDomain();
4210 // Initialize `domainSettingsSet`.
4211 Set<String> domainSettingsSet = new HashSet<>();
4213 // Get the domain name column index.
4214 int domainNameColumnIndex = domainNameCursor.getColumnIndex(DomainsDatabaseHelper.DOMAIN_NAME);
4216 // Populate `domainSettingsSet`.
4217 for (int i = 0; i < domainNameCursor.getCount(); i++) {
4218 // Move `domainsCursor` to the current row.
4219 domainNameCursor.moveToPosition(i);
4221 // Store the domain name in `domainSettingsSet`.
4222 domainSettingsSet.add(domainNameCursor.getString(domainNameColumnIndex));
4225 // Close `domainNameCursor.
4226 domainNameCursor.close();
4228 // Initialize variables to track if domain settings will be applied and, if so, under which name.
4229 domainSettingsApplied = false;
4230 String domainNameInDatabase = null;
4232 // Check the hostname.
4233 if (domainSettingsSet.contains(hostName)) {
4234 domainSettingsApplied = true;
4235 domainNameInDatabase = hostName;
4238 // Check all the subdomains of the host name against wildcard domains in the domain cursor.
4239 while (!domainSettingsApplied && hostName.contains(".")) { // Stop checking if domain settings are already applied or there are no more `.` in the host name.
4240 if (domainSettingsSet.contains("*." + hostName)) { // Check the host name prepended by `*.`.
4241 // Apply the domain settings.
4242 domainSettingsApplied = true;
4244 // Store the applied domain names as it appears in the database.
4245 domainNameInDatabase = "*." + hostName;
4248 // Strip out the lowest subdomain of of the host name.
4249 hostName = hostName.substring(hostName.indexOf(".") + 1);
4253 // Get a handle for the shared preference.
4254 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
4256 // Store the general preference information.
4257 String defaultFontSizeString = sharedPreferences.getString("font_size", getString(R.string.font_size_default_value));
4258 String defaultUserAgentName = sharedPreferences.getString("user_agent", getString(R.string.user_agent_default_value));
4259 defaultCustomUserAgentString = sharedPreferences.getString("custom_user_agent", getString(R.string.custom_user_agent_default_value));
4260 boolean defaultSwipeToRefresh = sharedPreferences.getBoolean("swipe_to_refresh", true);
4261 nightMode = sharedPreferences.getBoolean("night_mode", false);
4262 boolean displayWebpageImages = sharedPreferences.getBoolean("display_webpage_images", true);
4264 if (domainSettingsApplied) { // The url has custom domain settings.
4265 // Get a cursor for the current host and move it to the first position.
4266 Cursor currentHostDomainSettingsCursor = domainsDatabaseHelper.getCursorForDomainName(domainNameInDatabase);
4267 currentHostDomainSettingsCursor.moveToFirst();
4269 // Get the settings from the cursor.
4270 domainSettingsDatabaseId = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper._ID)));
4271 javaScriptEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_JAVASCRIPT)) == 1);
4272 firstPartyCookiesEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FIRST_PARTY_COOKIES)) == 1);
4273 thirdPartyCookiesEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_THIRD_PARTY_COOKIES)) == 1);
4274 domStorageEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_DOM_STORAGE)) == 1);
4275 // Form data can be removed once the minimum API >= 26.
4276 saveFormDataEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FORM_DATA)) == 1);
4277 easyListEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_EASYLIST)) == 1);
4278 easyPrivacyEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_EASYPRIVACY)) == 1);
4279 fanboysAnnoyanceListEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FANBOYS_ANNOYANCE_LIST)) == 1);
4280 fanboysSocialBlockingListEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FANBOYS_SOCIAL_BLOCKING_LIST)) == 1);
4281 ultraPrivacyEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_ULTRAPRIVACY)) == 1);
4282 blockAllThirdPartyRequests = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.BLOCK_ALL_THIRD_PARTY_REQUESTS)) == 1);
4283 String userAgentName = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.USER_AGENT));
4284 int fontSize = currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.FONT_SIZE));
4285 int swipeToRefreshInt = currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SWIPE_TO_REFRESH));
4286 int nightModeInt = currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.NIGHT_MODE));
4287 int displayWebpageImagesInt = currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.DISPLAY_IMAGES));
4288 pinnedSslCertificate = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.PINNED_SSL_CERTIFICATE)) == 1);
4289 pinnedSslIssuedToCName = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_TO_COMMON_NAME));
4290 pinnedSslIssuedToOName = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_TO_ORGANIZATION));
4291 pinnedSslIssuedToUName = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_TO_ORGANIZATIONAL_UNIT));
4292 pinnedSslIssuedByCName = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_BY_COMMON_NAME));
4293 pinnedSslIssuedByOName = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_BY_ORGANIZATION));
4294 pinnedSslIssuedByUName = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_BY_ORGANIZATIONAL_UNIT));
4295 pinnedIpAddresses = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.PINNED_IP_ADDRESSES)) == 1);
4296 pinnedHostIpAddresses = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.IP_ADDRESSES));
4298 // Set `nightMode` according to `nightModeInt`. If `nightModeInt` is `DomainsDatabaseHelper.NIGHT_MODE_SYSTEM_DEFAULT` the current setting from `sharedPreferences` will be used.
4299 switch (nightModeInt) {
4300 case DomainsDatabaseHelper.NIGHT_MODE_ENABLED:
4304 case DomainsDatabaseHelper.NIGHT_MODE_DISABLED:
4309 // Store the domain JavaScript status. This is used by the options menu night mode toggle.
4310 domainSettingsJavaScriptEnabled = javaScriptEnabled;
4312 // Enable JavaScript if night mode is enabled.
4314 javaScriptEnabled = true;
4317 // Set the pinned SSL certificate start date to `null` if the saved date `long` is 0.
4318 if (currentHostDomainSettingsCursor.getLong(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_START_DATE)) == 0) {
4319 pinnedSslStartDate = null;
4321 pinnedSslStartDate = new Date(currentHostDomainSettingsCursor.getLong(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_START_DATE)));
4324 // Set the pinned SSL certificate end date to `null` if the saved date `long` is 0.
4325 if (currentHostDomainSettingsCursor.getLong(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_END_DATE)) == 0) {
4326 pinnedSslEndDate = null;
4328 pinnedSslEndDate = new Date(currentHostDomainSettingsCursor.getLong(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_END_DATE)));
4331 // Close `currentHostDomainSettingsCursor`.
4332 currentHostDomainSettingsCursor.close();
4334 // Apply the domain settings.
4335 mainWebView.getSettings().setJavaScriptEnabled(javaScriptEnabled);
4336 cookieManager.setAcceptCookie(firstPartyCookiesEnabled);
4337 mainWebView.getSettings().setDomStorageEnabled(domStorageEnabled);
4339 // Apply the form data setting if the API < 26.
4340 if (Build.VERSION.SDK_INT < 26) {
4341 mainWebView.getSettings().setSaveFormData(saveFormDataEnabled);
4344 // Apply the font size.
4345 if (fontSize == 0) { // Apply the default font size.
4346 mainWebView.getSettings().setTextZoom(Integer.valueOf(defaultFontSizeString));
4347 } else { // Apply the specified font size.
4348 mainWebView.getSettings().setTextZoom(fontSize);
4351 // Set third-party cookies status if API >= 21.
4352 if (Build.VERSION.SDK_INT >= 21) {
4353 cookieManager.setAcceptThirdPartyCookies(mainWebView, thirdPartyCookiesEnabled);
4356 // 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.
4357 // <https://redmine.stoutner.com/issues/160>
4358 if (!urlIsLoading) {
4359 // Set the user agent.
4360 if (userAgentName.equals(getString(R.string.system_default_user_agent))) { // Use the system default user agent.
4361 // Get the array position of the default user agent name.
4362 int defaultUserAgentArrayPosition = userAgentNamesArray.getPosition(defaultUserAgentName);
4364 // Set the user agent according to the system default.
4365 switch (defaultUserAgentArrayPosition) {
4366 case UNRECOGNIZED_USER_AGENT: // The default user agent name is not on the canonical list.
4367 // This is probably because it was set in an older version of Privacy Browser before the switch to persistent user agent names.
4368 mainWebView.getSettings().setUserAgentString(defaultUserAgentName);
4371 case SETTINGS_WEBVIEW_DEFAULT_USER_AGENT:
4372 // Set the user agent to `""`, which uses the default value.
4373 mainWebView.getSettings().setUserAgentString("");
4376 case SETTINGS_CUSTOM_USER_AGENT:
4377 // Set the custom user agent.
4378 mainWebView.getSettings().setUserAgentString(defaultCustomUserAgentString);
4382 // Get the user agent string from the user agent data array
4383 mainWebView.getSettings().setUserAgentString(userAgentDataArray[defaultUserAgentArrayPosition]);
4385 } else { // Set the user agent according to the stored name.
4386 // Get the array position of the user agent name.
4387 int userAgentArrayPosition = userAgentNamesArray.getPosition(userAgentName);
4389 switch (userAgentArrayPosition) {
4390 case UNRECOGNIZED_USER_AGENT: // The user agent name contains a custom user agent.
4391 mainWebView.getSettings().setUserAgentString(userAgentName);
4394 case SETTINGS_WEBVIEW_DEFAULT_USER_AGENT:
4395 // Set the user agent to `""`, which uses the default value.
4396 mainWebView.getSettings().setUserAgentString("");
4400 // Get the user agent string from the user agent data array.
4401 mainWebView.getSettings().setUserAgentString(userAgentDataArray[userAgentArrayPosition]);
4405 // Store the applied user agent string, which is used in the View Source activity.
4406 appliedUserAgentString = mainWebView.getSettings().getUserAgentString();
4408 // Update the user agent change tracker.
4409 userAgentChanged = !appliedUserAgentString.equals(initialUserAgent);
4412 // Set swipe to refresh.
4413 switch (swipeToRefreshInt) {
4414 case DomainsDatabaseHelper.SWIPE_TO_REFRESH_SYSTEM_DEFAULT:
4415 // Set swipe to refresh according to the default.
4416 swipeRefreshLayout.setEnabled(defaultSwipeToRefresh);
4419 case DomainsDatabaseHelper.SWIPE_TO_REFRESH_ENABLED:
4420 // Enable swipe to refresh.
4421 swipeRefreshLayout.setEnabled(true);
4424 case DomainsDatabaseHelper.SWIPE_TO_REFRESH_DISABLED:
4425 // Disable swipe to refresh.
4426 swipeRefreshLayout.setEnabled(false);
4429 // Set the loading of webpage images.
4430 switch (displayWebpageImagesInt) {
4431 case DomainsDatabaseHelper.DISPLAY_WEBPAGE_IMAGES_SYSTEM_DEFAULT:
4432 mainWebView.getSettings().setLoadsImagesAutomatically(displayWebpageImages);
4435 case DomainsDatabaseHelper.DISPLAY_WEBPAGE_IMAGES_ENABLED:
4436 mainWebView.getSettings().setLoadsImagesAutomatically(true);
4439 case DomainsDatabaseHelper.DISPLAY_WEBPAGE_IMAGES_DISABLED:
4440 mainWebView.getSettings().setLoadsImagesAutomatically(false);
4444 // 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.
4446 urlAppBarRelativeLayout.setBackground(getResources().getDrawable(R.drawable.url_bar_background_dark_blue));
4448 urlAppBarRelativeLayout.setBackground(getResources().getDrawable(R.drawable.url_bar_background_light_green));
4450 } else { // The new URL does not have custom domain settings. Load the defaults.
4451 // Store the values from `sharedPreferences` in variables.
4452 javaScriptEnabled = sharedPreferences.getBoolean("javascript", false);
4453 firstPartyCookiesEnabled = sharedPreferences.getBoolean("first_party_cookies", false);
4454 thirdPartyCookiesEnabled = sharedPreferences.getBoolean("third_party_cookies", false);
4455 domStorageEnabled = sharedPreferences.getBoolean("dom_storage", false);
4456 saveFormDataEnabled = sharedPreferences.getBoolean("save_form_data", false); // Form data can be removed once the minimum API >= 26.
4457 easyListEnabled = sharedPreferences.getBoolean("easylist", true);
4458 easyPrivacyEnabled = sharedPreferences.getBoolean("easyprivacy", true);
4459 fanboysAnnoyanceListEnabled = sharedPreferences.getBoolean("fanboys_annoyance_list", true);
4460 fanboysSocialBlockingListEnabled = sharedPreferences.getBoolean("fanboys_social_blocking_list", true);
4461 ultraPrivacyEnabled = sharedPreferences.getBoolean("ultraprivacy", true);
4462 blockAllThirdPartyRequests = sharedPreferences.getBoolean("block_all_third_party_requests", false);
4464 // Set `javaScriptEnabled` to be `true` if `night_mode` is `true`.
4466 javaScriptEnabled = true;
4469 // Apply the default settings.
4470 mainWebView.getSettings().setJavaScriptEnabled(javaScriptEnabled);
4471 cookieManager.setAcceptCookie(firstPartyCookiesEnabled);
4472 mainWebView.getSettings().setDomStorageEnabled(domStorageEnabled);
4473 mainWebView.getSettings().setTextZoom(Integer.valueOf(defaultFontSizeString));
4474 swipeRefreshLayout.setEnabled(defaultSwipeToRefresh);
4476 // Apply the form data setting if the API < 26.
4477 if (Build.VERSION.SDK_INT < 26) {
4478 mainWebView.getSettings().setSaveFormData(saveFormDataEnabled);
4481 // Reset the pinned variables.
4482 domainSettingsDatabaseId = -1;
4483 pinnedSslCertificate = false;
4484 pinnedSslIssuedToCName = "";
4485 pinnedSslIssuedToOName = "";
4486 pinnedSslIssuedToUName = "";
4487 pinnedSslIssuedByCName = "";
4488 pinnedSslIssuedByOName = "";
4489 pinnedSslIssuedByUName = "";
4490 pinnedSslStartDate = null;
4491 pinnedSslEndDate = null;
4492 pinnedIpAddresses = false;
4493 pinnedHostIpAddresses = "";
4495 // Set third-party cookies status if API >= 21.
4496 if (Build.VERSION.SDK_INT >= 21) {
4497 cookieManager.setAcceptThirdPartyCookies(mainWebView, thirdPartyCookiesEnabled);
4500 // 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.
4501 // <https://redmine.stoutner.com/issues/160>
4502 if (!urlIsLoading) {
4503 // Get the array position of the user agent name.
4504 int userAgentArrayPosition = userAgentNamesArray.getPosition(defaultUserAgentName);
4506 // Set the user agent.
4507 switch (userAgentArrayPosition) {
4508 case UNRECOGNIZED_USER_AGENT: // The default user agent name is not on the canonical list.
4509 // This is probably because it was set in an older version of Privacy Browser before the switch to persistent user agent names.
4510 mainWebView.getSettings().setUserAgentString(defaultUserAgentName);
4513 case SETTINGS_WEBVIEW_DEFAULT_USER_AGENT:
4514 // Set the user agent to `""`, which uses the default value.
4515 mainWebView.getSettings().setUserAgentString("");
4518 case SETTINGS_CUSTOM_USER_AGENT:
4519 // Set the custom user agent.
4520 mainWebView.getSettings().setUserAgentString(defaultCustomUserAgentString);
4524 // Get the user agent string from the user agent data array
4525 mainWebView.getSettings().setUserAgentString(userAgentDataArray[userAgentArrayPosition]);
4528 // Store the applied user agent string, which is used in the View Source activity.
4529 appliedUserAgentString = mainWebView.getSettings().getUserAgentString();
4531 // Update the user agent change tracker.
4532 userAgentChanged = !appliedUserAgentString.equals(initialUserAgent);
4535 // Set the loading of webpage images.
4536 mainWebView.getSettings().setLoadsImagesAutomatically(displayWebpageImages);
4538 // Set a transparent background on `urlTextBox`. The deprecated `.getDrawable()` must be used until the minimum API >= 21.
4539 urlAppBarRelativeLayout.setBackgroundDrawable(getResources().getDrawable(R.color.transparent));
4542 // Close the domains database helper.
4543 domainsDatabaseHelper.close();
4545 // Update the privacy icons, but only if `mainMenu` has already been populated.
4546 if (mainMenu != null) {
4547 updatePrivacyIcons(true);
4551 // Reload the website if returning from the Domains activity.
4552 if (reloadWebsite) {
4553 mainWebView.reload();
4556 // Return the user agent changed status.
4557 return userAgentChanged;
4560 private void applyProxyThroughOrbot(boolean reloadWebsite) {
4561 // Get a handle for the shared preferences.
4562 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
4564 // Get the search preferences.
4565 String homepageString = sharedPreferences.getString("homepage", getString(R.string.homepage_default_value));
4566 String torHomepageString = sharedPreferences.getString("tor_homepage", getString(R.string.tor_homepage_default_value));
4567 String torSearchString = sharedPreferences.getString("tor_search", getString(R.string.tor_search_default_value));
4568 String torSearchCustomUrlString = sharedPreferences.getString("tor_search_custom_url", getString(R.string.tor_search_custom_url_default_value));
4569 String searchString = sharedPreferences.getString("search", getString(R.string.search_default_value));
4570 String searchCustomUrlString = sharedPreferences.getString("search_custom_url", getString(R.string.search_custom_url_default_value));
4572 // Get a handle for the action bar. `getSupportActionBar()` must be used until the minimum API >= 21.
4573 ActionBar actionBar = getSupportActionBar();
4575 // Remove the incorrect lint warning later that the action bar might be null.
4576 assert actionBar != null;
4578 // Set the homepage, search, and proxy options.
4579 if (proxyThroughOrbot) { // Set the Tor options.
4580 // Set `torHomepageString` as `homepage`.
4581 homepage = torHomepageString;
4583 // If formattedUrlString is null assign the homepage to it.
4584 if (formattedUrlString == null) {
4585 formattedUrlString = homepage;
4588 // Set the search URL.
4589 if (torSearchString.equals("Custom URL")) { // Get the custom URL string.
4590 searchURL = torSearchCustomUrlString;
4591 } else { // Use the string from the pre-built list.
4592 searchURL = torSearchString;
4595 // Set the proxy. `this` refers to the current activity where an `AlertDialog` might be displayed.
4596 OrbotProxyHelper.setProxy(getApplicationContext(), this, "localhost", "8118");
4598 // Set the `appBar` background to indicate proxying through Orbot is enabled. `this` refers to the context.
4600 actionBar.setBackgroundDrawable(ContextCompat.getDrawable(this, R.color.dark_blue_30));
4602 actionBar.setBackgroundDrawable(ContextCompat.getDrawable(this, R.color.blue_50));
4605 // Check to see if Orbot is ready.
4606 if (!orbotStatus.equals("ON")) { // Orbot is not ready.
4607 // Set `waitingForOrbot`.
4608 waitingForOrbot = true;
4610 // Disable the wide view port so that the waiting for Orbot text is displayed correctly.
4611 mainWebView.getSettings().setUseWideViewPort(false);
4613 // Load a waiting page. `null` specifies no encoding, which defaults to ASCII.
4614 mainWebView.loadData(waitingForOrbotHtmlString, "text/html", null);
4615 } else if (reloadWebsite) { // Orbot is ready and the website should be reloaded.
4616 // Reload the website.
4617 mainWebView.reload();
4619 } else { // Set the non-Tor options.
4620 // Set `homepageString` as `homepage`.
4621 homepage = homepageString;
4623 // If formattedUrlString is null assign the homepage to it.
4624 if (formattedUrlString == null) {
4625 formattedUrlString = homepage;
4628 // Set the search URL.
4629 if (searchString.equals("Custom URL")) { // Get the custom URL string.
4630 searchURL = searchCustomUrlString;
4631 } else { // Use the string from the pre-built list.
4632 searchURL = searchString;
4635 // Reset the proxy to default. The host is `""` and the port is `"0"`.
4636 OrbotProxyHelper.setProxy(getApplicationContext(), this, "", "0");
4638 // Set the default `appBar` background. `this` refers to the context.
4640 actionBar.setBackgroundDrawable(ContextCompat.getDrawable(this, R.color.gray_900));
4642 actionBar.setBackgroundDrawable(ContextCompat.getDrawable(this, R.color.gray_100));
4645 // Reset `waitingForOrbot.
4646 waitingForOrbot = false;
4648 // Reload the website if requested.
4649 if (reloadWebsite) {
4650 mainWebView.reload();
4655 private void updatePrivacyIcons(boolean runInvalidateOptionsMenu) {
4656 // Get handles for the menu items.
4657 MenuItem privacyMenuItem = mainMenu.findItem(R.id.toggle_javascript);
4658 MenuItem firstPartyCookiesMenuItem = mainMenu.findItem(R.id.toggle_first_party_cookies);
4659 MenuItem domStorageMenuItem = mainMenu.findItem(R.id.toggle_dom_storage);
4660 MenuItem refreshMenuItem = mainMenu.findItem(R.id.refresh);
4662 // Update the privacy icon.
4663 if (javaScriptEnabled) { // JavaScript is enabled.
4664 privacyMenuItem.setIcon(R.drawable.javascript_enabled);
4665 } else if (firstPartyCookiesEnabled) { // JavaScript is disabled but cookies are enabled.
4666 privacyMenuItem.setIcon(R.drawable.warning);
4667 } else { // All the dangerous features are disabled.
4668 privacyMenuItem.setIcon(R.drawable.privacy_mode);
4671 // Update the first-party cookies icon.
4672 if (firstPartyCookiesEnabled) { // First-party cookies are enabled.
4673 firstPartyCookiesMenuItem.setIcon(R.drawable.cookies_enabled);
4674 } else { // First-party cookies are disabled.
4676 firstPartyCookiesMenuItem.setIcon(R.drawable.cookies_disabled_dark);
4678 firstPartyCookiesMenuItem.setIcon(R.drawable.cookies_disabled_light);
4682 // Update the DOM storage icon.
4683 if (javaScriptEnabled && domStorageEnabled) { // Both JavaScript and DOM storage are enabled.
4684 domStorageMenuItem.setIcon(R.drawable.dom_storage_enabled);
4685 } else if (javaScriptEnabled) { // JavaScript is enabled but DOM storage is disabled.
4687 domStorageMenuItem.setIcon(R.drawable.dom_storage_disabled_dark);
4689 domStorageMenuItem.setIcon(R.drawable.dom_storage_disabled_light);
4691 } else { // JavaScript is disabled, so DOM storage is ghosted.
4693 domStorageMenuItem.setIcon(R.drawable.dom_storage_ghosted_dark);
4695 domStorageMenuItem.setIcon(R.drawable.dom_storage_ghosted_light);
4699 // Update the refresh icon.
4701 refreshMenuItem.setIcon(R.drawable.refresh_enabled_dark);
4703 refreshMenuItem.setIcon(R.drawable.refresh_enabled_light);
4706 // `invalidateOptionsMenu` calls `onPrepareOptionsMenu()` and redraws the icons in the `AppBar`.
4707 if (runInvalidateOptionsMenu) {
4708 invalidateOptionsMenu();
4712 private void openUrlWithExternalApp(String url) {
4713 // Create a download intent. Not specifying the action type will display the maximum number of options.
4714 Intent downloadIntent = new Intent();
4716 // Set the URI and the MIME type. Specifying `text/html` displays a good number of options.
4717 downloadIntent.setDataAndType(Uri.parse(url), "text/html");
4719 // Flag the intent to open in a new task.
4720 downloadIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
4722 // Show the chooser.
4723 startActivity(Intent.createChooser(downloadIntent, getString(R.string.open_with)));
4726 private void highlightUrlText() {
4727 // Only highlight the URL text if the box is not currently selected.
4728 if (!urlTextBox.hasFocus()) {
4729 // Get the URL string.
4730 String urlString = urlTextBox.getText().toString();
4732 // Highlight the URL according to the protocol.
4733 if (urlString.startsWith("file://")) { // This is a file URL.
4734 // De-emphasize only the protocol.
4735 urlTextBox.getText().setSpan(initialGrayColorSpan, 0, 7, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
4736 } else if (urlString.startsWith("content://")) {
4737 // De-emphasize only the protocol.
4738 urlTextBox.getText().setSpan(initialGrayColorSpan, 0, 10, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
4739 } else { // This is a web URL.
4740 // Get the index of the `/` immediately after the domain name.
4741 int endOfDomainName = urlString.indexOf("/", (urlString.indexOf("//") + 2));
4743 // Create a base URL string.
4746 // Get the base URL.
4747 if (endOfDomainName > 0) { // There is at least one character after the base URL.
4748 // Get the base URL.
4749 baseUrl = urlString.substring(0, endOfDomainName);
4750 } else { // There are no characters after the base URL.
4751 // Set the base URL to be the entire URL string.
4752 baseUrl = urlString;
4755 // Get the index of the last `.` in the domain.
4756 int lastDotIndex = baseUrl.lastIndexOf(".");
4758 // Get the index of the penultimate `.` in the domain.
4759 int penultimateDotIndex = baseUrl.lastIndexOf(".", lastDotIndex - 1);
4761 // Markup the beginning of the URL.
4762 if (urlString.startsWith("http://")) { // Highlight the protocol of connections that are not encrypted.
4763 urlTextBox.getText().setSpan(redColorSpan, 0, 7, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
4765 // De-emphasize subdomains.
4766 if (penultimateDotIndex > 0) { // There is more than one subdomain in the domain name.
4767 urlTextBox.getText().setSpan(initialGrayColorSpan, 7, penultimateDotIndex + 1, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
4769 } else if (urlString.startsWith("https://")) { // De-emphasize the protocol of connections that are encrypted.
4770 if (penultimateDotIndex > 0) { // There is more than one subdomain in the domain name.
4771 // De-emphasize the protocol and the additional subdomains.
4772 urlTextBox.getText().setSpan(initialGrayColorSpan, 0, penultimateDotIndex + 1, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
4773 } else { // There is only one subdomain in the domain name.
4774 // De-emphasize only the protocol.
4775 urlTextBox.getText().setSpan(initialGrayColorSpan, 0, 8, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
4779 // De-emphasize the text after the domain name.
4780 if (endOfDomainName > 0) {
4781 urlTextBox.getText().setSpan(finalGrayColorSpan, endOfDomainName, urlString.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
4787 private void loadBookmarksFolder() {
4788 // Update the bookmarks cursor with the contents of the bookmarks database for the current folder.
4789 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder);
4791 // Populate the bookmarks cursor adapter. `this` specifies the `Context`. `false` disables `autoRequery`.
4792 bookmarksCursorAdapter = new CursorAdapter(this, bookmarksCursor, false) {
4794 public View newView(Context context, Cursor cursor, ViewGroup parent) {
4795 // Inflate the individual item layout. `false` does not attach it to the root.
4796 return getLayoutInflater().inflate(R.layout.bookmarks_drawer_item_linearlayout, parent, false);
4800 public void bindView(View view, Context context, Cursor cursor) {
4801 // Get handles for the views.
4802 ImageView bookmarkFavoriteIcon = view.findViewById(R.id.bookmark_favorite_icon);
4803 TextView bookmarkNameTextView = view.findViewById(R.id.bookmark_name);
4805 // Get the favorite icon byte array from the cursor.
4806 byte[] favoriteIconByteArray = cursor.getBlob(cursor.getColumnIndex(BookmarksDatabaseHelper.FAVORITE_ICON));
4808 // Convert the byte array to a `Bitmap` beginning at the first byte and ending at the last.
4809 Bitmap favoriteIconBitmap = BitmapFactory.decodeByteArray(favoriteIconByteArray, 0, favoriteIconByteArray.length);
4811 // Display the bitmap in `bookmarkFavoriteIcon`.
4812 bookmarkFavoriteIcon.setImageBitmap(favoriteIconBitmap);
4814 // Get the bookmark name from the cursor and display it in `bookmarkNameTextView`.
4815 String bookmarkNameString = cursor.getString(cursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME));
4816 bookmarkNameTextView.setText(bookmarkNameString);
4818 // Make the font bold for folders.
4819 if (cursor.getInt(cursor.getColumnIndex(BookmarksDatabaseHelper.IS_FOLDER)) == 1) {
4820 bookmarkNameTextView.setTypeface(Typeface.DEFAULT_BOLD);
4821 } else { // Reset the font to default for normal bookmarks.
4822 bookmarkNameTextView.setTypeface(Typeface.DEFAULT);
4827 // Populate the `ListView` with the adapter.
4828 bookmarksListView.setAdapter(bookmarksCursorAdapter);
4830 // Set the bookmarks drawer title.
4831 if (currentBookmarksFolder.isEmpty()) {
4832 bookmarksTitleTextView.setText(R.string.bookmarks);
4834 bookmarksTitleTextView.setText(currentBookmarksFolder);
4838 private void openWithApp(String url) {
4839 // Create the open with intent with `ACTION_VIEW`.
4840 Intent openWithAppIntent = new Intent(Intent.ACTION_VIEW);
4842 // Set the URI but not the MIME type. This should open all available apps.
4843 openWithAppIntent.setData(Uri.parse(url));
4845 // Flag the intent to open in a new task.
4846 openWithAppIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
4848 // Show the chooser.
4849 startActivity(openWithAppIntent);
4852 private void openWithBrowser(String url) {
4853 // Create the open with intent with `ACTION_VIEW`.
4854 Intent openWithBrowserIntent = new Intent(Intent.ACTION_VIEW);
4856 // Set the URI and the MIME type. `"text/html"` should load browser options.
4857 openWithBrowserIntent.setDataAndType(Uri.parse(url), "text/html");
4859 // Flag the intent to open in a new task.
4860 openWithBrowserIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
4862 // Show the chooser.
4863 startActivity(openWithBrowserIntent);
4866 private static void checkPinnedMismatch() {
4867 if ((pinnedSslCertificate || pinnedIpAddresses) && !ignorePinnedDomainInformation) {
4868 // Initialize the current SSL certificate variables.
4869 String currentWebsiteIssuedToCName = "";
4870 String currentWebsiteIssuedToOName = "";
4871 String currentWebsiteIssuedToUName = "";
4872 String currentWebsiteIssuedByCName = "";
4873 String currentWebsiteIssuedByOName = "";
4874 String currentWebsiteIssuedByUName = "";
4875 Date currentWebsiteSslStartDate = null;
4876 Date currentWebsiteSslEndDate = null;
4879 // Extract the individual pieces of information from the current website SSL certificate if it is not null.
4880 if (sslCertificate != null) {
4881 currentWebsiteIssuedToCName = sslCertificate.getIssuedTo().getCName();
4882 currentWebsiteIssuedToOName = sslCertificate.getIssuedTo().getOName();
4883 currentWebsiteIssuedToUName = sslCertificate.getIssuedTo().getUName();
4884 currentWebsiteIssuedByCName = sslCertificate.getIssuedBy().getCName();
4885 currentWebsiteIssuedByOName = sslCertificate.getIssuedBy().getOName();
4886 currentWebsiteIssuedByUName = sslCertificate.getIssuedBy().getUName();
4887 currentWebsiteSslStartDate = sslCertificate.getValidNotBeforeDate();
4888 currentWebsiteSslEndDate = sslCertificate.getValidNotAfterDate();
4891 // 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`.
4892 String currentWebsiteSslStartDateString = "";
4893 String currentWebsiteSslEndDateString = "";
4894 String pinnedSslStartDateString = "";
4895 String pinnedSslEndDateString = "";
4897 // Convert the `Dates` to `Strings` if they are not `null`.
4898 if (currentWebsiteSslStartDate != null) {
4899 currentWebsiteSslStartDateString = currentWebsiteSslStartDate.toString();
4902 if (currentWebsiteSslEndDate != null) {
4903 currentWebsiteSslEndDateString = currentWebsiteSslEndDate.toString();
4906 if (pinnedSslStartDate != null) {
4907 pinnedSslStartDateString = pinnedSslStartDate.toString();
4910 if (pinnedSslEndDate != null) {
4911 pinnedSslEndDateString = pinnedSslEndDate.toString();
4914 // Check to see if the pinned information matches the current information.
4915 if ((pinnedIpAddresses && !currentHostIpAddresses.equals(pinnedHostIpAddresses)) || (pinnedSslCertificate && (!currentWebsiteIssuedToCName.equals(pinnedSslIssuedToCName) ||
4916 !currentWebsiteIssuedToOName.equals(pinnedSslIssuedToOName) || !currentWebsiteIssuedToUName.equals(pinnedSslIssuedToUName) ||
4917 !currentWebsiteIssuedByCName.equals(pinnedSslIssuedByCName) || !currentWebsiteIssuedByOName.equals(pinnedSslIssuedByOName) ||
4918 !currentWebsiteIssuedByUName.equals(pinnedSslIssuedByUName) || !currentWebsiteSslStartDateString.equals(pinnedSslStartDateString) ||
4919 !currentWebsiteSslEndDateString.equals(pinnedSslEndDateString)))) {
4921 // Get a handle for the pinned mismatch alert dialog.
4922 DialogFragment pinnedMismatchDialogFragment = PinnedMismatchDialog.displayDialog(pinnedSslCertificate, pinnedIpAddresses);
4924 // Show the pinned mismatch alert dialog.
4925 pinnedMismatchDialogFragment.show(fragmentManager, "Pinned Mismatch");
4930 // This must run asynchronously because it involves a network request. `String` declares the parameters. `Void` does not declare progress units. `String` contains the results.
4931 private static class GetHostIpAddresses extends AsyncTask<String, Void, String> {
4932 // The weak references are used to determine if the activity have disappeared while the AsyncTask is running.
4933 private final WeakReference<Activity> activityWeakReference;
4935 GetHostIpAddresses(Activity activity) {
4936 // Populate the weak references.
4937 activityWeakReference = new WeakReference<>(activity);
4940 // `onPreExecute()` operates on the UI thread.
4942 protected void onPreExecute() {
4943 // Get a handle for the activity.
4944 Activity activity = activityWeakReference.get();
4946 // Abort if the activity is gone.
4947 if ((activity == null) || activity.isFinishing()) {
4951 // Set the getting IP addresses tracker.
4952 gettingIpAddresses = true;
4957 protected String doInBackground(String... domainName) {
4958 // Get a handle for the activity.
4959 Activity activity = activityWeakReference.get();
4961 // Abort if the activity is gone.
4962 if ((activity == null) || activity.isFinishing()) {
4963 // Return an empty spannable string builder.
4967 // Initialize an IP address string builder.
4968 StringBuilder ipAddresses = new StringBuilder();
4970 // Get an array with the IP addresses for the host.
4972 // Get an array with all the IP addresses for the domain.
4973 InetAddress[] inetAddressesArray = InetAddress.getAllByName(domainName[0]);
4975 // Add each IP address to the string builder.
4976 for (InetAddress inetAddress : inetAddressesArray) {
4977 if (ipAddresses.length() == 0) { // This is the first IP address.
4978 // Add the IP address to the string builder.
4979 ipAddresses.append(inetAddress.getHostAddress());
4980 } else { // This is not the first IP address.
4981 // Add a line break to the string builder first.
4982 ipAddresses.append("\n");
4984 // Add the IP address to the string builder.
4985 ipAddresses.append(inetAddress.getHostAddress());
4988 } catch (UnknownHostException exception) {
4992 // Return the string.
4993 return ipAddresses.toString();
4996 // `onPostExecute()` operates on the UI thread.
4998 protected void onPostExecute(String ipAddresses) {
4999 // Get a handle for the activity.
5000 Activity activity = activityWeakReference.get();
5002 // Abort if the activity is gone.
5003 if ((activity == null) || activity.isFinishing()) {
5007 // Store the IP addresses.
5008 currentHostIpAddresses = ipAddresses;
5010 if (!urlIsLoading) {
5011 checkPinnedMismatch();
5014 // Reset the getting IP addresses tracker.
5015 gettingIpAddresses = false;