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.coordinatorlayout.widget.CoordinatorLayout;
102 import androidx.core.app.ActivityCompat;
103 import androidx.core.content.ContextCompat;
104 // `ShortcutInfoCompat`, `ShortcutManagerCompat`, and `IconCompat` can be switched to the non-compat versions once API >= 26.
105 import androidx.core.content.pm.ShortcutInfoCompat;
106 import androidx.core.content.pm.ShortcutManagerCompat;
107 import androidx.core.graphics.drawable.IconCompat;
108 import androidx.core.view.GravityCompat;
109 import androidx.drawerlayout.widget.DrawerLayout;
110 import androidx.fragment.app.DialogFragment;
111 import androidx.fragment.app.FragmentManager;
112 import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
114 import com.google.android.material.floatingactionbutton.FloatingActionButton;
115 import com.google.android.material.navigation.NavigationView;
116 import com.google.android.material.snackbar.Snackbar;
118 import com.stoutner.privacybrowser.BuildConfig;
119 import com.stoutner.privacybrowser.R;
120 import com.stoutner.privacybrowser.dialogs.AdConsentDialog;
121 import com.stoutner.privacybrowser.dialogs.CreateBookmarkDialog;
122 import com.stoutner.privacybrowser.dialogs.CreateBookmarkFolderDialog;
123 import com.stoutner.privacybrowser.dialogs.CreateHomeScreenShortcutDialog;
124 import com.stoutner.privacybrowser.dialogs.DownloadImageDialog;
125 import com.stoutner.privacybrowser.dialogs.DownloadLocationPermissionDialog;
126 import com.stoutner.privacybrowser.dialogs.EditBookmarkDialog;
127 import com.stoutner.privacybrowser.dialogs.EditBookmarkFolderDialog;
128 import com.stoutner.privacybrowser.dialogs.HttpAuthenticationDialog;
129 import com.stoutner.privacybrowser.dialogs.PinnedMismatchDialog;
130 import com.stoutner.privacybrowser.dialogs.UrlHistoryDialog;
131 import com.stoutner.privacybrowser.dialogs.ViewSslCertificateDialog;
132 import com.stoutner.privacybrowser.helpers.AdHelper;
133 import com.stoutner.privacybrowser.helpers.BlockListHelper;
134 import com.stoutner.privacybrowser.helpers.BookmarksDatabaseHelper;
135 import com.stoutner.privacybrowser.helpers.DomainsDatabaseHelper;
136 import com.stoutner.privacybrowser.helpers.OrbotProxyHelper;
137 import com.stoutner.privacybrowser.dialogs.DownloadFileDialog;
138 import com.stoutner.privacybrowser.dialogs.SslCertificateErrorDialog;
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 WebView 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 // `hideSystemBarsOnFullscreen` is used in `onCreate()` and `applyAppSettings()`.
406 private boolean hideSystemBarsOnFullscreen;
408 // `translucentNavigationBarOnFullscreen` is used in `onCreate()` and `applyAppSettings()`.
409 private boolean translucentNavigationBarOnFullscreen;
411 // `reapplyDomainSettingsOnRestart` is used in `onCreate()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onRestart()`, and `onAddDomain()`, .
412 private boolean reapplyDomainSettingsOnRestart;
414 // `reapplyAppSettingsOnRestart` is used in `onNavigationItemSelected()` and `onRestart()`.
415 private boolean reapplyAppSettingsOnRestart;
417 // `displayingFullScreenVideo` is used in `onCreate()` and `onResume()`.
418 private boolean displayingFullScreenVideo;
420 // `downloadWithExternalApp` is used in `onCreate()`, `onCreateContextMenu()`, and `applyDomainSettings()`.
421 private boolean downloadWithExternalApp;
423 // `currentDomainName` is used in `onCreate()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onAddDomain()`, and `applyDomainSettings()`.
424 private String currentDomainName;
426 // `orbotStatusBroadcastReceiver` is used in `onCreate()` and `onDestroy()`.
427 private BroadcastReceiver orbotStatusBroadcastReceiver;
429 // `waitingForOrbot` is used in `onCreate()`, `onResume()`, and `applyProxyThroughOrbot()`.
430 private boolean waitingForOrbot;
432 // `domainSettingsApplied` is used in `prepareOptionsMenu()` and `applyDomainSettings()`.
433 private boolean domainSettingsApplied;
435 // `domainSettingsJavaScriptEnabled` is used in `onOptionsItemSelected()` and `applyDomainSettings()`.
436 private Boolean domainSettingsJavaScriptEnabled;
438 // `waitingForOrbotHtmlString` is used in `onCreate()` and `applyProxyThroughOrbot()`.
439 private String waitingForOrbotHtmlString;
441 // `privateDataDirectoryString` is used in `onCreate()`, `onOptionsItemSelected()`, and `onNavigationItemSelected()`.
442 private String privateDataDirectoryString;
444 // `findOnPageLinearLayout` is used in `onCreate()`, `onOptionsItemSelected()`, and `closeFindOnPage()`.
445 private LinearLayout findOnPageLinearLayout;
447 // `findOnPageEditText` is used in `onCreate()`, `onOptionsItemSelected()`, and `closeFindOnPage()`.
448 private EditText findOnPageEditText;
450 // `displayAdditionalAppBarIcons` is used in `onCreate()` and `onCreateOptionsMenu()`.
451 private boolean displayAdditionalAppBarIcons;
453 // The action bar drawer toggle is initialized in `onCreate()` and used in `onPostCreate()`.
454 private ActionBarDrawerToggle actionBarDrawerToggle;
456 // `urlTextBox` is used in `onCreate()`, `onOptionsItemSelected()`, `loadUrlFromTextBox()`, `loadUrl()`, and `highlightUrlText()`.
457 private EditText urlTextBox;
459 // The color spans are used in `onCreate()` and `highlightUrlText()`.
460 private ForegroundColorSpan redColorSpan;
461 private ForegroundColorSpan initialGrayColorSpan;
462 private ForegroundColorSpan finalGrayColorSpan;
464 // The drawer header padding variables are used in `onCreate()` and `onConfigurationChanged()`.
465 private int drawerHeaderPaddingLeftAndRight;
466 private int drawerHeaderPaddingTop;
467 private int drawerHeaderPaddingBottom;
469 // `sslErrorHandler` is used in `onCreate()`, `onSslErrorCancel()`, and `onSslErrorProceed`.
470 private SslErrorHandler sslErrorHandler;
472 // `httpAuthHandler` is used in `onCreate()`, `onHttpAuthenticationCancel()`, and `onHttpAuthenticationProceed()`.
473 private static HttpAuthHandler httpAuthHandler;
475 // `inputMethodManager` is used in `onOptionsItemSelected()`, `loadUrlFromTextBox()`, and `closeFindOnPage()`.
476 private InputMethodManager inputMethodManager;
478 // `bookmarksDatabaseHelper` is used in `onCreate()`, `onDestroy`, `onOptionsItemSelected()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`,
479 // and `loadBookmarksFolder()`.
480 private BookmarksDatabaseHelper bookmarksDatabaseHelper;
482 // `bookmarksListView` is used in `onCreate()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, and `loadBookmarksFolder()`.
483 private ListView bookmarksListView;
485 // `bookmarksTitleTextView` is used in `onCreate()` and `loadBookmarksFolder()`.
486 private TextView bookmarksTitleTextView;
488 // `bookmarksCursor` is used in `onDestroy()`, `onOptionsItemSelected()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and `loadBookmarksFolder()`.
489 private Cursor bookmarksCursor;
491 // `bookmarksCursorAdapter` is used in `onCreateBookmark()`, `onCreateBookmarkFolder()` `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and `loadBookmarksFolder()`.
492 private CursorAdapter bookmarksCursorAdapter;
494 // `oldFolderNameString` is used in `onCreate()` and `onSaveEditBookmarkFolder()`.
495 private String oldFolderNameString;
497 // `fileChooserCallback` is used in `onCreate()` and `onActivityResult()`.
498 private ValueCallback<Uri[]> fileChooserCallback;
500 // The download strings are used in `onCreate()` and `onRequestPermissionResult()`.
501 private String downloadUrl;
502 private String downloadContentDisposition;
503 private long downloadContentLength;
505 // `downloadImageUrl` is used in `onCreateContextMenu()` and `onRequestPermissionResult()`.
506 private String downloadImageUrl;
508 // The user agent variables are used in `onCreate()` and `applyDomainSettings()`.
509 private ArrayAdapter<CharSequence> userAgentNamesArray;
510 private String[] userAgentDataArray;
512 // The request codes are used in `onCreate()`, `onCreateContextMenu()`, `onCloseDownloadLocationPermissionDialog()`, and `onRequestPermissionResult()`.
513 private final int DOWNLOAD_FILE_REQUEST_CODE = 1;
514 private final int DOWNLOAD_IMAGE_REQUEST_CODE = 2;
517 // 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.
518 // Also, remove the warning about needing to override `performClick()` when using an `OnTouchListener` with `WebView`.
519 @SuppressLint({"SetJavaScriptEnabled", "ClickableViewAccessibility"})
520 // Remove Android Studio's warning about deprecations. The deprecated `getColor()` must be used until API >= 23.
521 @SuppressWarnings("deprecation")
522 protected void onCreate(Bundle savedInstanceState) {
523 // Get a handle for the shared preferences.
524 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
526 // Get the theme and screenshot preferences.
527 darkTheme = sharedPreferences.getBoolean("dark_theme", false);
528 allowScreenshots = sharedPreferences.getBoolean("allow_screenshots", false);
530 // Disable screenshots if not allowed.
531 if (!allowScreenshots) {
532 getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE);
535 // Set the activity theme.
537 setTheme(R.style.PrivacyBrowserDark);
539 setTheme(R.style.PrivacyBrowserLight);
542 // Run the default commands.
543 super.onCreate(savedInstanceState);
545 // Set the content view.
546 setContentView(R.layout.main_drawerlayout);
548 // Get handles for views, resources, and managers.
549 Resources resources = getResources();
550 fragmentManager = getSupportFragmentManager();
551 inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
552 Toolbar toolbar = findViewById(R.id.toolbar);
554 // Set the action bar. `SupportActionBar` must be used until the minimum API is >= 21.
555 setSupportActionBar(toolbar);
556 ActionBar actionBar = getSupportActionBar();
558 // This is needed to get rid of the Android Studio warning that the action bar might be null.
559 assert actionBar != null;
561 // Add the custom `url_app_bar` layout, which shows the favorite icon and the URL text bar.
562 actionBar.setCustomView(R.layout.url_app_bar);
563 actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM);
565 // Initialize the foreground color spans for highlighting the URLs. We have to use the deprecated `getColor()` until API >= 23.
566 redColorSpan = new ForegroundColorSpan(resources.getColor(R.color.red_a700));
567 initialGrayColorSpan = new ForegroundColorSpan(resources.getColor(R.color.gray_500));
568 finalGrayColorSpan = new ForegroundColorSpan(resources.getColor(R.color.gray_500));
570 // Get a handle for `urlTextBox`.
571 urlTextBox = findViewById(R.id.url_edittext);
573 // Remove the formatting from `urlTextBar` when the user is editing the text.
574 urlTextBox.setOnFocusChangeListener((View v, boolean hasFocus) -> {
575 if (hasFocus) { // The user is editing the URL text box.
576 // Remove the highlighting.
577 urlTextBox.getText().removeSpan(redColorSpan);
578 urlTextBox.getText().removeSpan(initialGrayColorSpan);
579 urlTextBox.getText().removeSpan(finalGrayColorSpan);
580 } else { // The user has stopped editing the URL text box.
581 // Move to the beginning of the string.
582 urlTextBox.setSelection(0);
584 // Reapply the highlighting.
589 // Set the go button on the keyboard to load the URL in `urlTextBox`.
590 urlTextBox.setOnKeyListener((View v, int keyCode, KeyEvent event) -> {
591 // If the event is a key-down event on the `enter` button, load the URL.
592 if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) {
593 // Load the URL into the mainWebView and consume the event.
594 loadUrlFromTextBox();
596 // If the enter key was pressed, consume the event.
599 // If any other key was pressed, do not consume the event.
604 // Set `waitingForOrbotHTMLString`.
605 waitingForOrbotHtmlString = "<html><body><br/><center><h1>" + getString(R.string.waiting_for_orbot) + "</h1></center></body></html>";
607 // Initialize `currentDomainName`, `orbotStatus`, and `waitingForOrbot`.
608 currentDomainName = "";
609 orbotStatus = "unknown";
610 waitingForOrbot = false;
612 // Create an Orbot status `BroadcastReceiver`.
613 orbotStatusBroadcastReceiver = new BroadcastReceiver() {
615 public void onReceive(Context context, Intent intent) {
616 // Store the content of the status message in `orbotStatus`.
617 orbotStatus = intent.getStringExtra("org.torproject.android.intent.extra.STATUS");
619 // If Privacy Browser is waiting on Orbot, load the website now that Orbot is connected.
620 if (orbotStatus.equals("ON") && waitingForOrbot) {
621 // Reset `waitingForOrbot`.
622 waitingForOrbot = false;
624 // Load `formattedUrlString
625 loadUrl(formattedUrlString);
630 // Register `orbotStatusBroadcastReceiver` on `this` context.
631 this.registerReceiver(orbotStatusBroadcastReceiver, new IntentFilter("org.torproject.android.intent.action.STATUS"));
633 // Get handles for views that need to be modified.
634 DrawerLayout drawerLayout = findViewById(R.id.drawerlayout);
635 CoordinatorLayout coordinatorLayout = findViewById(R.id.coordinatorlayout);
636 bookmarksListView = findViewById(R.id.bookmarks_drawer_listview);
637 bookmarksTitleTextView = findViewById(R.id.bookmarks_title_textview);
638 FloatingActionButton launchBookmarksActivityFab = findViewById(R.id.launch_bookmarks_activity_fab);
639 FloatingActionButton createBookmarkFolderFab = findViewById(R.id.create_bookmark_folder_fab);
640 FloatingActionButton createBookmarkFab = findViewById(R.id.create_bookmark_fab);
641 SwipeRefreshLayout swipeRefreshLayout = findViewById(R.id.swiperefreshlayout);
642 mainWebView = findViewById(R.id.main_webview);
643 findOnPageLinearLayout = findViewById(R.id.find_on_page_linearlayout);
644 findOnPageEditText = findViewById(R.id.find_on_page_edittext);
645 fullScreenVideoFrameLayout = findViewById(R.id.full_screen_video_framelayout);
646 urlAppBarRelativeLayout = findViewById(R.id.url_app_bar_relativelayout);
647 favoriteIconImageView = findViewById(R.id.favorite_icon);
649 // 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.
651 launchBookmarksActivityFab.setImageDrawable(resources.getDrawable(R.drawable.bookmarks_dark));
652 createBookmarkFolderFab.setImageDrawable(resources.getDrawable(R.drawable.create_folder_dark));
653 createBookmarkFab.setImageDrawable(resources.getDrawable(R.drawable.create_bookmark_dark));
654 bookmarksListView.setBackgroundColor(resources.getColor(R.color.gray_850));
656 launchBookmarksActivityFab.setImageDrawable(resources.getDrawable(R.drawable.bookmarks_light));
657 createBookmarkFolderFab.setImageDrawable(resources.getDrawable(R.drawable.create_folder_light));
658 createBookmarkFab.setImageDrawable(resources.getDrawable(R.drawable.create_bookmark_light));
659 bookmarksListView.setBackgroundColor(resources.getColor(R.color.white));
662 // Set the launch bookmarks activity FAB to launch the bookmarks activity.
663 launchBookmarksActivityFab.setOnClickListener(v -> {
664 // Create an intent to launch the bookmarks activity.
665 Intent bookmarksIntent = new Intent(getApplicationContext(), BookmarksActivity.class);
667 // Include the current folder with the `Intent`.
668 bookmarksIntent.putExtra("Current Folder", currentBookmarksFolder);
671 startActivity(bookmarksIntent);
674 // Set the create new bookmark folder FAB to display an alert dialog.
675 createBookmarkFolderFab.setOnClickListener(v -> {
676 // Show the create bookmark folder dialog and name the instance `@string/create_folder`.
677 DialogFragment createBookmarkFolderDialog = new CreateBookmarkFolderDialog();
678 createBookmarkFolderDialog.show(fragmentManager, resources.getString(R.string.create_folder));
681 // Set the create new bookmark FAB to display an alert dialog.
682 createBookmarkFab.setOnClickListener(view -> {
683 // Show the create bookmark dialog and name the instance `@string/create_bookmark`.
684 DialogFragment createBookmarkDialog = new CreateBookmarkDialog();
685 createBookmarkDialog.show(fragmentManager, resources.getString(R.string.create_bookmark));
688 // Create a double-tap listener to toggle full-screen mode.
689 final GestureDetector gestureDetector = new GestureDetector(this, new GestureDetector.SimpleOnGestureListener() {
690 // Override `onDoubleTap()`. All other events are handled using the default settings.
692 public boolean onDoubleTap(MotionEvent event) {
693 if (fullScreenBrowsingModeEnabled) { // Only process the double-tap if full screen browsing mode is enabled.
694 // Toggle `inFullScreenBrowsingMode`.
695 inFullScreenBrowsingMode = !inFullScreenBrowsingMode;
697 if (inFullScreenBrowsingMode) { // Switch to full screen mode.
698 // Hide the action bar.
701 // Hide the banner ad in the free flavor.
702 if (BuildConfig.FLAVOR.contentEquals("free")) {
703 // The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations.
704 AdHelper.hideAd(findViewById(R.id.adview));
707 // Modify the system bars.
708 if (hideSystemBarsOnFullscreen) { // Hide everything.
709 // Remove the translucent overlays.
710 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
712 // Remove the translucent status bar overlay on the `Drawer Layout`, which is special and needs its own command.
713 drawerLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
715 /* SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
716 * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
717 * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
719 coordinatorLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
721 // Set the coordinator layout to fill the entire screen.
722 coordinatorLayout.setFitsSystemWindows(false);
723 } else { // Hide everything except the status and navigation bars.
724 // Set the coordinator layout to fill the entire screen.
725 coordinatorLayout.setFitsSystemWindows(false);
727 // There is an Android Support Library bug that causes a scrim to print on the right side of the `Drawer Layout` when the navigation bar is displayed on the right of the screen.
728 if (translucentNavigationBarOnFullscreen) {
729 // Set the navigation bar to be translucent.
730 getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
733 } else { // Switch to normal viewing mode.
734 // Show the action bar.
737 // Show the `BannerAd` in the free flavor.
738 if (BuildConfig.FLAVOR.contentEquals("free")) {
739 // Reload the ad. The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations.
740 AdHelper.loadAd(findViewById(R.id.adview), getApplicationContext(), getString(R.string.ad_unit_id));
743 // Remove the translucent navigation bar flag if it is set.
744 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
746 // Add the translucent status flag if it is unset. This also resets `drawerLayout's` `View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN`.
747 getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
749 // Remove any `SYSTEM_UI` flags from the coordinator layout.
750 coordinatorLayout.setSystemUiVisibility(0);
752 // Constrain the coordinator layout inside the status and navigation bars.
753 coordinatorLayout.setFitsSystemWindows(true);
756 // Consume the double-tap.
758 } else { // Do not consume the double-tap because full screen browsing mode is disabled.
764 // Pass all touch events on `mainWebView` through `gestureDetector` to check for double-taps.
765 mainWebView.setOnTouchListener((View view, MotionEvent event) -> {
766 // Call `performClick()` on the view, which is required for accessibility.
769 // Send the `event` to `gestureDetector`.
770 return gestureDetector.onTouchEvent(event);
773 // Update `findOnPageCountTextView`.
774 mainWebView.setFindListener(new WebView.FindListener() {
775 // Get a handle for `findOnPageCountTextView`.
776 final TextView findOnPageCountTextView = findViewById(R.id.find_on_page_count_textview);
779 public void onFindResultReceived(int activeMatchOrdinal, int numberOfMatches, boolean isDoneCounting) {
780 if ((isDoneCounting) && (numberOfMatches == 0)) { // There are no matches.
781 // Set `findOnPageCountTextView` to `0/0`.
782 findOnPageCountTextView.setText(R.string.zero_of_zero);
783 } else if (isDoneCounting) { // There are matches.
784 // `activeMatchOrdinal` is zero-based.
785 int activeMatch = activeMatchOrdinal + 1;
787 // Build the match string.
788 String matchString = activeMatch + "/" + numberOfMatches;
790 // Set `findOnPageCountTextView`.
791 findOnPageCountTextView.setText(matchString);
796 // Search for the string on the page whenever a character changes in the `findOnPageEditText`.
797 findOnPageEditText.addTextChangedListener(new TextWatcher() {
799 public void beforeTextChanged(CharSequence s, int start, int count, int after) {
804 public void onTextChanged(CharSequence s, int start, int before, int count) {
809 public void afterTextChanged(Editable s) {
810 // Search for the text in `mainWebView`.
811 mainWebView.findAllAsync(findOnPageEditText.getText().toString());
815 // Set the `check mark` button for the `findOnPageEditText` keyboard to close the soft keyboard.
816 findOnPageEditText.setOnKeyListener((v, keyCode, event) -> {
817 if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) { // The `enter` key was pressed.
818 // Hide the soft keyboard. `0` indicates no additional flags.
819 inputMethodManager.hideSoftInputFromWindow(mainWebView.getWindowToken(), 0);
821 // Consume the event.
823 } else { // A different key was pressed.
824 // Do not consume the event.
829 // Implement swipe to refresh.
830 swipeRefreshLayout.setOnRefreshListener(() -> mainWebView.reload());
832 // Set the swipe to refresh color according to the theme.
834 swipeRefreshLayout.setColorSchemeResources(R.color.blue_600);
835 swipeRefreshLayout.setProgressBackgroundColorSchemeResource(R.color.gray_850);
837 swipeRefreshLayout.setColorSchemeResources(R.color.blue_700);
840 // `DrawerTitle` identifies the `DrawerLayouts` in accessibility mode.
841 drawerLayout.setDrawerTitle(GravityCompat.START, getString(R.string.navigation_drawer));
842 drawerLayout.setDrawerTitle(GravityCompat.END, getString(R.string.bookmarks));
844 // Listen for touches on the navigation menu.
845 final NavigationView navigationView = findViewById(R.id.navigationview);
846 navigationView.setNavigationItemSelectedListener(this);
848 // 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.
849 final Menu navigationMenu = navigationView.getMenu();
850 final MenuItem navigationBackMenuItem = navigationMenu.getItem(1);
851 final MenuItem navigationForwardMenuItem = navigationMenu.getItem(2);
852 final MenuItem navigationHistoryMenuItem = navigationMenu.getItem(3);
853 final MenuItem navigationRequestsMenuItem = navigationMenu.getItem(4);
855 // Initialize the bookmarks database helper. The `0` specifies a database version, but that is ignored and set instead using a constant in `BookmarksDatabaseHelper`.
856 bookmarksDatabaseHelper = new BookmarksDatabaseHelper(this, null, null, 0);
858 // Initialize `currentBookmarksFolder`. `""` is the home folder in the database.
859 currentBookmarksFolder = "";
861 // Load the home folder, which is `""` in the database.
862 loadBookmarksFolder();
864 bookmarksListView.setOnItemClickListener((parent, view, position, id) -> {
865 // Convert the id from long to int to match the format of the bookmarks database.
866 int databaseID = (int) id;
868 // Get the bookmark cursor for this ID and move it to the first row.
869 Cursor bookmarkCursor = bookmarksDatabaseHelper.getBookmark(databaseID);
870 bookmarkCursor.moveToFirst();
872 // Act upon the bookmark according to the type.
873 if (bookmarkCursor.getInt(bookmarkCursor.getColumnIndex(BookmarksDatabaseHelper.IS_FOLDER)) == 1) { // The selected bookmark is a folder.
874 // Store the new folder name in `currentBookmarksFolder`.
875 currentBookmarksFolder = bookmarkCursor.getString(bookmarkCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME));
877 // Load the new folder.
878 loadBookmarksFolder();
879 } else { // The selected bookmark is not a folder.
880 // Load the bookmark URL.
881 loadUrl(bookmarkCursor.getString(bookmarkCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_URL)));
883 // Close the bookmarks drawer.
884 drawerLayout.closeDrawer(GravityCompat.END);
887 // Close the `Cursor`.
888 bookmarkCursor.close();
891 bookmarksListView.setOnItemLongClickListener((parent, view, position, id) -> {
892 // Convert the database ID from `long` to `int`.
893 int databaseId = (int) id;
895 // Find out if the selected bookmark is a folder.
896 boolean isFolder = bookmarksDatabaseHelper.isFolder(databaseId);
899 // Save the current folder name, which is used in `onSaveEditBookmarkFolder()`.
900 oldFolderNameString = bookmarksCursor.getString(bookmarksCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME));
902 // Show the edit bookmark folder `AlertDialog` and name the instance `@string/edit_folder`.
903 DialogFragment editBookmarkFolderDialog = EditBookmarkFolderDialog.folderDatabaseId(databaseId);
904 editBookmarkFolderDialog.show(fragmentManager, resources.getString(R.string.edit_folder));
906 // Show the edit bookmark `AlertDialog` and name the instance `@string/edit_bookmark`.
907 DialogFragment editBookmarkDialog = EditBookmarkDialog.bookmarkDatabaseId(databaseId);
908 editBookmarkDialog.show(fragmentManager, resources.getString(R.string.edit_bookmark));
911 // Consume the event.
915 // Get the status bar pixel size.
916 int statusBarResourceId = resources.getIdentifier("status_bar_height", "dimen", "android");
917 int statusBarPixelSize = resources.getDimensionPixelSize(statusBarResourceId);
919 // Get the resource density.
920 float screenDensity = resources.getDisplayMetrics().density;
922 // Calculate the drawer header padding. This is used to move the text in the drawer headers below any cutouts.
923 drawerHeaderPaddingLeftAndRight = (int) (15 * screenDensity);
924 drawerHeaderPaddingTop = statusBarPixelSize + (int) (4 * screenDensity);
925 drawerHeaderPaddingBottom = (int) (8 * screenDensity);
927 // The drawer listener is used to update the navigation menu.`
928 drawerLayout.addDrawerListener(new DrawerLayout.DrawerListener() {
930 public void onDrawerSlide(@NonNull View drawerView, float slideOffset) {
934 public void onDrawerOpened(@NonNull View drawerView) {
938 public void onDrawerClosed(@NonNull View drawerView) {
942 public void onDrawerStateChanged(int newState) {
943 if ((newState == DrawerLayout.STATE_SETTLING) || (newState == DrawerLayout.STATE_DRAGGING)) { // A drawer is opening or closing.
944 // Get handles for the drawer headers.
945 TextView navigationHeaderTextView = findViewById(R.id.navigationText);
946 TextView bookmarksHeaderTextView = findViewById(R.id.bookmarks_title_textview);
948 // 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.
949 if (navigationHeaderTextView != null) {
950 navigationHeaderTextView.setPadding(drawerHeaderPaddingLeftAndRight, drawerHeaderPaddingTop, drawerHeaderPaddingLeftAndRight, drawerHeaderPaddingBottom);
953 // 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.
954 if (bookmarksHeaderTextView != null) {
955 bookmarksHeaderTextView.setPadding(drawerHeaderPaddingLeftAndRight, drawerHeaderPaddingTop, drawerHeaderPaddingLeftAndRight, drawerHeaderPaddingBottom);
958 // Update the back, forward, history, and requests menu items.
959 navigationBackMenuItem.setEnabled(mainWebView.canGoBack());
960 navigationForwardMenuItem.setEnabled(mainWebView.canGoForward());
961 navigationHistoryMenuItem.setEnabled((mainWebView.canGoBack() || mainWebView.canGoForward()));
962 navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
964 // Hide the keyboard (if displayed).
965 inputMethodManager.hideSoftInputFromWindow(mainWebView.getWindowToken(), 0);
967 // 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.
968 urlTextBox.clearFocus();
969 mainWebView.clearFocus();
974 // Create the hamburger icon at the start of the AppBar.
975 actionBarDrawerToggle = new ActionBarDrawerToggle(this, drawerLayout, toolbar, R.string.open_navigation_drawer, R.string.close_navigation_drawer);
977 // Get a handle for the progress bar.
978 final ProgressBar progressBar = findViewById(R.id.progress_bar);
980 mainWebView.setWebChromeClient(new WebChromeClient() {
981 // Update the progress bar when a page is loading.
983 public void onProgressChanged(WebView view, int progress) {
984 // Inject the night mode CSS if night mode is enabled.
986 // `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
987 // used by WordPress. `text-decoration: none` removes all text underlines. `text-shadow: none` removes text shadows, which usually have a hard coded color.
988 // `border: none` removes all borders, which can also be used to underline text. `a {color: #1565C0}` sets links to be a dark blue.
989 // `::selection {background: #0D47A1}' sets the text selection highlight color to be a dark blue. `!important` takes precedent over any existing sub-settings.
990 mainWebView.evaluateJavascript("(function() {var parent = document.getElementsByTagName('head').item(0); var style = document.createElement('style'); style.type = 'text/css'; " +
991 "style.innerHTML = '* {background-color: #212121 !important; color: #BDBDBD !important; box-shadow: none !important; text-decoration: none !important;" +
992 "text-shadow: none !important; border: none !important;} a {color: #1565C0 !important;} ::selection {background: #0D47A1 !important;}'; parent.appendChild(style)})()", value -> {
993 // Initialize a handler to display `mainWebView`.
994 Handler displayWebViewHandler = new Handler();
996 // Setup a runnable to display `mainWebView` after a delay to allow the CSS to be applied.
997 Runnable displayWebViewRunnable = () -> {
998 // Only display `mainWebView` if the progress bar is gone. This prevents the display of the `WebView` while it is still loading.
999 if (progressBar.getVisibility() == View.GONE) {
1000 mainWebView.setVisibility(View.VISIBLE);
1004 // Displaying of `mainWebView` after 500 milliseconds.
1005 displayWebViewHandler.postDelayed(displayWebViewRunnable, 500);
1009 // Update the progress bar.
1010 progressBar.setProgress(progress);
1012 // Set the visibility of the progress bar.
1013 if (progress < 100) {
1014 // Show the progress bar.
1015 progressBar.setVisibility(View.VISIBLE);
1017 // Hide the progress bar.
1018 progressBar.setVisibility(View.GONE);
1020 // Display `mainWebView` if night mode is disabled.
1021 // 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
1022 // currently enabled.
1024 mainWebView.setVisibility(View.VISIBLE);
1027 //Stop the swipe to refresh indicator if it is running
1028 swipeRefreshLayout.setRefreshing(false);
1032 // Set the favorite icon when it changes.
1034 public void onReceivedIcon(WebView view, Bitmap icon) {
1035 // Only update the favorite icon if the website has finished loading.
1036 if (progressBar.getVisibility() == View.GONE) {
1037 // Save a copy of the favorite icon.
1038 favoriteIconBitmap = icon;
1040 // Place the favorite icon in the appBar.
1041 favoriteIconImageView.setImageBitmap(Bitmap.createScaledBitmap(icon, 64, 64, true));
1045 // Save a copy of the title when it changes.
1047 public void onReceivedTitle(WebView view, String title) {
1048 // Save a copy of the title.
1049 webViewTitle = title;
1052 // Enter full screen video.
1054 public void onShowCustomView(View view, CustomViewCallback callback) {
1055 // Set the full screen video flag.
1056 displayingFullScreenVideo = true;
1058 // Pause the ad if this is the free flavor.
1059 if (BuildConfig.FLAVOR.contentEquals("free")) {
1060 // The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations.
1061 AdHelper.pauseAd(findViewById(R.id.adview));
1064 // Remove the translucent overlays.
1065 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
1067 // Remove the translucent status bar overlay on the `Drawer Layout`, which is special and needs its own command.
1068 drawerLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
1070 /* SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
1071 * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
1072 * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
1074 coordinatorLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
1076 // Set the coordinator layout to fill the entire screen.
1077 coordinatorLayout.setFitsSystemWindows(false);
1079 // Hide the action bar.
1082 // Disable the sliding drawers.
1083 drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED);
1085 // Add `view` to `fullScreenVideoFrameLayout` and display it on the screen.
1086 fullScreenVideoFrameLayout.addView(view);
1087 fullScreenVideoFrameLayout.setVisibility(View.VISIBLE);
1090 // Exit full screen video.
1092 public void onHideCustomView() {
1093 // Unset the full screen video flag.
1094 displayingFullScreenVideo = false;
1096 // Hide `fullScreenVideoFrameLayout`.
1097 fullScreenVideoFrameLayout.removeAllViews();
1098 fullScreenVideoFrameLayout.setVisibility(View.GONE);
1100 // Enable the sliding drawers.
1101 drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED);
1103 // Apply the appropriate full screen mode the `SYSTEM_UI` flags.
1104 if (fullScreenBrowsingModeEnabled && inFullScreenBrowsingMode) { // Privacy Browser is currently in full screen browsing mode.
1105 if (hideSystemBarsOnFullscreen) { // Hide everything.
1106 // Remove the translucent navigation setting if it is currently flagged.
1107 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
1109 // Remove the translucent status bar overlay.
1110 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
1112 // Remove the translucent status bar overlay on the `Drawer Layout`, which is special and needs its own command.
1113 drawerLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
1115 /* SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
1116 * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
1117 * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
1119 coordinatorLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
1120 } else { // Hide everything except the status and navigation bars.
1121 // Remove any `SYSTEM_UI` flags from the coordinator layout.
1122 coordinatorLayout.setSystemUiVisibility(0);
1124 // Add the translucent status flag if it is unset.
1125 getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
1127 if (translucentNavigationBarOnFullscreen) {
1128 // Set the navigation bar to be translucent. This also resets `drawerLayout's` `View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN`.
1129 getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
1131 // Set the navigation bar to be black.
1132 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
1135 } else { // Switch to normal viewing mode.
1136 // Show the action bar if the find on page linear layout is not visible.
1137 if (findOnPageLinearLayout.getVisibility() == View.GONE) {
1141 // Show the `BannerAd` in the free flavor.
1142 if (BuildConfig.FLAVOR.contentEquals("free")) {
1143 // Initialize the ad. The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations.
1144 AdHelper.initializeAds(findViewById(R.id.adview), getApplicationContext(), fragmentManager, getString(R.string.google_app_id), getString(R.string.ad_unit_id));
1147 // Remove any `SYSTEM_UI` flags from the coordinator layout.
1148 coordinatorLayout.setSystemUiVisibility(0);
1150 // Remove the translucent navigation bar flag if it is set.
1151 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
1153 // Add the translucent status flag if it is unset. This also resets `drawerLayout's` `View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN`.
1154 getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
1156 // Constrain the coordinator layout inside the status and navigation bars.
1157 coordinatorLayout.setFitsSystemWindows(true);
1159 // Show the action bar.
1163 // Show the ad if this is the free flavor.
1164 if (BuildConfig.FLAVOR.contentEquals("free")) {
1165 // Reload the ad. The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations.
1166 AdHelper.loadAd(findViewById(R.id.adview), getApplicationContext(), getString(R.string.ad_unit_id));
1172 public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) {
1173 // Show the file chooser if the device is running API >= 21.
1174 if (Build.VERSION.SDK_INT >= 21) {
1175 // Store the file path callback.
1176 fileChooserCallback = filePathCallback;
1178 // Create an intent to open a chooser based ont the file chooser parameters.
1179 Intent fileChooserIntent = fileChooserParams.createIntent();
1181 // Open the file chooser. Currently only one `startActivityForResult` exists in this activity, so the request code, used to differentiate them, is simply `0`.
1182 startActivityForResult(fileChooserIntent, 0);
1188 // Register `mainWebView` for a context menu. This is used to see link targets and download images.
1189 registerForContextMenu(mainWebView);
1191 // Allow the downloading of files.
1192 mainWebView.setDownloadListener((String url, String userAgent, String contentDisposition, String mimetype, long contentLength) -> {
1193 // Check if the download should be processed by an external app.
1194 if (downloadWithExternalApp) { // Download with an external app.
1195 openUrlWithExternalApp(url);
1196 } else { // Download with Android's download manager.
1197 // Check to see if the WRITE_EXTERNAL_STORAGE permission has already been granted.
1198 if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) { // The storage permission has not been granted.
1199 // The WRITE_EXTERNAL_STORAGE permission needs to be requested.
1201 // Store the variables for future use by `onRequestPermissionsResult()`.
1203 downloadContentDisposition = contentDisposition;
1204 downloadContentLength = contentLength;
1206 // Show a dialog if the user has previously denied the permission.
1207 if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) { // Show a dialog explaining the request first.
1208 // Instantiate the download location permission alert dialog and set the download type to DOWNLOAD_FILE.
1209 DialogFragment downloadLocationPermissionDialogFragment = DownloadLocationPermissionDialog.downloadType(DownloadLocationPermissionDialog.DOWNLOAD_FILE);
1211 // Show the download location permission alert dialog. The permission will be requested when the the dialog is closed.
1212 downloadLocationPermissionDialogFragment.show(fragmentManager, getString(R.string.download_location));
1213 } else { // Show the permission request directly.
1214 // Request the permission. The download dialog will be launched by `onRequestPermissionResult()`.
1215 ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_FILE_REQUEST_CODE);
1217 } else { // The storage permission has already been granted.
1218 // Get a handle for the download file alert dialog.
1219 DialogFragment downloadFileDialogFragment = DownloadFileDialog.fromUrl(url, contentDisposition, contentLength);
1221 // Show the download file alert dialog.
1222 downloadFileDialogFragment.show(fragmentManager, getString(R.string.download));
1227 // Allow pinch to zoom.
1228 mainWebView.getSettings().setBuiltInZoomControls(true);
1230 // Hide zoom controls.
1231 mainWebView.getSettings().setDisplayZoomControls(false);
1233 // Don't allow mixed content (HTTP and HTTPS) on the same website.
1234 if (Build.VERSION.SDK_INT >= 21) {
1235 mainWebView.getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_NEVER_ALLOW);
1238 // Set the WebView to use a wide viewport. Otherwise, some web pages will be scrunched and some content will render outside the screen.
1239 mainWebView.getSettings().setUseWideViewPort(true);
1241 // Set the WebView to load in overview mode (zoomed out to the maximum width).
1242 mainWebView.getSettings().setLoadWithOverviewMode(true);
1244 // Explicitly disable geolocation.
1245 mainWebView.getSettings().setGeolocationEnabled(false);
1247 // Initialize cookieManager.
1248 cookieManager = CookieManager.getInstance();
1250 // 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).
1251 customHeaders.put("X-Requested-With", "");
1253 // Initialize the default preference values the first time the program is run. `false` keeps this command from resetting any current preferences back to default.
1254 PreferenceManager.setDefaultValues(this, R.xml.preferences, false);
1256 // Get a handle for the `Runtime`.
1257 privacyBrowserRuntime = Runtime.getRuntime();
1259 // Store the application's private data directory.
1260 privateDataDirectoryString = getApplicationInfo().dataDir;
1261 // `dataDir` will vary, but will be something like `/data/user/0/com.stoutner.privacybrowser.standard`, which links to `/data/data/com.stoutner.privacybrowser.standard`.
1263 // Initialize `inFullScreenBrowsingMode`, which is always false at this point because Privacy Browser never starts in full screen browsing mode.
1264 inFullScreenBrowsingMode = false;
1266 // Initialize the privacy settings variables.
1267 javaScriptEnabled = false;
1268 firstPartyCookiesEnabled = false;
1269 thirdPartyCookiesEnabled = false;
1270 domStorageEnabled = false;
1271 saveFormDataEnabled = false; // Form data can be removed once the minimum API >= 26.
1274 // Store the default user agent.
1275 webViewDefaultUserAgent = mainWebView.getSettings().getUserAgentString();
1277 // Initialize the WebView title.
1278 webViewTitle = getString(R.string.no_title);
1280 // Initialize the favorite icon bitmap. `ContextCompat` must be used until API >= 21.
1281 Drawable favoriteIconDrawable = ContextCompat.getDrawable(getApplicationContext(), R.drawable.world);
1282 BitmapDrawable favoriteIconBitmapDrawable = (BitmapDrawable) favoriteIconDrawable;
1283 assert favoriteIconBitmapDrawable != null;
1284 favoriteIconDefaultBitmap = favoriteIconBitmapDrawable.getBitmap();
1286 // If the favorite icon is null, load the default.
1287 if (favoriteIconBitmap == null) {
1288 favoriteIconBitmap = favoriteIconDefaultBitmap;
1291 // Initialize the user agent array adapter and string array.
1292 userAgentNamesArray = ArrayAdapter.createFromResource(this, R.array.user_agent_names, R.layout.spinner_item);
1293 userAgentDataArray = resources.getStringArray(R.array.user_agent_data);
1295 // Apply the app settings from the shared preferences.
1298 // Instantiate the block list helper.
1299 BlockListHelper blockListHelper = new BlockListHelper();
1301 // Initialize the list of resource requests.
1302 resourceRequests = new ArrayList<>();
1304 // Parse the block lists.
1305 final ArrayList<List<String[]>> easyList = blockListHelper.parseBlockList(getAssets(), "blocklists/easylist.txt");
1306 final ArrayList<List<String[]>> easyPrivacy = blockListHelper.parseBlockList(getAssets(), "blocklists/easyprivacy.txt");
1307 final ArrayList<List<String[]>> fanboysAnnoyanceList = blockListHelper.parseBlockList(getAssets(), "blocklists/fanboy-annoyance.txt");
1308 final ArrayList<List<String[]>> fanboysSocialList = blockListHelper.parseBlockList(getAssets(), "blocklists/fanboy-social.txt");
1309 final ArrayList<List<String[]>> ultraPrivacy = blockListHelper.parseBlockList(getAssets(), "blocklists/ultraprivacy.txt");
1311 // Store the list versions.
1312 easyListVersion = easyList.get(0).get(0)[0];
1313 easyPrivacyVersion = easyPrivacy.get(0).get(0)[0];
1314 fanboysAnnoyanceVersion = fanboysAnnoyanceList.get(0).get(0)[0];
1315 fanboysSocialVersion = fanboysSocialList.get(0).get(0)[0];
1316 ultraPrivacyVersion = ultraPrivacy.get(0).get(0)[0];
1318 // Get a handle for the activity. This is used to update the requests counter while the navigation menu is open.
1319 Activity activity = this;
1321 mainWebView.setWebViewClient(new WebViewClient() {
1322 // `shouldOverrideUrlLoading` makes this `WebView` the default handler for URLs inside the app, so that links are not kicked out to other apps.
1323 // The deprecated `shouldOverrideUrlLoading` must be used until API >= 24.
1324 @SuppressWarnings("deprecation")
1326 public boolean shouldOverrideUrlLoading(WebView view, String url) {
1327 if (url.startsWith("http")) { // Load the URL in Privacy Browser.
1328 // Reset the formatted URL string so the page will load correctly if blocking of third-party requests is enabled.
1329 formattedUrlString = "";
1331 // Apply the domain settings for the new URL. `applyDomainSettings` doesn't do anything if the domain has not changed.
1332 boolean userAgentChanged = applyDomainSettings(url, true, false);
1334 // Check if the user agent has changed.
1335 if (userAgentChanged) {
1336 // Manually load the URL. The changing of the user agent will cause WebView to reload the previous URL.
1337 mainWebView.loadUrl(url, customHeaders);
1339 // Returning true indicates that Privacy Browser is manually handling the loading of the URL.
1342 // Returning false causes the current WebView to handle the URL and prevents it from adding redirects to the history list.
1345 } else if (url.startsWith("mailto:")) { // Load the email address in an external email program.
1346 // Use `ACTION_SENDTO` instead of `ACTION_SEND` so that only email programs are launched.
1347 Intent emailIntent = new Intent(Intent.ACTION_SENDTO);
1349 // Parse the url and set it as the data for the intent.
1350 emailIntent.setData(Uri.parse(url));
1352 // Open the email program in a new task instead of as part of Privacy Browser.
1353 emailIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
1356 startActivity(emailIntent);
1358 // Returning true indicates Privacy Browser is handling the URL by creating an intent.
1360 } else if (url.startsWith("tel:")) { // Load the phone number in the dialer.
1361 // Open the dialer and load the phone number, but wait for the user to place the call.
1362 Intent dialIntent = new Intent(Intent.ACTION_DIAL);
1364 // Add the phone number to the intent.
1365 dialIntent.setData(Uri.parse(url));
1367 // Open the dialer in a new task instead of as part of Privacy Browser.
1368 dialIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
1371 startActivity(dialIntent);
1373 // Returning true indicates Privacy Browser is handling the URL by creating an intent.
1375 } else { // Load a system chooser to select an app that can handle the URL.
1376 // Open an app that can handle the URL.
1377 Intent genericIntent = new Intent(Intent.ACTION_VIEW);
1379 // Add the URL to the intent.
1380 genericIntent.setData(Uri.parse(url));
1382 // List all apps that can handle the URL instead of just opening the first one.
1383 genericIntent.addCategory(Intent.CATEGORY_BROWSABLE);
1385 // Open the app in a new task instead of as part of Privacy Browser.
1386 genericIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
1388 // Start the app or display a snackbar if no app is available to handle the URL.
1390 startActivity(genericIntent);
1391 } catch (ActivityNotFoundException exception) {
1392 Snackbar.make(mainWebView, getString(R.string.unrecognized_url) + " " + url, Snackbar.LENGTH_SHORT).show();
1395 // Returning true indicates Privacy Browser is handling the URL by creating an intent.
1400 // Check requests against the block lists. The deprecated `shouldInterceptRequest()` must be used until minimum API >= 21.
1401 @SuppressWarnings("deprecation")
1403 public WebResourceResponse shouldInterceptRequest(WebView view, String url){
1404 // Create an empty web resource response to be used if the resource request is blocked.
1405 WebResourceResponse emptyWebResourceResponse = new WebResourceResponse("text/plain", "utf8", new ByteArrayInputStream("".getBytes()));
1407 // Reset the whitelist results tracker.
1408 whiteListResultStringArray = null;
1410 // Initialize the third party request tracker.
1411 boolean isThirdPartyRequest = false;
1413 // Initialize the current domain string.
1414 String currentDomain = "";
1416 // Nobody is happy when comparing null strings.
1417 if (!(formattedUrlString == null) && !(url == null)) {
1418 // Get the domain strings to URIs.
1419 Uri currentDomainUri = Uri.parse(formattedUrlString);
1420 Uri requestDomainUri = Uri.parse(url);
1422 // Get the domain host names.
1423 String currentBaseDomain = currentDomainUri.getHost();
1424 String requestBaseDomain = requestDomainUri.getHost();
1426 // Update the current domain variable.
1427 currentDomain = currentBaseDomain;
1429 // Only compare the current base domain and the request base domain if neither is null.
1430 if (!(currentBaseDomain == null) && !(requestBaseDomain == null)) {
1431 // Determine the current base domain.
1432 while (currentBaseDomain.indexOf(".", currentBaseDomain.indexOf(".") + 1) > 0) { // There is at least one subdomain.
1433 // Remove the first subdomain.
1434 currentBaseDomain = currentBaseDomain.substring(currentBaseDomain.indexOf(".") + 1);
1437 // Determine the request base domain.
1438 while (requestBaseDomain.indexOf(".", requestBaseDomain.indexOf(".") + 1) > 0) { // There is at least one subdomain.
1439 // Remove the first subdomain.
1440 requestBaseDomain = requestBaseDomain.substring(requestBaseDomain.indexOf(".") + 1);
1443 // Update the third party request tracker.
1444 isThirdPartyRequest = !currentBaseDomain.equals(requestBaseDomain);
1448 // Block third-party requests if enabled.
1449 if (isThirdPartyRequest && blockAllThirdPartyRequests) {
1450 // Increment the blocked requests counters.
1452 thirdPartyBlockedRequests++;
1454 // Update the titles of the blocklist menu items. This must be run from the UI thread.
1455 activity.runOnUiThread(() -> {
1456 navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
1457 blocklistsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
1458 blockAllThirdPartyRequestsMenuItem.setTitle(thirdPartyBlockedRequests + " - " + getString(R.string.block_all_third_party_requests));
1461 // Add the request to the log.
1462 resourceRequests.add(new String[]{String.valueOf(REQUEST_THIRD_PARTY), url});
1464 // Return an empty web resource response.
1465 return emptyWebResourceResponse;
1468 // Check UltraPrivacy if it is enabled.
1469 if (ultraPrivacyEnabled) {
1470 if (blockListHelper.isBlocked(currentDomain, url, isThirdPartyRequest, ultraPrivacy)) {
1471 // Increment the blocked requests counters.
1473 ultraPrivacyBlockedRequests++;
1475 // Update the titles of the blocklist menu items. This must be run from the UI thread.
1476 activity.runOnUiThread(() -> {
1477 navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
1478 blocklistsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
1479 ultraPrivacyMenuItem.setTitle(ultraPrivacyBlockedRequests + " - " + getString(R.string.ultraprivacy));
1482 // The resource request was blocked. Return an empty web resource response.
1483 return emptyWebResourceResponse;
1486 // If the whitelist result is not null, the request has been allowed by UltraPrivacy.
1487 if (whiteListResultStringArray != null) {
1488 // Add a whitelist entry to the resource requests array.
1489 resourceRequests.add(whiteListResultStringArray);
1491 // The resource request has been allowed by UltraPrivacy. `return null` loads the requested resource.
1496 // Check EasyList if it is enabled.
1497 if (easyListEnabled) {
1498 if (blockListHelper.isBlocked(currentDomain, url, isThirdPartyRequest, easyList)) {
1499 // Increment the blocked requests counters.
1501 easyListBlockedRequests++;
1503 // Update the titles of the blocklist menu items. This must be run from the UI thread.
1504 activity.runOnUiThread(() -> {
1505 navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
1506 blocklistsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
1507 easyListMenuItem.setTitle(easyListBlockedRequests + " - " + getString(R.string.easylist));
1510 // Reset the whitelist results tracker (because otherwise it will sometimes add results to the list due to a race condition).
1511 whiteListResultStringArray = null;
1513 // The resource request was blocked. Return an empty web resource response.
1514 return emptyWebResourceResponse;
1518 // Check EasyPrivacy if it is enabled.
1519 if (easyPrivacyEnabled) {
1520 if (blockListHelper.isBlocked(currentDomain, url, isThirdPartyRequest, easyPrivacy)) {
1521 // Increment the blocked requests counters.
1523 easyPrivacyBlockedRequests++;
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 easyPrivacyMenuItem.setTitle(easyPrivacyBlockedRequests + " - " + getString(R.string.easyprivacy));
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 // Check Fanboy’s Annoyance List if it is enabled.
1541 if (fanboysAnnoyanceListEnabled) {
1542 if (blockListHelper.isBlocked(currentDomain, url, isThirdPartyRequest, fanboysAnnoyanceList)) {
1543 // Increment the blocked requests counters.
1545 fanboysAnnoyanceListBlockedRequests++;
1547 // Update the titles of the blocklist menu items. This must be run from the UI thread.
1548 activity.runOnUiThread(() -> {
1549 navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
1550 blocklistsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
1551 fanboysAnnoyanceListMenuItem.setTitle(fanboysAnnoyanceListBlockedRequests + " - " + getString(R.string.fanboys_annoyance_list));
1554 // Reset the whitelist results tracker (because otherwise it will sometimes add results to the list due to a race condition).
1555 whiteListResultStringArray = null;
1557 // The resource request was blocked. Return an empty web resource response.
1558 return emptyWebResourceResponse;
1560 } else if (fanboysSocialBlockingListEnabled){ // Only check Fanboy’s Social Blocking List if Fanboy’s Annoyance List is disabled.
1561 if (blockListHelper.isBlocked(currentDomain, url, isThirdPartyRequest, fanboysSocialList)) {
1562 // Increment the blocked requests counters.
1564 fanboysSocialBlockingListBlockedRequests++;
1566 // Update the titles of the blocklist menu items. This must be run from the UI thread.
1567 activity.runOnUiThread(() -> {
1568 navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
1569 blocklistsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
1570 fanboysSocialBlockingListMenuItem.setTitle(fanboysSocialBlockingListBlockedRequests + " - " + getString(R.string.fanboys_social_blocking_list));
1573 // Reset the whitelist results tracker (because otherwise it will sometimes add results to the list due to a race condition).
1574 whiteListResultStringArray = null;
1576 // The resource request was blocked. Return an empty web resource response.
1577 return emptyWebResourceResponse;
1581 // Add the request to the log because it hasn't been processed by any of the previous checks.
1582 if (whiteListResultStringArray != null ) { // The request was processed by a whitelist.
1583 resourceRequests.add(whiteListResultStringArray);
1584 } else { // The request didn't match any blocklist entry. Log it as a default request.
1585 resourceRequests.add(new String[]{String.valueOf(REQUEST_DEFAULT), url});
1588 // The resource request has not been blocked. `return null` loads the requested resource.
1592 // Handle HTTP authentication requests.
1594 public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, String host, String realm) {
1595 // Store `handler` so it can be accessed from `onHttpAuthenticationCancel()` and `onHttpAuthenticationProceed()`.
1596 httpAuthHandler = handler;
1598 // Display the HTTP authentication dialog.
1599 DialogFragment httpAuthenticationDialogFragment = HttpAuthenticationDialog.displayDialog(host, realm);
1600 httpAuthenticationDialogFragment.show(fragmentManager, getString(R.string.http_authentication));
1603 // Update the URL in urlTextBox when the page starts to load.
1605 public void onPageStarted(WebView view, String url, Bitmap favicon) {
1606 // 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.
1607 // This is also used to determine when to check for pinned mismatches.
1608 urlIsLoading = true;
1610 // Reset the list of host IP addresses.
1611 currentHostIpAddresses = "";
1613 // Reset the list of resource requests.
1614 resourceRequests.clear();
1616 // Initialize the counters for requests blocked by each blocklist.
1617 blockedRequests = 0;
1618 easyListBlockedRequests = 0;
1619 easyPrivacyBlockedRequests = 0;
1620 fanboysAnnoyanceListBlockedRequests = 0;
1621 fanboysSocialBlockingListBlockedRequests = 0;
1622 ultraPrivacyBlockedRequests = 0;
1623 thirdPartyBlockedRequests = 0;
1625 // If night mode is enabled, hide `mainWebView` until after the night mode CSS is applied.
1627 mainWebView.setVisibility(View.INVISIBLE);
1630 // Hide the keyboard. `0` indicates no additional flags.
1631 inputMethodManager.hideSoftInputFromWindow(mainWebView.getWindowToken(), 0);
1633 // Check to see if Privacy Browser is waiting on Orbot.
1634 if (!waitingForOrbot) { // Process the URL.
1635 // The formatted URL string must be updated at the beginning of the load, so that if the user toggles JavaScript during the load the new website is reloaded.
1636 formattedUrlString = url;
1638 // Display the formatted URL text.
1639 urlTextBox.setText(formattedUrlString);
1641 // Apply text highlighting to `urlTextBox`.
1644 // Get a URI for the current URL.
1645 Uri currentUri = Uri.parse(formattedUrlString);
1647 // Get the IP addresses for the host.
1648 new GetHostIpAddresses(activity).execute(currentUri.getHost());
1650 // Apply any custom domain settings if the URL was loaded by navigating history.
1651 if (navigatingHistory) {
1652 // Apply the domain settings.
1653 boolean userAgentChanged = applyDomainSettings(url, true, false);
1655 // Reset `navigatingHistory`.
1656 navigatingHistory = false;
1658 // Manually load the URL if the user agent has changed, which will have caused the previous URL to be reloaded.
1659 if (userAgentChanged) {
1660 loadUrl(formattedUrlString);
1664 // Replace Refresh with Stop if the menu item has been created. (The WebView typically begins loading before the menu items are instantiated.)
1665 if (refreshMenuItem != null) {
1667 refreshMenuItem.setTitle(R.string.stop);
1669 // If the icon is displayed in the AppBar, set it according to the theme.
1670 if (displayAdditionalAppBarIcons) {
1672 refreshMenuItem.setIcon(R.drawable.close_dark);
1674 refreshMenuItem.setIcon(R.drawable.close_light);
1681 // It is necessary to update `formattedUrlString` and `urlTextBox` after the page finishes loading because the final URL can change during load.
1683 public void onPageFinished(WebView view, String url) {
1684 // Reset the wide view port if it has been turned off by the waiting for Orbot message.
1685 if (!waitingForOrbot) {
1686 // Only use a wide view port if the URL starts with `http`, not for `file://` and `content://`.
1687 mainWebView.getSettings().setUseWideViewPort(url.startsWith("http"));
1690 // Flush any cookies to persistent storage. `CookieManager` has become very lazy about flushing cookies in recent versions.
1691 if (firstPartyCookiesEnabled && Build.VERSION.SDK_INT >= 21) {
1692 cookieManager.flush();
1695 // Update the Refresh menu item if it has been created.
1696 if (refreshMenuItem != null) {
1697 // Reset the Refresh title.
1698 refreshMenuItem.setTitle(R.string.refresh);
1700 // If the icon is displayed in the AppBar, reset it according to the theme.
1701 if (displayAdditionalAppBarIcons) {
1703 refreshMenuItem.setIcon(R.drawable.refresh_enabled_dark);
1705 refreshMenuItem.setIcon(R.drawable.refresh_enabled_light);
1712 // Clear the cache and history if Incognito Mode is enabled.
1713 if (incognitoModeEnabled) {
1714 // Clear the cache. `true` includes disk files.
1715 mainWebView.clearCache(true);
1717 // Clear the back/forward history.
1718 mainWebView.clearHistory();
1720 // Manually delete cache folders.
1722 // Delete the main cache directory.
1723 privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/cache");
1725 // Delete the secondary `Service Worker` cache directory.
1726 // A `String[]` must be used because the directory contains a space and `Runtime.exec` will not escape the string correctly otherwise.
1727 privacyBrowserRuntime.exec(new String[] {"rm", "-rf", privateDataDirectoryString + "/app_webview/Service Worker/"});
1728 } catch (IOException e) {
1729 // Do nothing if an error is thrown.
1733 // Update the URL text box and apply domain settings if not waiting on Orbot.
1734 if (!waitingForOrbot) {
1735 // Check to see if `WebView` has set `url` to be `about:blank`.
1736 if (url.equals("about:blank")) { // `WebView` is blank, so `formattedUrlString` should be `""` and `urlTextBox` should display a hint.
1737 // Set `formattedUrlString` to `""`.
1738 formattedUrlString = "";
1740 urlTextBox.setText(formattedUrlString);
1742 // Request focus for `urlTextBox`.
1743 urlTextBox.requestFocus();
1745 // Display the keyboard.
1746 inputMethodManager.showSoftInput(urlTextBox, 0);
1748 // Apply the domain settings. This clears any settings from the previous domain.
1749 applyDomainSettings(formattedUrlString, true, false);
1750 } else { // `WebView` has loaded a webpage.
1751 // 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.
1752 formattedUrlString = mainWebView.getUrl();
1754 // Only update the URL text box if the user is not typing in it.
1755 if (!urlTextBox.hasFocus()) {
1756 // Display the formatted URL text.
1757 urlTextBox.setText(formattedUrlString);
1759 // Apply text highlighting to `urlTextBox`.
1764 // Store the SSL certificate so it can be accessed from `ViewSslCertificateDialog` and `PinnedMismatchDialog`.
1765 sslCertificate = mainWebView.getCertificate();
1767 // Check the current website information against any pinned domain information if the current IP addresses have been loaded.
1768 if (!gettingIpAddresses) {
1769 checkPinnedMismatch();
1773 // 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.
1774 urlIsLoading = false;
1777 // Handle SSL Certificate errors.
1779 public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
1780 // Get the current website SSL certificate.
1781 SslCertificate currentWebsiteSslCertificate = error.getCertificate();
1783 // Extract the individual pieces of information from the current website SSL certificate.
1784 String currentWebsiteIssuedToCName = currentWebsiteSslCertificate.getIssuedTo().getCName();
1785 String currentWebsiteIssuedToOName = currentWebsiteSslCertificate.getIssuedTo().getOName();
1786 String currentWebsiteIssuedToUName = currentWebsiteSslCertificate.getIssuedTo().getUName();
1787 String currentWebsiteIssuedByCName = currentWebsiteSslCertificate.getIssuedBy().getCName();
1788 String currentWebsiteIssuedByOName = currentWebsiteSslCertificate.getIssuedBy().getOName();
1789 String currentWebsiteIssuedByUName = currentWebsiteSslCertificate.getIssuedBy().getUName();
1790 Date currentWebsiteSslStartDate = currentWebsiteSslCertificate.getValidNotBeforeDate();
1791 Date currentWebsiteSslEndDate = currentWebsiteSslCertificate.getValidNotAfterDate();
1793 // Proceed to the website if the current SSL website certificate matches the pinned domain certificate.
1794 if (pinnedSslCertificate &&
1795 currentWebsiteIssuedToCName.equals(pinnedSslIssuedToCName) && currentWebsiteIssuedToOName.equals(pinnedSslIssuedToOName) &&
1796 currentWebsiteIssuedToUName.equals(pinnedSslIssuedToUName) && currentWebsiteIssuedByCName.equals(pinnedSslIssuedByCName) &&
1797 currentWebsiteIssuedByOName.equals(pinnedSslIssuedByOName) && currentWebsiteIssuedByUName.equals(pinnedSslIssuedByUName) &&
1798 currentWebsiteSslStartDate.equals(pinnedSslStartDate) && currentWebsiteSslEndDate.equals(pinnedSslEndDate)) {
1800 // An SSL certificate is pinned and matches the current domain certificate. Proceed to the website without displaying an error.
1802 } else { // Either there isn't a pinned SSL certificate or it doesn't match the current website certificate.
1803 // Store `handler` so it can be accesses from `onSslErrorCancel()` and `onSslErrorProceed()`.
1804 sslErrorHandler = handler;
1806 // Display the SSL error `AlertDialog`.
1807 DialogFragment sslCertificateErrorDialogFragment = SslCertificateErrorDialog.displayDialog(error);
1808 sslCertificateErrorDialogFragment.show(fragmentManager, getString(R.string.ssl_certificate_error));
1813 // Get the intent that started the app.
1814 Intent launchingIntent = getIntent();
1816 // Get the information from the intent.
1817 String launchingIntentAction = launchingIntent.getAction();
1818 Uri launchingIntentUriData = launchingIntent.getData();
1820 // If the intent action is a web search, perform the search.
1821 if ((launchingIntentAction != null) && launchingIntentAction.equals(Intent.ACTION_WEB_SEARCH)) {
1822 // Create an encoded URL string.
1823 String encodedUrlString;
1825 // Sanitize the search input and convert it to a search.
1827 encodedUrlString = URLEncoder.encode(launchingIntent.getStringExtra(SearchManager.QUERY), "UTF-8");
1828 } catch (UnsupportedEncodingException exception) {
1829 encodedUrlString = "";
1832 // Add the base search URL.
1833 formattedUrlString = searchURL + encodedUrlString;
1834 } else if (launchingIntentUriData != null){ // Check to see if the intent contains a new URL.
1835 // Set the formatted URL string.
1836 formattedUrlString = launchingIntentUriData.toString();
1839 // Load the website if not waiting for Orbot to connect.
1840 if (!waitingForOrbot) {
1841 loadUrl(formattedUrlString);
1846 protected void onNewIntent(Intent intent) {
1847 // Sets the new intent as the activity intent, so that any future `getIntent()`s pick up this one instead of creating a new activity.
1850 // Get the information from the intent.
1851 String intentAction = intent.getAction();
1852 Uri intentUriData = intent.getData();
1854 // If the intent action is a web search, perform the search.
1855 if ((intentAction != null) && intentAction.equals(Intent.ACTION_WEB_SEARCH)) {
1856 // Create an encoded URL string.
1857 String encodedUrlString;
1859 // Sanitize the search input and convert it to a search.
1861 encodedUrlString = URLEncoder.encode(intent.getStringExtra(SearchManager.QUERY), "UTF-8");
1862 } catch (UnsupportedEncodingException exception) {
1863 encodedUrlString = "";
1866 // Add the base search URL.
1867 formattedUrlString = searchURL + encodedUrlString;
1868 } else if (intentUriData != null){ // Check to see if the intent contains a new URL.
1869 // Set the formatted URL string.
1870 formattedUrlString = intentUriData.toString();
1874 loadUrl(formattedUrlString);
1876 // Get a handle for the drawer layout.
1877 DrawerLayout drawerLayout = findViewById(R.id.drawerlayout);
1879 // Close the navigation drawer if it is open.
1880 if (drawerLayout.isDrawerVisible(GravityCompat.START)) {
1881 drawerLayout.closeDrawer(GravityCompat.START);
1884 // Close the bookmarks drawer if it is open.
1885 if (drawerLayout.isDrawerVisible(GravityCompat.END)) {
1886 drawerLayout.closeDrawer(GravityCompat.END);
1889 // Clear the keyboard if displayed and remove the focus on the urlTextBar if it has it.
1890 mainWebView.requestFocus();
1894 public void onRestart() {
1895 // Run the default commands.
1898 // Make sure Orbot is running if Privacy Browser is proxying through Orbot.
1899 if (proxyThroughOrbot) {
1900 // Request Orbot to start. If Orbot is already running no hard will be caused by this request.
1901 Intent orbotIntent = new Intent("org.torproject.android.intent.action.START");
1903 // Send the intent to the Orbot package.
1904 orbotIntent.setPackage("org.torproject.android");
1907 sendBroadcast(orbotIntent);
1910 // Apply the app settings if returning from the Settings activity..
1911 if (reapplyAppSettingsOnRestart) {
1912 // Apply the app settings.
1915 // Reload the webpage if displaying of images has been disabled in the Settings activity.
1916 if (reloadOnRestart) {
1917 // Reload `mainWebView`.
1918 mainWebView.reload();
1920 // Reset `reloadOnRestartBoolean`.
1921 reloadOnRestart = false;
1924 // Reset the return from settings flag.
1925 reapplyAppSettingsOnRestart = false;
1928 // Apply the domain settings if returning from the Domains activity.
1929 if (reapplyDomainSettingsOnRestart) {
1930 // Reapply the domain settings.
1931 applyDomainSettings(formattedUrlString, false, true);
1933 // Reset `reapplyDomainSettingsOnRestart`.
1934 reapplyDomainSettingsOnRestart = false;
1937 // Load the URL on restart to apply changes to night mode.
1938 if (loadUrlOnRestart) {
1939 // Load the current `formattedUrlString`.
1940 loadUrl(formattedUrlString);
1942 // Reset `loadUrlOnRestart.
1943 loadUrlOnRestart = false;
1946 // Update the bookmarks drawer if returning from the Bookmarks activity.
1947 if (restartFromBookmarksActivity) {
1948 // Get a handle for the drawer layout.
1949 DrawerLayout drawerLayout = findViewById(R.id.drawerlayout);
1951 // Close the bookmarks drawer.
1952 drawerLayout.closeDrawer(GravityCompat.END);
1954 // Reload the bookmarks drawer.
1955 loadBookmarksFolder();
1957 // Reset `restartFromBookmarksActivity`.
1958 restartFromBookmarksActivity = false;
1961 // Update the privacy icon. `true` runs `invalidateOptionsMenu` as the last step. This can be important if the screen was rotated.
1962 updatePrivacyIcons(true);
1965 // `onResume()` runs after `onStart()`, which runs after `onCreate()` and `onRestart()`.
1967 public void onResume() {
1968 // Run the default commands.
1971 // Resume JavaScript (if enabled).
1972 mainWebView.resumeTimers();
1974 // Resume `mainWebView`.
1975 mainWebView.onResume();
1977 // Resume the adView for the free flavor.
1978 if (BuildConfig.FLAVOR.contentEquals("free")) {
1980 AdHelper.resumeAd(findViewById(R.id.adview));
1983 // Display a message to the user if waiting for Orbot.
1984 if (waitingForOrbot && !orbotStatus.equals("ON")) {
1985 // Disable the wide view port so that the waiting for Orbot text is displayed correctly.
1986 mainWebView.getSettings().setUseWideViewPort(false);
1988 // Load a waiting page. `null` specifies no encoding, which defaults to ASCII.
1989 mainWebView.loadData(waitingForOrbotHtmlString, "text/html", null);
1992 if (displayingFullScreenVideo) {
1993 // Get handles for the layouts that need to be modified.
1994 DrawerLayout drawerLayout = findViewById(R.id.drawerlayout);
1995 CoordinatorLayout coordinatorLayout = findViewById(R.id.coordinatorlayout);
1997 // Remove the translucent overlays.
1998 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
2000 // Remove the translucent status bar overlay on the `Drawer Layout`, which is special and needs its own command.
2001 drawerLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
2003 /* SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
2004 * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
2005 * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
2007 coordinatorLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
2012 public void onPause() {
2013 // Run the default commands.
2016 // Pause `mainWebView`.
2017 mainWebView.onPause();
2019 // Stop all JavaScript.
2020 mainWebView.pauseTimers();
2022 // Pause the ad or it will continue to consume resources in the background on the free flavor.
2023 if (BuildConfig.FLAVOR.contentEquals("free")) {
2025 AdHelper.pauseAd(findViewById(R.id.adview));
2030 public void onDestroy() {
2031 // Unregister the Orbot status broadcast receiver.
2032 this.unregisterReceiver(orbotStatusBroadcastReceiver);
2034 // Close the bookmarks cursor and database.
2035 bookmarksCursor.close();
2036 bookmarksDatabaseHelper.close();
2038 // Run the default commands.
2043 public boolean onCreateOptionsMenu(Menu menu) {
2044 // Inflate the menu. This adds items to the action bar if it is present.
2045 getMenuInflater().inflate(R.menu.webview_options_menu, menu);
2047 // Set mainMenu so it can be used by `onOptionsItemSelected()` and `updatePrivacyIcons`.
2050 // Set the initial status of the privacy icons. `false` does not call `invalidateOptionsMenu` as the last step.
2051 updatePrivacyIcons(false);
2053 // Get handles for the menu items.
2054 MenuItem toggleFirstPartyCookiesMenuItem = menu.findItem(R.id.toggle_first_party_cookies);
2055 MenuItem toggleThirdPartyCookiesMenuItem = menu.findItem(R.id.toggle_third_party_cookies);
2056 MenuItem toggleDomStorageMenuItem = menu.findItem(R.id.toggle_dom_storage);
2057 MenuItem toggleSaveFormDataMenuItem = menu.findItem(R.id.toggle_save_form_data); // Form data can be removed once the minimum API >= 26.
2058 MenuItem clearFormDataMenuItem = menu.findItem(R.id.clear_form_data); // Form data can be removed once the minimum API >= 26.
2059 refreshMenuItem = menu.findItem(R.id.refresh);
2060 blocklistsMenuItem = menu.findItem(R.id.blocklists);
2061 easyListMenuItem = menu.findItem(R.id.easylist);
2062 easyPrivacyMenuItem = menu.findItem(R.id.easyprivacy);
2063 fanboysAnnoyanceListMenuItem = menu.findItem(R.id.fanboys_annoyance_list);
2064 fanboysSocialBlockingListMenuItem = menu.findItem(R.id.fanboys_social_blocking_list);
2065 ultraPrivacyMenuItem = menu.findItem(R.id.ultraprivacy);
2066 blockAllThirdPartyRequestsMenuItem = menu.findItem(R.id.block_all_third_party_requests);
2067 MenuItem adConsentMenuItem = menu.findItem(R.id.ad_consent);
2069 // Only display third-party cookies if API >= 21
2070 toggleThirdPartyCookiesMenuItem.setVisible(Build.VERSION.SDK_INT >= 21);
2072 // Only display the form data menu items if the API < 26.
2073 toggleSaveFormDataMenuItem.setVisible(Build.VERSION.SDK_INT < 26);
2074 clearFormDataMenuItem.setVisible(Build.VERSION.SDK_INT < 26);
2076 // Only show Ad Consent if this is the free flavor.
2077 adConsentMenuItem.setVisible(BuildConfig.FLAVOR.contentEquals("free"));
2079 // Get the shared preference values.
2080 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
2082 // Get the status of the additional AppBar icons.
2083 displayAdditionalAppBarIcons = sharedPreferences.getBoolean("display_additional_app_bar_icons", false);
2085 // 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.
2086 if (displayAdditionalAppBarIcons) {
2087 toggleFirstPartyCookiesMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
2088 toggleDomStorageMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
2089 refreshMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
2090 } else { //Do not display the additional icons.
2091 toggleFirstPartyCookiesMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
2092 toggleDomStorageMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
2093 refreshMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
2096 // Replace Refresh with Stop if a URL is already loading.
2099 refreshMenuItem.setTitle(R.string.stop);
2101 // If the icon is displayed in the AppBar, set it according to the theme.
2102 if (displayAdditionalAppBarIcons) {
2104 refreshMenuItem.setIcon(R.drawable.close_dark);
2106 refreshMenuItem.setIcon(R.drawable.close_light);
2115 public boolean onPrepareOptionsMenu(Menu menu) {
2116 // Get a handle for the swipe refresh layout.
2117 SwipeRefreshLayout swipeRefreshLayout = findViewById(R.id.swiperefreshlayout);
2119 // Get handles for the menu items.
2120 MenuItem addOrEditDomain = menu.findItem(R.id.add_or_edit_domain);
2121 MenuItem toggleFirstPartyCookiesMenuItem = menu.findItem(R.id.toggle_first_party_cookies);
2122 MenuItem toggleThirdPartyCookiesMenuItem = menu.findItem(R.id.toggle_third_party_cookies);
2123 MenuItem toggleDomStorageMenuItem = menu.findItem(R.id.toggle_dom_storage);
2124 MenuItem toggleSaveFormDataMenuItem = menu.findItem(R.id.toggle_save_form_data); // Form data can be removed once the minimum API >= 26.
2125 MenuItem clearDataMenuItem = menu.findItem(R.id.clear_data);
2126 MenuItem clearCookiesMenuItem = menu.findItem(R.id.clear_cookies);
2127 MenuItem clearDOMStorageMenuItem = menu.findItem(R.id.clear_dom_storage);
2128 MenuItem clearFormDataMenuItem = menu.findItem(R.id.clear_form_data); // Form data can be removed once the minimum API >= 26.
2129 MenuItem fontSizeMenuItem = menu.findItem(R.id.font_size);
2130 MenuItem swipeToRefreshMenuItem = menu.findItem(R.id.swipe_to_refresh);
2131 MenuItem displayImagesMenuItem = menu.findItem(R.id.display_images);
2132 MenuItem nightModeMenuItem = menu.findItem(R.id.night_mode);
2133 MenuItem proxyThroughOrbotMenuItem = menu.findItem(R.id.proxy_through_orbot);
2135 // Set the text for the domain menu item.
2136 if (domainSettingsApplied) {
2137 addOrEditDomain.setTitle(R.string.edit_domain_settings);
2139 addOrEditDomain.setTitle(R.string.add_domain_settings);
2142 // Set the status of the menu item checkboxes.
2143 toggleFirstPartyCookiesMenuItem.setChecked(firstPartyCookiesEnabled);
2144 toggleThirdPartyCookiesMenuItem.setChecked(thirdPartyCookiesEnabled);
2145 toggleDomStorageMenuItem.setChecked(domStorageEnabled);
2146 toggleSaveFormDataMenuItem.setChecked(saveFormDataEnabled); // Form data can be removed once the minimum API >= 26.
2147 easyListMenuItem.setChecked(easyListEnabled);
2148 easyPrivacyMenuItem.setChecked(easyPrivacyEnabled);
2149 fanboysAnnoyanceListMenuItem.setChecked(fanboysAnnoyanceListEnabled);
2150 fanboysSocialBlockingListMenuItem.setChecked(fanboysSocialBlockingListEnabled);
2151 ultraPrivacyMenuItem.setChecked(ultraPrivacyEnabled);
2152 blockAllThirdPartyRequestsMenuItem.setChecked(blockAllThirdPartyRequests);
2153 swipeToRefreshMenuItem.setChecked(swipeRefreshLayout.isEnabled());
2154 displayImagesMenuItem.setChecked(mainWebView.getSettings().getLoadsImagesAutomatically());
2155 nightModeMenuItem.setChecked(nightMode);
2156 proxyThroughOrbotMenuItem.setChecked(proxyThroughOrbot);
2158 // Enable third-party cookies if first-party cookies are enabled.
2159 toggleThirdPartyCookiesMenuItem.setEnabled(firstPartyCookiesEnabled);
2161 // Enable DOM Storage if JavaScript is enabled.
2162 toggleDomStorageMenuItem.setEnabled(javaScriptEnabled);
2164 // Enable Clear Cookies if there are any.
2165 clearCookiesMenuItem.setEnabled(cookieManager.hasCookies());
2167 // Get a count of the number of files in the Local Storage directory.
2168 File localStorageDirectory = new File (privateDataDirectoryString + "/app_webview/Local Storage/");
2169 int localStorageDirectoryNumberOfFiles = 0;
2170 if (localStorageDirectory.exists()) {
2171 localStorageDirectoryNumberOfFiles = localStorageDirectory.list().length;
2174 // Get a count of the number of files in the IndexedDB directory.
2175 File indexedDBDirectory = new File (privateDataDirectoryString + "/app_webview/IndexedDB");
2176 int indexedDBDirectoryNumberOfFiles = 0;
2177 if (indexedDBDirectory.exists()) {
2178 indexedDBDirectoryNumberOfFiles = indexedDBDirectory.list().length;
2181 // Enable Clear DOM Storage if there is any.
2182 clearDOMStorageMenuItem.setEnabled(localStorageDirectoryNumberOfFiles > 0 || indexedDBDirectoryNumberOfFiles > 0);
2184 // Enable Clear Form Data is there is any. This can be removed once the minimum API >= 26.
2185 if (Build.VERSION.SDK_INT < 26) {
2186 WebViewDatabase mainWebViewDatabase = WebViewDatabase.getInstance(this);
2187 clearFormDataMenuItem.setEnabled(mainWebViewDatabase.hasFormData());
2189 // Disable clear form data because it is not supported on current version of Android.
2190 clearFormDataMenuItem.setEnabled(false);
2193 // Enable Clear Data if any of the submenu items are enabled.
2194 clearDataMenuItem.setEnabled(clearCookiesMenuItem.isEnabled() || clearDOMStorageMenuItem.isEnabled() || clearFormDataMenuItem.isEnabled());
2196 // Disable Fanboy's Social Blocking List if Fanboy's Annoyance List is checked.
2197 fanboysSocialBlockingListMenuItem.setEnabled(!fanboysAnnoyanceListEnabled);
2199 // Initialize the display names for the blocklists with the number of blocked requests.
2200 blocklistsMenuItem.setTitle(getString(R.string.blocklists) + " - " + blockedRequests);
2201 easyListMenuItem.setTitle(easyListBlockedRequests + " - " + getString(R.string.easylist));
2202 easyPrivacyMenuItem.setTitle(easyPrivacyBlockedRequests + " - " + getString(R.string.easyprivacy));
2203 fanboysAnnoyanceListMenuItem.setTitle(fanboysAnnoyanceListBlockedRequests + " - " + getString(R.string.fanboys_annoyance_list));
2204 fanboysSocialBlockingListMenuItem.setTitle(fanboysSocialBlockingListBlockedRequests + " - " + getString(R.string.fanboys_social_blocking_list));
2205 ultraPrivacyMenuItem.setTitle(ultraPrivacyBlockedRequests + " - " + getString(R.string.ultraprivacy));
2206 blockAllThirdPartyRequestsMenuItem.setTitle(thirdPartyBlockedRequests + " - " + getString(R.string.block_all_third_party_requests));
2208 // Get the current user agent.
2209 String currentUserAgent = mainWebView.getSettings().getUserAgentString();
2211 // Select the current user agent menu item. A switch statement cannot be used because the user agents are not compile time constants.
2212 if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[0])) { // Privacy Browser.
2213 menu.findItem(R.id.user_agent_privacy_browser).setChecked(true);
2214 } else if (currentUserAgent.equals(webViewDefaultUserAgent)) { // WebView Default.
2215 menu.findItem(R.id.user_agent_webview_default).setChecked(true);
2216 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[2])) { // Firefox on Android.
2217 menu.findItem(R.id.user_agent_firefox_on_android).setChecked(true);
2218 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[3])) { // Chrome on Android.
2219 menu.findItem(R.id.user_agent_chrome_on_android).setChecked(true);
2220 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[4])) { // Safari on iOS.
2221 menu.findItem(R.id.user_agent_safari_on_ios).setChecked(true);
2222 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[5])) { // Firefox on Linux.
2223 menu.findItem(R.id.user_agent_firefox_on_linux).setChecked(true);
2224 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[6])) { // Chromium on Linux.
2225 menu.findItem(R.id.user_agent_chromium_on_linux).setChecked(true);
2226 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[7])) { // Firefox on Windows.
2227 menu.findItem(R.id.user_agent_firefox_on_windows).setChecked(true);
2228 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[8])) { // Chrome on Windows.
2229 menu.findItem(R.id.user_agent_chrome_on_windows).setChecked(true);
2230 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[9])) { // Edge on Windows.
2231 menu.findItem(R.id.user_agent_edge_on_windows).setChecked(true);
2232 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[10])) { // Internet Explorer on Windows.
2233 menu.findItem(R.id.user_agent_internet_explorer_on_windows).setChecked(true);
2234 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[11])) { // Safari on macOS.
2235 menu.findItem(R.id.user_agent_safari_on_macos).setChecked(true);
2236 } else { // Custom user agent.
2237 menu.findItem(R.id.user_agent_custom).setChecked(true);
2240 // Initialize font size variables.
2241 int fontSize = mainWebView.getSettings().getTextZoom();
2242 String fontSizeTitle;
2243 MenuItem selectedFontSizeMenuItem;
2245 // Prepare the font size title and current size menu item.
2248 fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.twenty_five_percent);
2249 selectedFontSizeMenuItem = menu.findItem(R.id.font_size_twenty_five_percent);
2253 fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.fifty_percent);
2254 selectedFontSizeMenuItem = menu.findItem(R.id.font_size_fifty_percent);
2258 fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.seventy_five_percent);
2259 selectedFontSizeMenuItem = menu.findItem(R.id.font_size_seventy_five_percent);
2263 fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.one_hundred_percent);
2264 selectedFontSizeMenuItem = menu.findItem(R.id.font_size_one_hundred_percent);
2268 fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.one_hundred_twenty_five_percent);
2269 selectedFontSizeMenuItem = menu.findItem(R.id.font_size_one_hundred_twenty_five_percent);
2273 fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.one_hundred_fifty_percent);
2274 selectedFontSizeMenuItem = menu.findItem(R.id.font_size_one_hundred_fifty_percent);
2278 fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.one_hundred_seventy_five_percent);
2279 selectedFontSizeMenuItem = menu.findItem(R.id.font_size_one_hundred_seventy_five_percent);
2283 fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.two_hundred_percent);
2284 selectedFontSizeMenuItem = menu.findItem(R.id.font_size_two_hundred_percent);
2288 fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.one_hundred_percent);
2289 selectedFontSizeMenuItem = menu.findItem(R.id.font_size_one_hundred_percent);
2293 // Set the font size title and select the current size menu item.
2294 fontSizeMenuItem.setTitle(fontSizeTitle);
2295 selectedFontSizeMenuItem.setChecked(true);
2297 // Run all the other default commands.
2298 super.onPrepareOptionsMenu(menu);
2300 // Display the menu.
2305 // Remove Android Studio's warning about the dangers of using SetJavaScriptEnabled.
2306 @SuppressLint("SetJavaScriptEnabled")
2307 // removeAllCookies is deprecated, but it is required for API < 21.
2308 @SuppressWarnings("deprecation")
2309 public boolean onOptionsItemSelected(MenuItem menuItem) {
2310 // Get the selected menu item ID.
2311 int menuItemId = menuItem.getItemId();
2313 // Run the commands that correlate to the selected menu item.
2314 switch (menuItemId) {
2315 case R.id.toggle_javascript:
2316 // Switch the status of javaScriptEnabled.
2317 javaScriptEnabled = !javaScriptEnabled;
2319 // Apply the new JavaScript status.
2320 mainWebView.getSettings().setJavaScriptEnabled(javaScriptEnabled);
2322 // Update the privacy icon. `true` runs `invalidateOptionsMenu` as the last step.
2323 updatePrivacyIcons(true);
2325 // Display a `Snackbar`.
2326 if (javaScriptEnabled) { // JavaScrip is enabled.
2327 Snackbar.make(findViewById(R.id.main_webview), R.string.javascript_enabled, Snackbar.LENGTH_SHORT).show();
2328 } else if (firstPartyCookiesEnabled) { // JavaScript is disabled, but first-party cookies are enabled.
2329 Snackbar.make(findViewById(R.id.main_webview), R.string.javascript_disabled, Snackbar.LENGTH_SHORT).show();
2330 } else { // Privacy mode.
2331 Snackbar.make(findViewById(R.id.main_webview), R.string.privacy_mode, Snackbar.LENGTH_SHORT).show();
2334 // Reload the WebView.
2335 mainWebView.reload();
2338 case R.id.add_or_edit_domain:
2339 if (domainSettingsApplied) { // Edit the current domain settings.
2340 // Reapply the domain settings on returning to `MainWebViewActivity`.
2341 reapplyDomainSettingsOnRestart = true;
2342 currentDomainName = "";
2344 // Create an intent to launch the domains activity.
2345 Intent domainsIntent = new Intent(this, DomainsActivity.class);
2347 // Put extra information instructing the domains activity to directly load the current domain and close on back instead of returning to the domains list.
2348 domainsIntent.putExtra("loadDomain", domainSettingsDatabaseId);
2349 domainsIntent.putExtra("closeOnBack", true);
2352 startActivity(domainsIntent);
2353 } else { // Add a new domain.
2354 // Apply the new domain settings on returning to `MainWebViewActivity`.
2355 reapplyDomainSettingsOnRestart = true;
2356 currentDomainName = "";
2358 // Get the current domain
2359 Uri currentUri = Uri.parse(formattedUrlString);
2360 String currentDomain = currentUri.getHost();
2362 // Initialize the database handler. The `0` specifies the database version, but that is ignored and set instead using a constant in `DomainsDatabaseHelper`.
2363 DomainsDatabaseHelper domainsDatabaseHelper = new DomainsDatabaseHelper(this, null, null, 0);
2365 // Create the domain and store the database ID.
2366 int newDomainDatabaseId = domainsDatabaseHelper.addDomain(currentDomain);
2368 // Create an intent to launch the domains activity.
2369 Intent domainsIntent = new Intent(this, DomainsActivity.class);
2371 // Put extra information instructing the domains activity to directly load the new domain and close on back instead of returning to the domains list.
2372 domainsIntent.putExtra("loadDomain", newDomainDatabaseId);
2373 domainsIntent.putExtra("closeOnBack", true);
2376 startActivity(domainsIntent);
2380 case R.id.toggle_first_party_cookies:
2381 // Switch the status of firstPartyCookiesEnabled.
2382 firstPartyCookiesEnabled = !firstPartyCookiesEnabled;
2384 // Update the menu checkbox.
2385 menuItem.setChecked(firstPartyCookiesEnabled);
2387 // Apply the new cookie status.
2388 cookieManager.setAcceptCookie(firstPartyCookiesEnabled);
2390 // Update the privacy icon. `true` runs `invalidateOptionsMenu` as the last step.
2391 updatePrivacyIcons(true);
2393 // Display a `Snackbar`.
2394 if (firstPartyCookiesEnabled) { // First-party cookies are enabled.
2395 Snackbar.make(findViewById(R.id.main_webview), R.string.first_party_cookies_enabled, Snackbar.LENGTH_SHORT).show();
2396 } else if (javaScriptEnabled) { // JavaScript is still enabled.
2397 Snackbar.make(findViewById(R.id.main_webview), R.string.first_party_cookies_disabled, Snackbar.LENGTH_SHORT).show();
2398 } else { // Privacy mode.
2399 Snackbar.make(findViewById(R.id.main_webview), R.string.privacy_mode, Snackbar.LENGTH_SHORT).show();
2402 // Reload the WebView.
2403 mainWebView.reload();
2406 case R.id.toggle_third_party_cookies:
2407 if (Build.VERSION.SDK_INT >= 21) {
2408 // Switch the status of thirdPartyCookiesEnabled.
2409 thirdPartyCookiesEnabled = !thirdPartyCookiesEnabled;
2411 // Update the menu checkbox.
2412 menuItem.setChecked(thirdPartyCookiesEnabled);
2414 // Apply the new cookie status.
2415 cookieManager.setAcceptThirdPartyCookies(mainWebView, thirdPartyCookiesEnabled);
2417 // Display a `Snackbar`.
2418 if (thirdPartyCookiesEnabled) {
2419 Snackbar.make(findViewById(R.id.main_webview), R.string.third_party_cookies_enabled, Snackbar.LENGTH_SHORT).show();
2421 Snackbar.make(findViewById(R.id.main_webview), R.string.third_party_cookies_disabled, Snackbar.LENGTH_SHORT).show();
2424 // Reload the WebView.
2425 mainWebView.reload();
2426 } // Else do nothing because SDK < 21.
2429 case R.id.toggle_dom_storage:
2430 // Switch the status of domStorageEnabled.
2431 domStorageEnabled = !domStorageEnabled;
2433 // Update the menu checkbox.
2434 menuItem.setChecked(domStorageEnabled);
2436 // Apply the new DOM Storage status.
2437 mainWebView.getSettings().setDomStorageEnabled(domStorageEnabled);
2439 // Update the privacy icon. `true` runs `invalidateOptionsMenu` as the last step.
2440 updatePrivacyIcons(true);
2442 // Display a `Snackbar`.
2443 if (domStorageEnabled) {
2444 Snackbar.make(findViewById(R.id.main_webview), R.string.dom_storage_enabled, Snackbar.LENGTH_SHORT).show();
2446 Snackbar.make(findViewById(R.id.main_webview), R.string.dom_storage_disabled, Snackbar.LENGTH_SHORT).show();
2449 // Reload the WebView.
2450 mainWebView.reload();
2453 // Form data can be removed once the minimum API >= 26.
2454 case R.id.toggle_save_form_data:
2455 // Switch the status of saveFormDataEnabled.
2456 saveFormDataEnabled = !saveFormDataEnabled;
2458 // Update the menu checkbox.
2459 menuItem.setChecked(saveFormDataEnabled);
2461 // Apply the new form data status.
2462 mainWebView.getSettings().setSaveFormData(saveFormDataEnabled);
2464 // Display a `Snackbar`.
2465 if (saveFormDataEnabled) {
2466 Snackbar.make(findViewById(R.id.main_webview), R.string.form_data_enabled, Snackbar.LENGTH_SHORT).show();
2468 Snackbar.make(findViewById(R.id.main_webview), R.string.form_data_disabled, Snackbar.LENGTH_SHORT).show();
2471 // Update the privacy icon. `true` runs `invalidateOptionsMenu` as the last step.
2472 updatePrivacyIcons(true);
2474 // Reload the WebView.
2475 mainWebView.reload();
2478 case R.id.clear_cookies:
2479 Snackbar.make(findViewById(R.id.main_webview), R.string.cookies_deleted, Snackbar.LENGTH_LONG)
2480 .setAction(R.string.undo, v -> {
2481 // Do nothing because everything will be handled by `onDismissed()` below.
2483 .addCallback(new Snackbar.Callback() {
2484 @SuppressLint("SwitchIntDef") // Ignore the lint warning about not handling the other possible events as they are covered by `default:`.
2486 public void onDismissed(Snackbar snackbar, int event) {
2488 // The user pushed the undo button.
2489 case Snackbar.Callback.DISMISS_EVENT_ACTION:
2493 // The snackbar was dismissed without the undo button being pushed.
2495 // `cookieManager.removeAllCookie()` varies by SDK.
2496 if (Build.VERSION.SDK_INT < 21) {
2497 cookieManager.removeAllCookie();
2499 cookieManager.removeAllCookies(null);
2507 case R.id.clear_dom_storage:
2508 Snackbar.make(findViewById(R.id.main_webview), R.string.dom_storage_deleted, Snackbar.LENGTH_LONG)
2509 .setAction(R.string.undo, v -> {
2510 // Do nothing because everything will be handled by `onDismissed()` below.
2512 .addCallback(new Snackbar.Callback() {
2513 @SuppressLint("SwitchIntDef") // Ignore the lint warning about not handling the other possible events as they are covered by `default:`.
2515 public void onDismissed(Snackbar snackbar, int event) {
2517 // The user pushed the undo button.
2518 case Snackbar.Callback.DISMISS_EVENT_ACTION:
2522 // The snackbar was dismissed without the undo button being pushed.
2524 // Delete the DOM Storage.
2525 WebStorage webStorage = WebStorage.getInstance();
2526 webStorage.deleteAllData();
2528 // Initialize a handler to manually delete the DOM storage files and directories.
2529 Handler deleteDomStorageHandler = new Handler();
2531 // Setup a runnable to manually delete the DOM storage files and directories.
2532 Runnable deleteDomStorageRunnable = () -> {
2534 // A string array must be used because the directory contains a space and `Runtime.exec` will otherwise not escape the string correctly.
2535 Process deleteLocalStorageProcess = privacyBrowserRuntime.exec(new String[]{"rm", "-rf", privateDataDirectoryString + "/app_webview/Local Storage/"});
2537 // Multiple commands must be used because `Runtime.exec()` does not like `*`.
2538 Process deleteIndexProcess = privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/IndexedDB");
2539 Process deleteQuotaManagerProcess = privacyBrowserRuntime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager");
2540 Process deleteQuotaManagerJournalProcess = privacyBrowserRuntime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager-journal");
2541 Process deleteDatabasesProcess = privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/databases");
2543 // Wait for the processes to finish.
2544 deleteLocalStorageProcess.waitFor();
2545 deleteIndexProcess.waitFor();
2546 deleteQuotaManagerProcess.waitFor();
2547 deleteQuotaManagerJournalProcess.waitFor();
2548 deleteDatabasesProcess.waitFor();
2549 } catch (Exception exception) {
2550 // Do nothing if an error is thrown.
2554 // Manually delete the DOM storage files after 200 milliseconds.
2555 deleteDomStorageHandler.postDelayed(deleteDomStorageRunnable, 200);
2562 // Form data can be remove once the minimum API >= 26.
2563 case R.id.clear_form_data:
2564 Snackbar.make(findViewById(R.id.main_webview), R.string.form_data_deleted, Snackbar.LENGTH_LONG)
2565 .setAction(R.string.undo, v -> {
2566 // Do nothing because everything will be handled by `onDismissed()` below.
2568 .addCallback(new Snackbar.Callback() {
2569 @SuppressLint("SwitchIntDef") // Ignore the lint warning about not handling the other possible events as they are covered by `default:`.
2571 public void onDismissed(Snackbar snackbar, int event) {
2573 // The user pushed the undo button.
2574 case Snackbar.Callback.DISMISS_EVENT_ACTION:
2578 // The snackbar was dismissed without the `Undo` button being pushed.
2580 // Delete the form data.
2581 WebViewDatabase mainWebViewDatabase = WebViewDatabase.getInstance(getApplicationContext());
2582 mainWebViewDatabase.clearFormData();
2590 // Toggle the EasyList status.
2591 easyListEnabled = !easyListEnabled;
2593 // Update the menu checkbox.
2594 menuItem.setChecked(easyListEnabled);
2596 // Reload the main WebView.
2597 mainWebView.reload();
2600 case R.id.easyprivacy:
2601 // Toggle the EasyPrivacy status.
2602 easyPrivacyEnabled = !easyPrivacyEnabled;
2604 // Update the menu checkbox.
2605 menuItem.setChecked(easyPrivacyEnabled);
2607 // Reload the main WebView.
2608 mainWebView.reload();
2611 case R.id.fanboys_annoyance_list:
2612 // Toggle Fanboy's Annoyance List status.
2613 fanboysAnnoyanceListEnabled = !fanboysAnnoyanceListEnabled;
2615 // Update the menu checkbox.
2616 menuItem.setChecked(fanboysAnnoyanceListEnabled);
2618 // Update the staus of Fanboy's Social Blocking List.
2619 MenuItem fanboysSocialBlockingListMenuItem = mainMenu.findItem(R.id.fanboys_social_blocking_list);
2620 fanboysSocialBlockingListMenuItem.setEnabled(!fanboysAnnoyanceListEnabled);
2622 // Reload the main WebView.
2623 mainWebView.reload();
2626 case R.id.fanboys_social_blocking_list:
2627 // Toggle Fanboy's Social Blocking List status.
2628 fanboysSocialBlockingListEnabled = !fanboysSocialBlockingListEnabled;
2630 // Update the menu checkbox.
2631 menuItem.setChecked(fanboysSocialBlockingListEnabled);
2633 // Reload the main WebView.
2634 mainWebView.reload();
2637 case R.id.ultraprivacy:
2638 // Toggle the UltraPrivacy status.
2639 ultraPrivacyEnabled = !ultraPrivacyEnabled;
2641 // Update the menu checkbox.
2642 menuItem.setChecked(ultraPrivacyEnabled);
2644 // Reload the main WebView.
2645 mainWebView.reload();
2648 case R.id.block_all_third_party_requests:
2649 //Toggle the third-party requests blocker status.
2650 blockAllThirdPartyRequests = !blockAllThirdPartyRequests;
2652 // Update the menu checkbox.
2653 menuItem.setChecked(blockAllThirdPartyRequests);
2655 // Reload the main WebView.
2656 mainWebView.reload();
2659 case R.id.user_agent_privacy_browser:
2660 // Update the user agent.
2661 mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[0]);
2663 // Reload the WebView.
2664 mainWebView.reload();
2667 case R.id.user_agent_webview_default:
2668 // Update the user agent.
2669 mainWebView.getSettings().setUserAgentString("");
2671 // Reload the WebView.
2672 mainWebView.reload();
2675 case R.id.user_agent_firefox_on_android:
2676 // Update the user agent.
2677 mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[2]);
2679 // Reload the WebView.
2680 mainWebView.reload();
2683 case R.id.user_agent_chrome_on_android:
2684 // Update the user agent.
2685 mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[3]);
2687 // Reload the WebView.
2688 mainWebView.reload();
2691 case R.id.user_agent_safari_on_ios:
2692 // Update the user agent.
2693 mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[4]);
2695 // Reload the WebView.
2696 mainWebView.reload();
2699 case R.id.user_agent_firefox_on_linux:
2700 // Update the user agent.
2701 mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[5]);
2703 // Reload the WebView.
2704 mainWebView.reload();
2707 case R.id.user_agent_chromium_on_linux:
2708 // Update the user agent.
2709 mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[6]);
2711 // Reload the WebView.
2712 mainWebView.reload();
2715 case R.id.user_agent_firefox_on_windows:
2716 // Update the user agent.
2717 mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[7]);
2719 // Reload the WebView.
2720 mainWebView.reload();
2723 case R.id.user_agent_chrome_on_windows:
2724 // Update the user agent.
2725 mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[8]);
2727 // Reload the WebView.
2728 mainWebView.reload();
2731 case R.id.user_agent_edge_on_windows:
2732 // Update the user agent.
2733 mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[9]);
2735 // Reload the WebView.
2736 mainWebView.reload();
2739 case R.id.user_agent_internet_explorer_on_windows:
2740 // Update the user agent.
2741 mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[10]);
2743 // Reload the WebView.
2744 mainWebView.reload();
2747 case R.id.user_agent_safari_on_macos:
2748 // Update the user agent.
2749 mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[11]);
2751 // Reload the WebView.
2752 mainWebView.reload();
2755 case R.id.user_agent_custom:
2756 // Update the user agent.
2757 mainWebView.getSettings().setUserAgentString(defaultCustomUserAgentString);
2759 // Reload the WebView.
2760 mainWebView.reload();
2763 case R.id.font_size_twenty_five_percent:
2764 mainWebView.getSettings().setTextZoom(25);
2767 case R.id.font_size_fifty_percent:
2768 mainWebView.getSettings().setTextZoom(50);
2771 case R.id.font_size_seventy_five_percent:
2772 mainWebView.getSettings().setTextZoom(75);
2775 case R.id.font_size_one_hundred_percent:
2776 mainWebView.getSettings().setTextZoom(100);
2779 case R.id.font_size_one_hundred_twenty_five_percent:
2780 mainWebView.getSettings().setTextZoom(125);
2783 case R.id.font_size_one_hundred_fifty_percent:
2784 mainWebView.getSettings().setTextZoom(150);
2787 case R.id.font_size_one_hundred_seventy_five_percent:
2788 mainWebView.getSettings().setTextZoom(175);
2791 case R.id.font_size_two_hundred_percent:
2792 mainWebView.getSettings().setTextZoom(200);
2795 case R.id.swipe_to_refresh:
2796 // Get a handle for the swipe refresh layout.
2797 SwipeRefreshLayout swipeRefreshLayout = findViewById(R.id.swiperefreshlayout);
2799 // Toggle swipe to refresh.
2800 swipeRefreshLayout.setEnabled(!swipeRefreshLayout.isEnabled());
2803 case R.id.display_images:
2804 if (mainWebView.getSettings().getLoadsImagesAutomatically()) { // Images are currently loaded automatically.
2805 mainWebView.getSettings().setLoadsImagesAutomatically(false);
2806 mainWebView.reload();
2807 } else { // Images are not currently loaded automatically.
2808 mainWebView.getSettings().setLoadsImagesAutomatically(true);
2812 case R.id.night_mode:
2813 // Toggle night mode.
2814 nightMode = !nightMode;
2816 // Enable or disable JavaScript according to night mode, the global preference, and any domain settings.
2817 if (nightMode) { // Night mode is enabled. Enable JavaScript.
2818 // Update the global variable.
2819 javaScriptEnabled = true;
2820 } else if (domainSettingsApplied) { // Night mode is disabled and domain settings are applied. Set JavaScript according to the domain settings.
2821 // Get the JavaScript preference that was stored the last time domain settings were loaded.
2822 javaScriptEnabled = domainSettingsJavaScriptEnabled;
2823 } else { // Night mode is disabled and domain settings are not applied. Set JavaScript according to the global preference.
2824 // Get a handle for the shared preference.
2825 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
2827 // Get the JavaScript preference.
2828 javaScriptEnabled = sharedPreferences.getBoolean("javascript", false);
2831 // Apply the JavaScript setting to the WebView.
2832 mainWebView.getSettings().setJavaScriptEnabled(javaScriptEnabled);
2834 // Update the privacy icons.
2835 updatePrivacyIcons(false);
2837 // Reload the website.
2838 mainWebView.reload();
2841 case R.id.find_on_page:
2842 // Get a handle for the toolbar.
2843 Toolbar toolbar = findViewById(R.id.toolbar);
2845 // Hide the URL app bar.
2846 toolbar.setVisibility(View.GONE);
2848 // Show the Find on Page `RelativeLayout`.
2849 findOnPageLinearLayout.setVisibility(View.VISIBLE);
2851 // Display the keyboard. We have to wait 200 ms before running the command to work around a bug in Android.
2852 // http://stackoverflow.com/questions/5520085/android-show-softkeyboard-with-showsoftinput-is-not-working
2853 findOnPageEditText.postDelayed(() -> {
2854 // Set the focus on `findOnPageEditText`.
2855 findOnPageEditText.requestFocus();
2857 // Display the keyboard. `0` sets no input flags.
2858 inputMethodManager.showSoftInput(findOnPageEditText, 0);
2862 case R.id.view_source:
2863 // Launch the View Source activity.
2864 Intent viewSourceIntent = new Intent(this, ViewSourceActivity.class);
2865 startActivity(viewSourceIntent);
2868 case R.id.share_url:
2869 // Setup the share string.
2870 String shareString = webViewTitle + " – " + urlTextBox.getText().toString();
2872 // Create the share intent.
2873 Intent shareIntent = new Intent(Intent.ACTION_SEND);
2874 shareIntent.putExtra(Intent.EXTRA_TEXT, shareString);
2875 shareIntent.setType("text/plain");
2878 startActivity(Intent.createChooser(shareIntent, getString(R.string.share_url)));
2882 // Get a `PrintManager` instance.
2883 PrintManager printManager = (PrintManager) getSystemService(Context.PRINT_SERVICE);
2885 // Convert `mainWebView` to `printDocumentAdapter`.
2886 PrintDocumentAdapter printDocumentAdapter = mainWebView.createPrintDocumentAdapter();
2888 // Remove the lint error below that `printManager` might be `null`.
2889 assert printManager != null;
2891 // Print the document. The print attributes are `null`.
2892 printManager.print(getString(R.string.privacy_browser_web_page), printDocumentAdapter, null);
2895 case R.id.open_with_app:
2896 openWithApp(formattedUrlString);
2899 case R.id.open_with_browser:
2900 openWithBrowser(formattedUrlString);
2903 case R.id.add_to_homescreen:
2904 // Show the alert dialog.
2905 DialogFragment createHomeScreenShortcutDialogFragment = new CreateHomeScreenShortcutDialog();
2906 createHomeScreenShortcutDialogFragment.show(getSupportFragmentManager(), getString(R.string.create_shortcut));
2908 //Everything else will be handled by the alert dialog and the associated listener below.
2911 case R.id.proxy_through_orbot:
2912 // Toggle the proxy through Orbot variable.
2913 proxyThroughOrbot = !proxyThroughOrbot;
2915 // Apply the proxy through Orbot settings.
2916 applyProxyThroughOrbot(true);
2920 if (menuItem.getTitle().equals(getString(R.string.refresh))) { // The refresh button was pushed.
2921 // Reload the WebView.
2922 mainWebView.reload();
2923 } else { // The stop button was pushed.
2924 // Stop the loading of the WebView.
2925 mainWebView.stopLoading();
2929 case R.id.ad_consent:
2930 // Display the ad consent dialog.
2931 DialogFragment adConsentDialogFragment = new AdConsentDialog();
2932 adConsentDialogFragment.show(getSupportFragmentManager(), getString(R.string.ad_consent));
2936 // Don't consume the event.
2937 return super.onOptionsItemSelected(menuItem);
2941 // removeAllCookies is deprecated, but it is required for API < 21.
2942 @SuppressWarnings("deprecation")
2944 public boolean onNavigationItemSelected(@NonNull MenuItem menuItem) {
2945 int menuItemId = menuItem.getItemId();
2947 switch (menuItemId) {
2953 if (mainWebView.canGoBack()) {
2954 // Reset the formatted URL string so the page will load correctly if blocking of third-party requests is enabled.
2955 formattedUrlString = "";
2957 // Set `navigatingHistory` so that the domain settings are applied when the new URL is loaded.
2958 navigatingHistory = true;
2960 // Load the previous website in the history.
2961 mainWebView.goBack();
2966 if (mainWebView.canGoForward()) {
2967 // Reset the formatted URL string so the page will load correctly if blocking of third-party requests is enabled.
2968 formattedUrlString = "";
2970 // Set `navigatingHistory` so that the domain settings are applied when the new URL is loaded.
2971 navigatingHistory = true;
2973 // Load the next website in the history.
2974 mainWebView.goForward();
2979 // Get the `WebBackForwardList`.
2980 WebBackForwardList webBackForwardList = mainWebView.copyBackForwardList();
2982 // Show the URL history dialog and name this instance `R.string.history`.
2983 DialogFragment urlHistoryDialogFragment = UrlHistoryDialog.loadBackForwardList(this, webBackForwardList);
2984 urlHistoryDialogFragment.show(getSupportFragmentManager(), getString(R.string.history));
2988 // Launch the requests activity.
2989 Intent requestsIntent = new Intent(this, RequestsActivity.class);
2990 startActivity(requestsIntent);
2993 case R.id.downloads:
2994 // Launch the system Download Manager.
2995 Intent downloadManagerIntent = new Intent(DownloadManager.ACTION_VIEW_DOWNLOADS);
2997 // Launch as a new task so that Download Manager and Privacy Browser show as separate windows in the recent tasks list.
2998 downloadManagerIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
3000 startActivity(downloadManagerIntent);
3004 // Set the flag to reapply the domain settings on restart when returning from Domain Settings.
3005 reapplyDomainSettingsOnRestart = true;
3006 currentDomainName = "";
3008 // Launch the domains activity.
3009 Intent domainsIntent = new Intent(this, DomainsActivity.class);
3010 startActivity(domainsIntent);
3014 // Set the flag to reapply app settings on restart when returning from Settings.
3015 reapplyAppSettingsOnRestart = true;
3017 // Set the flag to reapply the domain settings on restart when returning from Settings.
3018 reapplyDomainSettingsOnRestart = true;
3019 currentDomainName = "";
3021 // Launch the settings activity.
3022 Intent settingsIntent = new Intent(this, SettingsActivity.class);
3023 startActivity(settingsIntent);
3026 case R.id.import_export:
3027 // Launch the import/export activity.
3028 Intent importExportIntent = new Intent (this, ImportExportActivity.class);
3029 startActivity(importExportIntent);
3033 // Launch the logcat activity.
3034 Intent logcatIntent = new Intent(this, LogcatActivity.class);
3035 startActivity(logcatIntent);
3039 // Launch `GuideActivity`.
3040 Intent guideIntent = new Intent(this, GuideActivity.class);
3041 startActivity(guideIntent);
3045 // Launch `AboutActivity`.
3046 Intent aboutIntent = new Intent(this, AboutActivity.class);
3047 startActivity(aboutIntent);
3050 case R.id.clear_and_exit:
3051 // Close the bookmarks cursor and database.
3052 bookmarksCursor.close();
3053 bookmarksDatabaseHelper.close();
3055 // Get a handle for the shared preferences.
3056 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
3058 // Get the status of the clear everything preference.
3059 boolean clearEverything = sharedPreferences.getBoolean("clear_everything", true);
3062 if (clearEverything || sharedPreferences.getBoolean("clear_cookies", true)) {
3063 // The command to remove cookies changed slightly in API 21.
3064 if (Build.VERSION.SDK_INT >= 21) {
3065 cookieManager.removeAllCookies(null);
3067 cookieManager.removeAllCookie();
3070 // Manually delete the cookies database, as `CookieManager` sometimes will not flush its changes to disk before `System.exit(0)` is run.
3072 // Two commands must be used because `Runtime.exec()` does not like `*`.
3073 Process deleteCookiesProcess = privacyBrowserRuntime.exec("rm -f " + privateDataDirectoryString + "/app_webview/Cookies");
3074 Process deleteCookiesJournalProcess = privacyBrowserRuntime.exec("rm -f " + privateDataDirectoryString + "/app_webview/Cookies-journal");
3076 // Wait until the processes have finished.
3077 deleteCookiesProcess.waitFor();
3078 deleteCookiesJournalProcess.waitFor();
3079 } catch (Exception exception) {
3080 // Do nothing if an error is thrown.
3084 // Clear DOM storage.
3085 if (clearEverything || sharedPreferences.getBoolean("clear_dom_storage", true)) {
3086 // Ask `WebStorage` to clear the DOM storage.
3087 WebStorage webStorage = WebStorage.getInstance();
3088 webStorage.deleteAllData();
3090 // Manually delete the DOM storage files and directories, as `WebStorage` sometimes will not flush its changes to disk before `System.exit(0)` is run.
3092 // A `String[]` must be used because the directory contains a space and `Runtime.exec` will otherwise not escape the string correctly.
3093 Process deleteLocalStorageProcess = privacyBrowserRuntime.exec(new String[] {"rm", "-rf", privateDataDirectoryString + "/app_webview/Local Storage/"});
3095 // Multiple commands must be used because `Runtime.exec()` does not like `*`.
3096 Process deleteIndexProcess = privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/IndexedDB");
3097 Process deleteQuotaManagerProcess = privacyBrowserRuntime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager");
3098 Process deleteQuotaManagerJournalProcess = privacyBrowserRuntime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager-journal");
3099 Process deleteDatabaseProcess = privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/databases");
3101 // Wait until the processes have finished.
3102 deleteLocalStorageProcess.waitFor();
3103 deleteIndexProcess.waitFor();
3104 deleteQuotaManagerProcess.waitFor();
3105 deleteQuotaManagerJournalProcess.waitFor();
3106 deleteDatabaseProcess.waitFor();
3107 } catch (Exception exception) {
3108 // Do nothing if an error is thrown.
3112 // Clear form data if the API < 26.
3113 if ((Build.VERSION.SDK_INT < 26) && (clearEverything || sharedPreferences.getBoolean("clear_form_data", true))) {
3114 WebViewDatabase webViewDatabase = WebViewDatabase.getInstance(this);
3115 webViewDatabase.clearFormData();
3117 // Manually delete the form data database, as `WebViewDatabase` sometimes will not flush its changes to disk before `System.exit(0)` is run.
3119 // A string array must be used because the database contains a space and `Runtime.exec` will not otherwise escape the string correctly.
3120 Process deleteWebDataProcess = privacyBrowserRuntime.exec(new String[] {"rm", "-f", privateDataDirectoryString + "/app_webview/Web Data"});
3121 Process deleteWebDataJournalProcess = privacyBrowserRuntime.exec(new String[] {"rm", "-f", privateDataDirectoryString + "/app_webview/Web Data-journal"});
3123 // Wait until the processes have finished.
3124 deleteWebDataProcess.waitFor();
3125 deleteWebDataJournalProcess.waitFor();
3126 } catch (Exception exception) {
3127 // Do nothing if an error is thrown.
3132 if (clearEverything || sharedPreferences.getBoolean("clear_cache", true)) {
3133 // `true` includes disk files.
3134 mainWebView.clearCache(true);
3136 // Manually delete the cache directories.
3138 // Delete the main cache directory.
3139 Process deleteCacheProcess = privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/cache");
3141 // Delete the secondary `Service Worker` cache directory.
3142 // A string array must be used because the directory contains a space and `Runtime.exec` will otherwise not escape the string correctly.
3143 Process deleteServiceWorkerProcess = privacyBrowserRuntime.exec(new String[] {"rm", "-rf", privateDataDirectoryString + "/app_webview/Service Worker/"});
3145 // Wait until the processes have finished.
3146 deleteCacheProcess.waitFor();
3147 deleteServiceWorkerProcess.waitFor();
3148 } catch (Exception exception) {
3149 // Do nothing if an error is thrown.
3153 // Clear SSL certificate preferences.
3154 mainWebView.clearSslPreferences();
3156 // Clear the back/forward history.
3157 mainWebView.clearHistory();
3159 // Clear `formattedUrlString`.
3160 formattedUrlString = null;
3162 // Clear `customHeaders`.
3163 customHeaders.clear();
3165 // Destroy the internal state of `mainWebView`.
3166 mainWebView.destroy();
3168 // Manually delete the `app_webview` folder, which contains the cookies, DOM storage, form data, and `Service Worker` cache.
3169 // See `https://code.google.com/p/android/issues/detail?id=233826&thanks=233826&ts=1486670530`.
3170 if (clearEverything) {
3172 // Delete the folder.
3173 Process deleteAppWebviewProcess = privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/app_webview");
3175 // Wait until the process has finished.
3176 deleteAppWebviewProcess.waitFor();
3177 } catch (Exception exception) {
3178 // Do nothing if an error is thrown.
3182 // Close Privacy Browser. `finishAndRemoveTask` also removes Privacy Browser from the recent app list.
3183 if (Build.VERSION.SDK_INT >= 21) {
3184 finishAndRemoveTask();
3189 // Remove the terminated program from RAM. The status code is `0`.
3194 // Get a handle for the drawer layout.
3195 DrawerLayout drawerLayout = findViewById(R.id.drawerlayout);
3197 // Close the navigation drawer.
3198 drawerLayout.closeDrawer(GravityCompat.START);
3203 public void onPostCreate(Bundle savedInstanceState) {
3204 // Run the default commands.
3205 super.onPostCreate(savedInstanceState);
3207 // Sync the state of the DrawerToggle after onRestoreInstanceState has finished.
3208 actionBarDrawerToggle.syncState();
3212 public void onConfigurationChanged(Configuration newConfig) {
3213 super.onConfigurationChanged(newConfig);
3215 // Get the status bar pixel size.
3216 int statusBarResourceId = getResources().getIdentifier("status_bar_height", "dimen", "android");
3217 int statusBarPixelSize = getResources().getDimensionPixelSize(statusBarResourceId);
3219 // Get the resource density.
3220 float screenDensity = getResources().getDisplayMetrics().density;
3222 // Recalculate the drawer header padding.
3223 drawerHeaderPaddingLeftAndRight = (int) (15 * screenDensity);
3224 drawerHeaderPaddingTop = statusBarPixelSize + (int) (4 * screenDensity);
3225 drawerHeaderPaddingBottom = (int) (8 * screenDensity);
3227 // Reload the ad for the free flavor if not in full screen mode.
3228 if (BuildConfig.FLAVOR.contentEquals("free") && !inFullScreenBrowsingMode) {
3229 // Reload the ad. The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations.
3230 AdHelper.loadAd(findViewById(R.id.adview), getApplicationContext(), getString(R.string.ad_unit_id));
3233 // `invalidateOptionsMenu` should recalculate the number of action buttons from the menu to display on the app bar, but it doesn't because of the this bug:
3234 // https://code.google.com/p/android/issues/detail?id=20493#c8
3235 // ActivityCompat.invalidateOptionsMenu(this);
3239 public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.ContextMenuInfo menuInfo) {
3240 // Store the `HitTestResult`.
3241 final WebView.HitTestResult hitTestResult = mainWebView.getHitTestResult();
3244 final String imageUrl;
3245 final String linkUrl;
3247 // Get a handle for the the clipboard and fragment managers.
3248 final ClipboardManager clipboardManager = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
3249 FragmentManager fragmentManager = getSupportFragmentManager();
3251 // Remove the lint errors below that `clipboardManager` might be `null`.
3252 assert clipboardManager != null;
3254 switch (hitTestResult.getType()) {
3255 // `SRC_ANCHOR_TYPE` is a link.
3256 case WebView.HitTestResult.SRC_ANCHOR_TYPE:
3257 // Get the target URL.
3258 linkUrl = hitTestResult.getExtra();
3260 // Set the target URL as the title of the `ContextMenu`.
3261 menu.setHeaderTitle(linkUrl);
3263 // Add a Load URL entry.
3264 menu.add(R.string.load_url).setOnMenuItemClickListener((MenuItem item) -> {
3269 // Add a Copy URL entry.
3270 menu.add(R.string.copy_url).setOnMenuItemClickListener((MenuItem item) -> {
3271 // Save the link URL in a `ClipData`.
3272 ClipData srcAnchorTypeClipData = ClipData.newPlainText(getString(R.string.url), linkUrl);
3274 // Set the `ClipData` as the clipboard's primary clip.
3275 clipboardManager.setPrimaryClip(srcAnchorTypeClipData);
3279 // Add a Download URL entry.
3280 menu.add(R.string.download_url).setOnMenuItemClickListener((MenuItem item) -> {
3281 // Check if the download should be processed by an external app.
3282 if (downloadWithExternalApp) { // Download with an external app.
3283 openUrlWithExternalApp(linkUrl);
3284 } else { // Download with Android's download manager.
3285 // Check to see if the storage permission has already been granted.
3286 if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) { // The storage permission needs to be requested.
3287 // Store the variables for future use by `onRequestPermissionsResult()`.
3288 downloadUrl = linkUrl;
3289 downloadContentDisposition = "none";
3290 downloadContentLength = -1;
3292 // Show a dialog if the user has previously denied the permission.
3293 if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) { // Show a dialog explaining the request first.
3294 // Instantiate the download location permission alert dialog and set the download type to DOWNLOAD_FILE.
3295 DialogFragment downloadLocationPermissionDialogFragment = DownloadLocationPermissionDialog.downloadType(DownloadLocationPermissionDialog.DOWNLOAD_FILE);
3297 // Show the download location permission alert dialog. The permission will be requested when the the dialog is closed.
3298 downloadLocationPermissionDialogFragment.show(fragmentManager, getString(R.string.download_location));
3299 } else { // Show the permission request directly.
3300 // Request the permission. The download dialog will be launched by `onRequestPermissionResult()`.
3301 ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_FILE_REQUEST_CODE);
3303 } else { // The storage permission has already been granted.
3304 // Get a handle for the download file alert dialog.
3305 DialogFragment downloadFileDialogFragment = DownloadFileDialog.fromUrl(linkUrl, "none", -1);
3307 // Show the download file alert dialog.
3308 downloadFileDialogFragment.show(fragmentManager, getString(R.string.download));
3314 // Add an Open with App entry.
3315 menu.add(R.string.open_with_app).setOnMenuItemClickListener((MenuItem item) -> {
3316 openWithApp(linkUrl);
3320 // Add an Open with Browser entry.
3321 menu.add(R.string.open_with_browser).setOnMenuItemClickListener((MenuItem item) -> {
3322 openWithBrowser(linkUrl);
3326 // Add a Cancel entry, which by default closes the context menu.
3327 menu.add(R.string.cancel);
3330 case WebView.HitTestResult.EMAIL_TYPE:
3331 // Get the target URL.
3332 linkUrl = hitTestResult.getExtra();
3334 // Set the target URL as the title of the `ContextMenu`.
3335 menu.setHeaderTitle(linkUrl);
3337 // Add a Write Email entry.
3338 menu.add(R.string.write_email).setOnMenuItemClickListener(item -> {
3339 // Use `ACTION_SENDTO` instead of `ACTION_SEND` so that only email programs are launched.
3340 Intent emailIntent = new Intent(Intent.ACTION_SENDTO);
3342 // Parse the url and set it as the data for the `Intent`.
3343 emailIntent.setData(Uri.parse("mailto:" + linkUrl));
3345 // `FLAG_ACTIVITY_NEW_TASK` opens the email program in a new task instead as part of Privacy Browser.
3346 emailIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
3349 startActivity(emailIntent);
3353 // Add a Copy Email Address entry.
3354 menu.add(R.string.copy_email_address).setOnMenuItemClickListener(item -> {
3355 // Save the email address in a `ClipData`.
3356 ClipData srcEmailTypeClipData = ClipData.newPlainText(getString(R.string.email_address), linkUrl);
3358 // Set the `ClipData` as the clipboard's primary clip.
3359 clipboardManager.setPrimaryClip(srcEmailTypeClipData);
3363 // Add a `Cancel` entry, which by default closes the `ContextMenu`.
3364 menu.add(R.string.cancel);
3367 // `IMAGE_TYPE` is an image.
3368 case WebView.HitTestResult.IMAGE_TYPE:
3369 // Get the image URL.
3370 imageUrl = hitTestResult.getExtra();
3372 // Set the image URL as the title of the `ContextMenu`.
3373 menu.setHeaderTitle(imageUrl);
3375 // Add a View Image entry.
3376 menu.add(R.string.view_image).setOnMenuItemClickListener(item -> {
3381 // Add a Download Image entry.
3382 menu.add(R.string.download_image).setOnMenuItemClickListener((MenuItem item) -> {
3383 // Check if the download should be processed by an external app.
3384 if (downloadWithExternalApp) { // Download with an external app.
3385 openUrlWithExternalApp(imageUrl);
3386 } else { // Download with Android's download manager.
3387 // Check to see if the storage permission has already been granted.
3388 if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) { // The storage permission needs to be requested.
3389 // Store the image URL for use by `onRequestPermissionResult()`.
3390 downloadImageUrl = imageUrl;
3392 // Show a dialog if the user has previously denied the permission.
3393 if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) { // Show a dialog explaining the request first.
3394 // Instantiate the download location permission alert dialog and set the download type to DOWNLOAD_IMAGE.
3395 DialogFragment downloadLocationPermissionDialogFragment = DownloadLocationPermissionDialog.downloadType(DownloadLocationPermissionDialog.DOWNLOAD_IMAGE);
3397 // Show the download location permission alert dialog. The permission will be requested when the dialog is closed.
3398 downloadLocationPermissionDialogFragment.show(fragmentManager, getString(R.string.download_location));
3399 } else { // Show the permission request directly.
3400 // Request the permission. The download dialog will be launched by `onRequestPermissionResult().
3401 ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_IMAGE_REQUEST_CODE);
3403 } else { // The storage permission has already been granted.
3404 // Get a handle for the download image alert dialog.
3405 DialogFragment downloadImageDialogFragment = DownloadImageDialog.imageUrl(imageUrl);
3407 // Show the download image alert dialog.
3408 downloadImageDialogFragment.show(fragmentManager, getString(R.string.download));
3414 // Add a Copy URL entry.
3415 menu.add(R.string.copy_url).setOnMenuItemClickListener(item -> {
3416 // Save the image URL in a `ClipData`.
3417 ClipData srcImageTypeClipData = ClipData.newPlainText(getString(R.string.url), imageUrl);
3419 // Set the `ClipData` as the clipboard's primary clip.
3420 clipboardManager.setPrimaryClip(srcImageTypeClipData);
3424 // Add an Open with App entry.
3425 menu.add(R.string.open_with_app).setOnMenuItemClickListener((MenuItem item) -> {
3426 openWithApp(imageUrl);
3430 // Add an Open with Browser entry.
3431 menu.add(R.string.open_with_browser).setOnMenuItemClickListener((MenuItem item) -> {
3432 openWithBrowser(imageUrl);
3436 // Add a `Cancel` entry, which by default closes the `ContextMenu`.
3437 menu.add(R.string.cancel);
3441 // `SRC_IMAGE_ANCHOR_TYPE` is an image that is also a link.
3442 case WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE:
3443 // Get the image URL.
3444 imageUrl = hitTestResult.getExtra();
3446 // Set the image URL as the title of the `ContextMenu`.
3447 menu.setHeaderTitle(imageUrl);
3449 // Add a `View Image` entry.
3450 menu.add(R.string.view_image).setOnMenuItemClickListener(item -> {
3455 // Add a `Download Image` entry.
3456 menu.add(R.string.download_image).setOnMenuItemClickListener((MenuItem item) -> {
3457 // Check if the download should be processed by an external app.
3458 if (downloadWithExternalApp) { // Download with an external app.
3459 openUrlWithExternalApp(imageUrl);
3460 } else { // Download with Android's download manager.
3461 // Check to see if the storage permission has already been granted.
3462 if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) { // The storage permission needs to be requested.
3463 // Store the image URL for use by `onRequestPermissionResult()`.
3464 downloadImageUrl = imageUrl;
3466 // Show a dialog if the user has previously denied the permission.
3467 if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) { // Show a dialog explaining the request first.
3468 // Instantiate the download location permission alert dialog and set the download type to DOWNLOAD_IMAGE.
3469 DialogFragment downloadLocationPermissionDialogFragment = DownloadLocationPermissionDialog.downloadType(DownloadLocationPermissionDialog.DOWNLOAD_IMAGE);
3471 // Show the download location permission alert dialog. The permission will be requested when the dialog is closed.
3472 downloadLocationPermissionDialogFragment.show(fragmentManager, getString(R.string.download_location));
3473 } else { // Show the permission request directly.
3474 // Request the permission. The download dialog will be launched by `onRequestPermissionResult().
3475 ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_IMAGE_REQUEST_CODE);
3477 } else { // The storage permission has already been granted.
3478 // Get a handle for the download image alert dialog.
3479 DialogFragment downloadImageDialogFragment = DownloadImageDialog.imageUrl(imageUrl);
3481 // Show the download image alert dialog.
3482 downloadImageDialogFragment.show(fragmentManager, getString(R.string.download));
3488 // Add a `Copy URL` entry.
3489 menu.add(R.string.copy_url).setOnMenuItemClickListener(item -> {
3490 // Save the image URL in a `ClipData`.
3491 ClipData srcImageAnchorTypeClipData = ClipData.newPlainText(getString(R.string.url), imageUrl);
3493 // Set the `ClipData` as the clipboard's primary clip.
3494 clipboardManager.setPrimaryClip(srcImageAnchorTypeClipData);
3498 // Add an Open with App entry.
3499 menu.add(R.string.open_with_app).setOnMenuItemClickListener((MenuItem item) -> {
3500 openWithApp(imageUrl);
3504 // Add an Open with Browser entry.
3505 menu.add(R.string.open_with_browser).setOnMenuItemClickListener((MenuItem item) -> {
3506 openWithBrowser(imageUrl);
3510 // Add a `Cancel` entry, which by default closes the `ContextMenu`.
3511 menu.add(R.string.cancel);
3517 public void onCreateBookmark(DialogFragment dialogFragment) {
3518 // Get the `EditTexts` from the `dialogFragment`.
3519 EditText createBookmarkNameEditText = dialogFragment.getDialog().findViewById(R.id.create_bookmark_name_edittext);
3520 EditText createBookmarkUrlEditText = dialogFragment.getDialog().findViewById(R.id.create_bookmark_url_edittext);
3522 // Extract the strings from the `EditTexts`.
3523 String bookmarkNameString = createBookmarkNameEditText.getText().toString();
3524 String bookmarkUrlString = createBookmarkUrlEditText.getText().toString();
3526 // Convert the favoriteIcon Bitmap to a byte array. `0` is for lossless compression (the only option for a PNG).
3527 ByteArrayOutputStream favoriteIconByteArrayOutputStream = new ByteArrayOutputStream();
3528 favoriteIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, favoriteIconByteArrayOutputStream);
3529 byte[] favoriteIconByteArray = favoriteIconByteArrayOutputStream.toByteArray();
3531 // Display the new bookmark below the current items in the (0 indexed) list.
3532 int newBookmarkDisplayOrder = bookmarksListView.getCount();
3534 // Create the bookmark.
3535 bookmarksDatabaseHelper.createBookmark(bookmarkNameString, bookmarkUrlString, currentBookmarksFolder, newBookmarkDisplayOrder, favoriteIconByteArray);
3537 // Update the bookmarks cursor with the current contents of this folder.
3538 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder);
3540 // Update the `ListView`.
3541 bookmarksCursorAdapter.changeCursor(bookmarksCursor);
3543 // Scroll to the new bookmark.
3544 bookmarksListView.setSelection(newBookmarkDisplayOrder);
3548 public void onCreateBookmarkFolder(DialogFragment dialogFragment) {
3549 // Get handles for the views in `dialogFragment`.
3550 EditText createFolderNameEditText = dialogFragment.getDialog().findViewById(R.id.create_folder_name_edittext);
3551 RadioButton defaultFolderIconRadioButton = dialogFragment.getDialog().findViewById(R.id.create_folder_default_icon_radiobutton);
3552 ImageView folderIconImageView = dialogFragment.getDialog().findViewById(R.id.create_folder_default_icon);
3554 // Get new folder name string.
3555 String folderNameString = createFolderNameEditText.getText().toString();
3557 // Get the new folder icon `Bitmap`.
3558 Bitmap folderIconBitmap;
3559 if (defaultFolderIconRadioButton.isChecked()) { // Use the default folder icon.
3560 // Get the default folder icon and convert it to a `Bitmap`.
3561 Drawable folderIconDrawable = folderIconImageView.getDrawable();
3562 BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable;
3563 folderIconBitmap = folderIconBitmapDrawable.getBitmap();
3564 } else { // Use the `WebView` favorite icon.
3565 folderIconBitmap = favoriteIconBitmap;
3568 // Convert `folderIconBitmap` to a byte array. `0` is for lossless compression (the only option for a PNG).
3569 ByteArrayOutputStream folderIconByteArrayOutputStream = new ByteArrayOutputStream();
3570 folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, folderIconByteArrayOutputStream);
3571 byte[] folderIconByteArray = folderIconByteArrayOutputStream.toByteArray();
3573 // Move all the bookmarks down one in the display order.
3574 for (int i = 0; i < bookmarksListView.getCount(); i++) {
3575 int databaseId = (int) bookmarksListView.getItemIdAtPosition(i);
3576 bookmarksDatabaseHelper.updateDisplayOrder(databaseId, i + 1);
3579 // Create the folder, which will be placed at the top of the `ListView`.
3580 bookmarksDatabaseHelper.createFolder(folderNameString, currentBookmarksFolder, folderIconByteArray);
3582 // Update the bookmarks cursor with the current contents of this folder.
3583 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder);
3585 // Update the `ListView`.
3586 bookmarksCursorAdapter.changeCursor(bookmarksCursor);
3588 // Scroll to the new folder.
3589 bookmarksListView.setSelection(0);
3593 public void onCreateHomeScreenShortcut(DialogFragment dialogFragment) {
3594 // Get the shortcut name.
3595 EditText shortcutNameEditText = dialogFragment.getDialog().findViewById(R.id.shortcut_name_edittext);
3596 String shortcutNameString = shortcutNameEditText.getText().toString();
3598 // Convert the favorite icon bitmap to an `Icon`. `IconCompat` is required until API >= 26.
3599 IconCompat favoriteIcon = IconCompat.createWithBitmap(favoriteIconBitmap);
3601 // Setup the shortcut intent.
3602 Intent shortcutIntent = new Intent(Intent.ACTION_VIEW);
3603 shortcutIntent.setData(Uri.parse(formattedUrlString));
3605 // Create a shortcut info builder. The shortcut name becomes the shortcut ID.
3606 ShortcutInfoCompat.Builder shortcutInfoBuilder = new ShortcutInfoCompat.Builder(this, shortcutNameString);
3608 // Add the required fields to the shortcut info builder.
3609 shortcutInfoBuilder.setIcon(favoriteIcon);
3610 shortcutInfoBuilder.setIntent(shortcutIntent);
3611 shortcutInfoBuilder.setShortLabel(shortcutNameString);
3613 // Request the pin. `ShortcutManagerCompat` can be switched to `ShortcutManager` once API >= 26.
3614 ShortcutManagerCompat.requestPinShortcut(this, shortcutInfoBuilder.build(), null);
3618 public void onCloseDownloadLocationPermissionDialog(int downloadType) {
3619 switch (downloadType) {
3620 case DownloadLocationPermissionDialog.DOWNLOAD_FILE:
3621 // Request the WRITE_EXTERNAL_STORAGE permission with a file request code.
3622 ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_FILE_REQUEST_CODE);
3625 case DownloadLocationPermissionDialog.DOWNLOAD_IMAGE:
3626 // Request the WRITE_EXTERNAL_STORAGE permission with an image request code.
3627 ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_IMAGE_REQUEST_CODE);
3633 public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
3634 // Get a handle for the fragment manager.
3635 FragmentManager fragmentManager = getSupportFragmentManager();
3637 switch (requestCode) {
3638 case DOWNLOAD_FILE_REQUEST_CODE:
3639 // Show the download file alert dialog. When the dialog closes, the correct command will be used based on the permission status.
3640 DialogFragment downloadFileDialogFragment = DownloadFileDialog.fromUrl(downloadUrl, downloadContentDisposition, downloadContentLength);
3642 // On API 23, displaying the fragment must be delayed or the app will crash.
3643 if (Build.VERSION.SDK_INT == 23) {
3644 new Handler().postDelayed(() -> downloadFileDialogFragment.show(fragmentManager, getString(R.string.download)), 500);
3646 downloadFileDialogFragment.show(fragmentManager, getString(R.string.download));
3649 // Reset the download variables.
3651 downloadContentDisposition = "";
3652 downloadContentLength = 0;
3655 case DOWNLOAD_IMAGE_REQUEST_CODE:
3656 // Show the download image alert dialog. When the dialog closes, the correct command will be used based on the permission status.
3657 DialogFragment downloadImageDialogFragment = DownloadImageDialog.imageUrl(downloadImageUrl);
3659 // On API 23, displaying the fragment must be delayed or the app will crash.
3660 if (Build.VERSION.SDK_INT == 23) {
3661 new Handler().postDelayed(() -> downloadImageDialogFragment.show(fragmentManager, getString(R.string.download)), 500);
3663 downloadImageDialogFragment.show(fragmentManager, getString(R.string.download));
3666 // Reset the image URL variable.
3667 downloadImageUrl = "";
3673 public void onDownloadImage(DialogFragment dialogFragment, String imageUrl) {
3674 // Download the image if it has an HTTP or HTTPS URI.
3675 if (imageUrl.startsWith("http")) {
3676 // Get a handle for the system `DOWNLOAD_SERVICE`.
3677 DownloadManager downloadManager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
3679 // Parse `imageUrl`.
3680 DownloadManager.Request downloadRequest = new DownloadManager.Request(Uri.parse(imageUrl));
3682 // Pass cookies to download manager if cookies are enabled. This is required to download images from websites that require a login.
3683 // Code contributed 2017 Hendrik Knackstedt. Copyright assigned to Soren Stoutner <soren@stoutner.com>.
3684 if (firstPartyCookiesEnabled) {
3685 // Get the cookies for `imageUrl`.
3686 String cookies = cookieManager.getCookie(imageUrl);
3688 // Add the cookies to `downloadRequest`. In the HTTP request header, cookies are named `Cookie`.
3689 downloadRequest.addRequestHeader("Cookie", cookies);
3692 // Get the file name from the dialog fragment.
3693 EditText downloadImageNameEditText = dialogFragment.getDialog().findViewById(R.id.download_image_name);
3694 String imageName = downloadImageNameEditText.getText().toString();
3696 // Specify the download location.
3697 if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { // External write permission granted.
3698 // Download to the public download directory.
3699 downloadRequest.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, imageName);
3700 } else { // External write permission denied.
3701 // Download to the app's external download directory.
3702 downloadRequest.setDestinationInExternalFilesDir(this, Environment.DIRECTORY_DOWNLOADS, imageName);
3705 // Allow `MediaScanner` to index the download if it is a media file.
3706 downloadRequest.allowScanningByMediaScanner();
3708 // Add the URL as the description for the download.
3709 downloadRequest.setDescription(imageUrl);
3711 // Show the download notification after the download is completed.
3712 downloadRequest.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
3714 // Remove the lint warning below that `downloadManager` might be `null`.
3715 assert downloadManager != null;
3717 // Initiate the download.
3718 downloadManager.enqueue(downloadRequest);
3719 } else { // The image is not an HTTP or HTTPS URI.
3720 Snackbar.make(mainWebView, R.string.cannot_download_image, Snackbar.LENGTH_INDEFINITE).show();
3725 public void onDownloadFile(DialogFragment dialogFragment, String downloadUrl) {
3726 // Download the file if it has an HTTP or HTTPS URI.
3727 if (downloadUrl.startsWith("http")) {
3728 // Get a handle for the system `DOWNLOAD_SERVICE`.
3729 DownloadManager downloadManager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
3731 // Parse `downloadUrl`.
3732 DownloadManager.Request downloadRequest = new DownloadManager.Request(Uri.parse(downloadUrl));
3734 // Pass cookies to download manager if cookies are enabled. This is required to download files from websites that require a login.
3735 // Code contributed 2017 Hendrik Knackstedt. Copyright assigned to Soren Stoutner <soren@stoutner.com>.
3736 if (firstPartyCookiesEnabled) {
3737 // Get the cookies for `downloadUrl`.
3738 String cookies = cookieManager.getCookie(downloadUrl);
3740 // Add the cookies to `downloadRequest`. In the HTTP request header, cookies are named `Cookie`.
3741 downloadRequest.addRequestHeader("Cookie", cookies);
3744 // Get the file name from the dialog fragment.
3745 EditText downloadFileNameEditText = dialogFragment.getDialog().findViewById(R.id.download_file_name);
3746 String fileName = downloadFileNameEditText.getText().toString();
3748 // Specify the download location.
3749 if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { // External write permission granted.
3750 // Download to the public download directory.
3751 downloadRequest.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, fileName);
3752 } else { // External write permission denied.
3753 // Download to the app's external download directory.
3754 downloadRequest.setDestinationInExternalFilesDir(this, Environment.DIRECTORY_DOWNLOADS, fileName);
3757 // Allow `MediaScanner` to index the download if it is a media file.
3758 downloadRequest.allowScanningByMediaScanner();
3760 // Add the URL as the description for the download.
3761 downloadRequest.setDescription(downloadUrl);
3763 // Show the download notification after the download is completed.
3764 downloadRequest.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
3766 // Remove the lint warning below that `downloadManager` might be `null`.
3767 assert downloadManager != null;
3769 // Initiate the download.
3770 downloadManager.enqueue(downloadRequest);
3771 } else { // The download is not an HTTP or HTTPS URI.
3772 Snackbar.make(mainWebView, R.string.cannot_download_file, Snackbar.LENGTH_INDEFINITE).show();
3777 public void onSaveBookmark(DialogFragment dialogFragment, int selectedBookmarkDatabaseId) {
3778 // Get handles for the views from `dialogFragment`.
3779 EditText editBookmarkNameEditText = dialogFragment.getDialog().findViewById(R.id.edit_bookmark_name_edittext);
3780 EditText editBookmarkUrlEditText = dialogFragment.getDialog().findViewById(R.id.edit_bookmark_url_edittext);
3781 RadioButton currentBookmarkIconRadioButton = dialogFragment.getDialog().findViewById(R.id.edit_bookmark_current_icon_radiobutton);
3783 // Store the bookmark strings.
3784 String bookmarkNameString = editBookmarkNameEditText.getText().toString();
3785 String bookmarkUrlString = editBookmarkUrlEditText.getText().toString();
3787 // Update the bookmark.
3788 if (currentBookmarkIconRadioButton.isChecked()) { // Update the bookmark without changing the favorite icon.
3789 bookmarksDatabaseHelper.updateBookmark(selectedBookmarkDatabaseId, bookmarkNameString, bookmarkUrlString);
3790 } else { // Update the bookmark using the `WebView` favorite icon.
3791 // Convert the favorite icon to a byte array. `0` is for lossless compression (the only option for a PNG).
3792 ByteArrayOutputStream newFavoriteIconByteArrayOutputStream = new ByteArrayOutputStream();
3793 favoriteIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, newFavoriteIconByteArrayOutputStream);
3794 byte[] newFavoriteIconByteArray = newFavoriteIconByteArrayOutputStream.toByteArray();
3796 // Update the bookmark and the favorite icon.
3797 bookmarksDatabaseHelper.updateBookmark(selectedBookmarkDatabaseId, bookmarkNameString, bookmarkUrlString, newFavoriteIconByteArray);
3800 // Update the bookmarks cursor with the current contents of this folder.
3801 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder);
3803 // Update the `ListView`.
3804 bookmarksCursorAdapter.changeCursor(bookmarksCursor);
3808 public void onSaveBookmarkFolder(DialogFragment dialogFragment, int selectedFolderDatabaseId) {
3809 // Get handles for the views from `dialogFragment`.
3810 EditText editFolderNameEditText = dialogFragment.getDialog().findViewById(R.id.edit_folder_name_edittext);
3811 RadioButton currentFolderIconRadioButton = dialogFragment.getDialog().findViewById(R.id.edit_folder_current_icon_radiobutton);
3812 RadioButton defaultFolderIconRadioButton = dialogFragment.getDialog().findViewById(R.id.edit_folder_default_icon_radiobutton);
3813 ImageView folderIconImageView = dialogFragment.getDialog().findViewById(R.id.edit_folder_default_icon_imageview);
3815 // Get the new folder name.
3816 String newFolderNameString = editFolderNameEditText.getText().toString();
3818 // Check if the favorite icon has changed.
3819 if (currentFolderIconRadioButton.isChecked()) { // Only the name has changed.
3820 // Update the name in the database.
3821 bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, oldFolderNameString, newFolderNameString);
3822 } else if (!currentFolderIconRadioButton.isChecked() && newFolderNameString.equals(oldFolderNameString)) { // Only the icon has changed.
3823 // Get the new folder icon `Bitmap`.
3824 Bitmap folderIconBitmap;
3825 if (defaultFolderIconRadioButton.isChecked()) {
3826 // Get the default folder icon and convert it to a `Bitmap`.
3827 Drawable folderIconDrawable = folderIconImageView.getDrawable();
3828 BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable;
3829 folderIconBitmap = folderIconBitmapDrawable.getBitmap();
3830 } else { // Use the `WebView` favorite icon.
3831 folderIconBitmap = favoriteIconBitmap;
3834 // Convert the folder `Bitmap` to a byte array. `0` is for lossless compression (the only option for a PNG).
3835 ByteArrayOutputStream folderIconByteArrayOutputStream = new ByteArrayOutputStream();
3836 folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, folderIconByteArrayOutputStream);
3837 byte[] folderIconByteArray = folderIconByteArrayOutputStream.toByteArray();
3839 // Update the folder icon in the database.
3840 bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, folderIconByteArray);
3841 } else { // The folder icon and the name have changed.
3842 // Get the new folder icon `Bitmap`.
3843 Bitmap folderIconBitmap;
3844 if (defaultFolderIconRadioButton.isChecked()) {
3845 // Get the default folder icon and convert it to a `Bitmap`.
3846 Drawable folderIconDrawable = folderIconImageView.getDrawable();
3847 BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable;
3848 folderIconBitmap = folderIconBitmapDrawable.getBitmap();
3849 } else { // Use the `WebView` favorite icon.
3850 folderIconBitmap = MainWebViewActivity.favoriteIconBitmap;
3853 // Convert the folder `Bitmap` to a byte array. `0` is for lossless compression (the only option for a PNG).
3854 ByteArrayOutputStream folderIconByteArrayOutputStream = new ByteArrayOutputStream();
3855 folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, folderIconByteArrayOutputStream);
3856 byte[] folderIconByteArray = folderIconByteArrayOutputStream.toByteArray();
3858 // Update the folder name and icon in the database.
3859 bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, oldFolderNameString, newFolderNameString, folderIconByteArray);
3862 // Update the bookmarks cursor with the current contents of this folder.
3863 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder);
3865 // Update the `ListView`.
3866 bookmarksCursorAdapter.changeCursor(bookmarksCursor);
3870 public void onHttpAuthenticationCancel() {
3871 // Cancel the `HttpAuthHandler`.
3872 httpAuthHandler.cancel();
3876 public void onHttpAuthenticationProceed(DialogFragment dialogFragment) {
3877 // Get handles for the `EditTexts`.
3878 EditText usernameEditText = dialogFragment.getDialog().findViewById(R.id.http_authentication_username);
3879 EditText passwordEditText = dialogFragment.getDialog().findViewById(R.id.http_authentication_password);
3881 // Proceed with the HTTP authentication.
3882 httpAuthHandler.proceed(usernameEditText.getText().toString(), passwordEditText.getText().toString());
3885 public void viewSslCertificate(View view) {
3886 // Show the `ViewSslCertificateDialog` `AlertDialog` and name this instance `@string/view_ssl_certificate`.
3887 DialogFragment viewSslCertificateDialogFragment = new ViewSslCertificateDialog();
3888 viewSslCertificateDialogFragment.show(getSupportFragmentManager(), getString(R.string.view_ssl_certificate));
3892 public void onSslErrorCancel() {
3893 sslErrorHandler.cancel();
3897 public void onSslErrorProceed() {
3898 sslErrorHandler.proceed();
3902 public void onPinnedMismatchBack() {
3903 if (mainWebView.canGoBack()) { // There is a back page in the history.
3904 // Reset the formatted URL string so the page will load correctly if blocking of third-party requests is enabled.
3905 formattedUrlString = "";
3907 // Set `navigatingHistory` so that the domain settings are applied when the new URL is loaded.
3908 navigatingHistory = true;
3911 mainWebView.goBack();
3912 } else { // There are no pages to go back to.
3913 // Load a blank page
3919 public void onPinnedMismatchProceed() {
3920 // Do not check the pinned information for this domain again until the domain changes.
3921 ignorePinnedDomainInformation = true;
3925 public void onUrlHistoryEntrySelected(int moveBackOrForwardSteps) {
3926 // Reset the formatted URL string so the page will load correctly if blocking of third-party requests is enabled.
3927 formattedUrlString = "";
3929 // Set `navigatingHistory` so that the domain settings are applied when the new URL is loaded.
3930 navigatingHistory = true;
3932 // Load the history entry.
3933 mainWebView.goBackOrForward(moveBackOrForwardSteps);
3937 public void onClearHistory() {
3938 // Clear the history.
3939 mainWebView.clearHistory();
3942 // Override `onBackPressed` to handle the navigation drawer and `mainWebView`.
3944 public void onBackPressed() {
3945 // Get a handle for the drawer layout.
3946 DrawerLayout drawerLayout = findViewById(R.id.drawerlayout);
3948 if (drawerLayout.isDrawerVisible(GravityCompat.START)) { // The navigation drawer is open.
3949 // Close the navigation drawer.
3950 drawerLayout.closeDrawer(GravityCompat.START);
3951 } else if (drawerLayout.isDrawerVisible(GravityCompat.END)){ // The bookmarks drawer is open.
3952 if (currentBookmarksFolder.isEmpty()) { // The home folder is displayed.
3953 // close the bookmarks drawer.
3954 drawerLayout.closeDrawer(GravityCompat.END);
3955 } else { // A subfolder is displayed.
3956 // Place the former parent folder in `currentFolder`.
3957 currentBookmarksFolder = bookmarksDatabaseHelper.getParentFolderName(currentBookmarksFolder);
3959 // Load the new folder.
3960 loadBookmarksFolder();
3963 } else if (mainWebView.canGoBack()) { // There is at least one item in the `WebView` history.
3964 // Reset the formatted URL string so the page will load correctly if blocking of third-party requests is enabled.
3965 formattedUrlString = "";
3967 // Set `navigatingHistory` so that the domain settings are applied when the new URL is loaded.
3968 navigatingHistory = true;
3971 mainWebView.goBack();
3972 } else { // There isn't anything to do in Privacy Browser.
3973 // Pass `onBackPressed()` to the system.
3974 super.onBackPressed();
3978 // 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.
3980 public void onActivityResult(int requestCode, int resultCode, Intent data) {
3981 // File uploads only work on API >= 21.
3982 if (Build.VERSION.SDK_INT >= 21) {
3983 // Pass the file to the WebView.
3984 fileChooserCallback.onReceiveValue(WebChromeClient.FileChooserParams.parseResult(resultCode, data));
3988 private void loadUrlFromTextBox() {
3989 // Get the text from urlTextBox and convert it to a string. trim() removes white spaces from the beginning and end of the string.
3990 String unformattedUrlString = urlTextBox.getText().toString().trim();
3992 // Check to see if `unformattedUrlString` is a valid URL. Otherwise, convert it into a search.
3993 if (unformattedUrlString.startsWith("content://")) {
3994 // Load the entire content URL.
3995 formattedUrlString = unformattedUrlString;
3996 } else if (Patterns.WEB_URL.matcher(unformattedUrlString).matches() || unformattedUrlString.startsWith("http://") || unformattedUrlString.startsWith("https://")
3997 || unformattedUrlString.startsWith("file://")) {
3998 // Add `https://` at the beginning if there is no protocol. Otherwise the app will segfault.
3999 if (!unformattedUrlString.startsWith("http") && !unformattedUrlString.startsWith("file://") && !unformattedUrlString.startsWith("content://")) {
4000 unformattedUrlString = "https://" + unformattedUrlString;
4003 // Initialize `unformattedUrl`.
4004 URL unformattedUrl = null;
4006 // 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.
4008 unformattedUrl = new URL(unformattedUrlString);
4009 } catch (MalformedURLException e) {
4010 e.printStackTrace();
4013 // The ternary operator (? :) makes sure that a null pointer exception is not thrown, which would happen if `.get` was called on a `null` value.
4014 String scheme = unformattedUrl != null ? unformattedUrl.getProtocol() : null;
4015 String authority = unformattedUrl != null ? unformattedUrl.getAuthority() : null;
4016 String path = unformattedUrl != null ? unformattedUrl.getPath() : null;
4017 String query = unformattedUrl != null ? unformattedUrl.getQuery() : null;
4018 String fragment = unformattedUrl != null ? unformattedUrl.getRef() : null;
4021 Uri.Builder formattedUri = new Uri.Builder();
4022 formattedUri.scheme(scheme).authority(authority).path(path).query(query).fragment(fragment);
4024 // Decode `formattedUri` as a `String` in `UTF-8`.
4026 formattedUrlString = URLDecoder.decode(formattedUri.build().toString(), "UTF-8");
4027 } catch (UnsupportedEncodingException exception) {
4028 // Load a blank string.
4029 formattedUrlString = "";
4031 } else if (unformattedUrlString.isEmpty()){ // Load a blank web site.
4032 // Load a blank string.
4033 formattedUrlString = "";
4034 } else { // Search for the contents of the URL box.
4035 // Create an encoded URL String.
4036 String encodedUrlString;
4038 // Sanitize the search input.
4040 encodedUrlString = URLEncoder.encode(unformattedUrlString, "UTF-8");
4041 } catch (UnsupportedEncodingException exception) {
4042 encodedUrlString = "";
4045 // Add the base search URL.
4046 formattedUrlString = searchURL + encodedUrlString;
4049 // 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.
4050 urlTextBox.clearFocus();
4053 loadUrl(formattedUrlString);
4056 private void loadUrl(String url) {// Apply any custom domain settings.
4057 // Set the URL as the formatted URL string so that checking third-party requests works correctly.
4058 formattedUrlString = url;
4060 // Apply the domain settings.
4061 applyDomainSettings(url, true, false);
4063 // If loading a website, set `urlIsLoading` to prevent changes in the user agent on websites with redirects from reloading the current website.
4064 urlIsLoading = !url.equals("");
4067 mainWebView.loadUrl(url, customHeaders);
4070 public void findPreviousOnPage(View view) {
4071 // Go to the previous highlighted phrase on the page. `false` goes backwards instead of forwards.
4072 mainWebView.findNext(false);
4075 public void findNextOnPage(View view) {
4076 // Go to the next highlighted phrase on the page. `true` goes forwards instead of backwards.
4077 mainWebView.findNext(true);
4080 public void closeFindOnPage(View view) {
4081 // Delete the contents of `find_on_page_edittext`.
4082 findOnPageEditText.setText(null);
4084 // Clear the highlighted phrases.
4085 mainWebView.clearMatches();
4087 // Hide the Find on Page `RelativeLayout`.
4088 findOnPageLinearLayout.setVisibility(View.GONE);
4090 // Get a handle for the toolbar.
4091 Toolbar toolbar = findViewById(R.id.toolbar);
4093 // Show the toolbar.
4094 toolbar.setVisibility(View.VISIBLE);
4096 // Hide the keyboard so we can see the webpage. `0` indicates no additional flags.
4097 inputMethodManager.hideSoftInputFromWindow(mainWebView.getWindowToken(), 0);
4100 private void applyAppSettings() {
4101 // Get a handle for the shared preferences.
4102 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
4104 // Store the values from the shared preferences in variables.
4105 incognitoModeEnabled = sharedPreferences.getBoolean("incognito_mode", false);
4106 boolean doNotTrackEnabled = sharedPreferences.getBoolean("do_not_track", false);
4107 proxyThroughOrbot = sharedPreferences.getBoolean("proxy_through_orbot", false);
4108 fullScreenBrowsingModeEnabled = sharedPreferences.getBoolean("full_screen_browsing_mode", false);
4109 hideSystemBarsOnFullscreen = sharedPreferences.getBoolean("hide_system_bars", false);
4110 translucentNavigationBarOnFullscreen = sharedPreferences.getBoolean("translucent_navigation_bar", true);
4111 downloadWithExternalApp = sharedPreferences.getBoolean("download_with_external_app", false);
4113 // Apply the proxy through Orbot settings.
4114 applyProxyThroughOrbot(false);
4116 // Set Do Not Track status.
4117 if (doNotTrackEnabled) {
4118 customHeaders.put("DNT", "1");
4120 customHeaders.remove("DNT");
4123 // Get handles for the views that need to be modified. `getSupportActionBar()` must be used until the minimum API >= 21.
4124 DrawerLayout drawerLayout = findViewById(R.id.drawerlayout);
4125 CoordinatorLayout coordinatorLayout = findViewById(R.id.coordinatorlayout);
4126 ActionBar actionBar = getSupportActionBar();
4128 // Apply the appropriate full screen mode the `SYSTEM_UI` flags.
4129 if (fullScreenBrowsingModeEnabled && inFullScreenBrowsingMode) { // Privacy Browser is currently in full screen browsing mode.
4130 if (hideSystemBarsOnFullscreen) { // Hide everything.
4131 // Remove the translucent navigation setting if it is currently flagged.
4132 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
4134 // Remove the translucent status bar overlay.
4135 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
4137 // Remove the translucent status bar overlay on the `Drawer Layout`, which is special and needs its own command.
4138 drawerLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
4140 /* SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
4141 * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
4142 * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
4144 coordinatorLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
4145 } else { // Hide everything except the status and navigation bars.
4146 // Remove any `SYSTEM_UI` flags from the coordinator layout.
4147 coordinatorLayout.setSystemUiVisibility(0);
4149 // Add the translucent status flag if it is unset.
4150 getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
4152 if (translucentNavigationBarOnFullscreen) {
4153 // Set the navigation bar to be translucent.
4154 getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
4156 // Set the navigation bar to be black.
4157 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
4160 } else { // Privacy Browser is not in full screen browsing mode.
4161 // 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.
4162 inFullScreenBrowsingMode = false;
4164 // Remove the incorrect lint warning below that the action bar might be null.
4165 assert actionBar != null;
4167 // Show the action bar if the find on page linear layout is not visible.
4168 if (findOnPageLinearLayout.getVisibility() == View.GONE) {
4172 // Show the `BannerAd` in the free flavor.
4173 if (BuildConfig.FLAVOR.contentEquals("free")) {
4174 // Initialize the ad. The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations.
4175 AdHelper.initializeAds(findViewById(R.id.adview), getApplicationContext(), fragmentManager, getString(R.string.google_app_id), getString(R.string.ad_unit_id));
4178 // Remove any `SYSTEM_UI` flags from the coordinator layout.
4179 coordinatorLayout.setSystemUiVisibility(0);
4181 // Remove the translucent navigation bar flag if it is set.
4182 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
4184 // Add the translucent status flag if it is unset. This also resets `drawerLayout's` `View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN`.
4185 getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
4187 // Constrain the coordinator layout inside the status and navigation bars.
4188 coordinatorLayout.setFitsSystemWindows(true);
4192 // `reloadWebsite` is used if returning from the Domains activity. Otherwise JavaScript might not function correctly if it is newly enabled.
4193 // The deprecated `.getDrawable()` must be used until the minimum API >= 21.
4194 @SuppressWarnings("deprecation")
4195 private boolean applyDomainSettings(String url, boolean resetFavoriteIcon, boolean reloadWebsite) {
4196 // Get the current user agent.
4197 String initialUserAgent = mainWebView.getSettings().getUserAgentString();
4199 // Initialize a variable to track if the user agent changes.
4200 boolean userAgentChanged = false;
4202 // Parse the URL into a URI.
4203 Uri uri = Uri.parse(url);
4205 // Extract the domain from `uri`.
4206 String hostName = uri.getHost();
4208 // Initialize `loadingNewDomainName`.
4209 boolean loadingNewDomainName;
4211 // If either `hostName` or `currentDomainName` are `null`, run the options for loading a new domain name.
4212 // The lint suggestion to simplify the `if` statement is incorrect, because `hostName.equals(currentDomainName)` can produce a `null object reference.`
4213 //noinspection SimplifiableIfStatement
4214 if ((hostName == null) || (currentDomainName == null)) {
4215 loadingNewDomainName = true;
4216 } else { // Determine if `hostName` equals `currentDomainName`.
4217 loadingNewDomainName = !hostName.equals(currentDomainName);
4220 // Strings don't like to be null.
4221 if (hostName == null) {
4225 // 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.
4226 if (loadingNewDomainName) {
4227 // Set the new `hostname` as the `currentDomainName`.
4228 currentDomainName = hostName;
4230 // Reset the ignoring of pinned domain information.
4231 ignorePinnedDomainInformation = false;
4233 // Reset the favorite icon if specified.
4234 if (resetFavoriteIcon) {
4235 favoriteIconBitmap = favoriteIconDefaultBitmap;
4236 favoriteIconImageView.setImageBitmap(Bitmap.createScaledBitmap(favoriteIconBitmap, 64, 64, true));
4239 // Get a handle for the swipe refresh layout.
4240 SwipeRefreshLayout swipeRefreshLayout = findViewById(R.id.swiperefreshlayout);
4242 // Initialize the database handler. The `0` specifies the database version, but that is ignored and set instead using a constant in `DomainsDatabaseHelper`.
4243 DomainsDatabaseHelper domainsDatabaseHelper = new DomainsDatabaseHelper(this, null, null, 0);
4245 // Get a full cursor from `domainsDatabaseHelper`.
4246 Cursor domainNameCursor = domainsDatabaseHelper.getDomainNameCursorOrderedByDomain();
4248 // Initialize `domainSettingsSet`.
4249 Set<String> domainSettingsSet = new HashSet<>();
4251 // Get the domain name column index.
4252 int domainNameColumnIndex = domainNameCursor.getColumnIndex(DomainsDatabaseHelper.DOMAIN_NAME);
4254 // Populate `domainSettingsSet`.
4255 for (int i = 0; i < domainNameCursor.getCount(); i++) {
4256 // Move `domainsCursor` to the current row.
4257 domainNameCursor.moveToPosition(i);
4259 // Store the domain name in `domainSettingsSet`.
4260 domainSettingsSet.add(domainNameCursor.getString(domainNameColumnIndex));
4263 // Close `domainNameCursor.
4264 domainNameCursor.close();
4266 // Initialize variables to track if domain settings will be applied and, if so, under which name.
4267 domainSettingsApplied = false;
4268 String domainNameInDatabase = null;
4270 // Check the hostname.
4271 if (domainSettingsSet.contains(hostName)) {
4272 domainSettingsApplied = true;
4273 domainNameInDatabase = hostName;
4276 // Check all the subdomains of the host name against wildcard domains in the domain cursor.
4277 while (!domainSettingsApplied && hostName.contains(".")) { // Stop checking if domain settings are already applied or there are no more `.` in the host name.
4278 if (domainSettingsSet.contains("*." + hostName)) { // Check the host name prepended by `*.`.
4279 // Apply the domain settings.
4280 domainSettingsApplied = true;
4282 // Store the applied domain names as it appears in the database.
4283 domainNameInDatabase = "*." + hostName;
4286 // Strip out the lowest subdomain of of the host name.
4287 hostName = hostName.substring(hostName.indexOf(".") + 1);
4291 // Get a handle for the shared preference.
4292 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
4294 // Store the general preference information.
4295 String defaultFontSizeString = sharedPreferences.getString("font_size", getString(R.string.font_size_default_value));
4296 String defaultUserAgentName = sharedPreferences.getString("user_agent", getString(R.string.user_agent_default_value));
4297 defaultCustomUserAgentString = sharedPreferences.getString("custom_user_agent", getString(R.string.custom_user_agent_default_value));
4298 boolean defaultSwipeToRefresh = sharedPreferences.getBoolean("swipe_to_refresh", true);
4299 nightMode = sharedPreferences.getBoolean("night_mode", false);
4300 boolean displayWebpageImages = sharedPreferences.getBoolean("display_webpage_images", true);
4302 if (domainSettingsApplied) { // The url has custom domain settings.
4303 // Get a cursor for the current host and move it to the first position.
4304 Cursor currentHostDomainSettingsCursor = domainsDatabaseHelper.getCursorForDomainName(domainNameInDatabase);
4305 currentHostDomainSettingsCursor.moveToFirst();
4307 // Get the settings from the cursor.
4308 domainSettingsDatabaseId = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper._ID)));
4309 javaScriptEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_JAVASCRIPT)) == 1);
4310 firstPartyCookiesEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FIRST_PARTY_COOKIES)) == 1);
4311 thirdPartyCookiesEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_THIRD_PARTY_COOKIES)) == 1);
4312 domStorageEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_DOM_STORAGE)) == 1);
4313 // Form data can be removed once the minimum API >= 26.
4314 saveFormDataEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FORM_DATA)) == 1);
4315 easyListEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_EASYLIST)) == 1);
4316 easyPrivacyEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_EASYPRIVACY)) == 1);
4317 fanboysAnnoyanceListEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FANBOYS_ANNOYANCE_LIST)) == 1);
4318 fanboysSocialBlockingListEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FANBOYS_SOCIAL_BLOCKING_LIST)) == 1);
4319 ultraPrivacyEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_ULTRAPRIVACY)) == 1);
4320 blockAllThirdPartyRequests = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.BLOCK_ALL_THIRD_PARTY_REQUESTS)) == 1);
4321 String userAgentName = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.USER_AGENT));
4322 int fontSize = currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.FONT_SIZE));
4323 int swipeToRefreshInt = currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SWIPE_TO_REFRESH));
4324 int nightModeInt = currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.NIGHT_MODE));
4325 int displayWebpageImagesInt = currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.DISPLAY_IMAGES));
4326 pinnedSslCertificate = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.PINNED_SSL_CERTIFICATE)) == 1);
4327 pinnedSslIssuedToCName = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_TO_COMMON_NAME));
4328 pinnedSslIssuedToOName = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_TO_ORGANIZATION));
4329 pinnedSslIssuedToUName = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_TO_ORGANIZATIONAL_UNIT));
4330 pinnedSslIssuedByCName = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_BY_COMMON_NAME));
4331 pinnedSslIssuedByOName = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_BY_ORGANIZATION));
4332 pinnedSslIssuedByUName = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_BY_ORGANIZATIONAL_UNIT));
4333 pinnedIpAddresses = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.PINNED_IP_ADDRESSES)) == 1);
4334 pinnedHostIpAddresses = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.IP_ADDRESSES));
4336 // Set `nightMode` according to `nightModeInt`. If `nightModeInt` is `DomainsDatabaseHelper.NIGHT_MODE_SYSTEM_DEFAULT` the current setting from `sharedPreferences` will be used.
4337 switch (nightModeInt) {
4338 case DomainsDatabaseHelper.NIGHT_MODE_ENABLED:
4342 case DomainsDatabaseHelper.NIGHT_MODE_DISABLED:
4347 // Store the domain JavaScript status. This is used by the options menu night mode toggle.
4348 domainSettingsJavaScriptEnabled = javaScriptEnabled;
4350 // Enable JavaScript if night mode is enabled.
4352 javaScriptEnabled = true;
4355 // Set the pinned SSL certificate start date to `null` if the saved date `long` is 0.
4356 if (currentHostDomainSettingsCursor.getLong(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_START_DATE)) == 0) {
4357 pinnedSslStartDate = null;
4359 pinnedSslStartDate = new Date(currentHostDomainSettingsCursor.getLong(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_START_DATE)));
4362 // Set the pinned SSL certificate end date to `null` if the saved date `long` is 0.
4363 if (currentHostDomainSettingsCursor.getLong(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_END_DATE)) == 0) {
4364 pinnedSslEndDate = null;
4366 pinnedSslEndDate = new Date(currentHostDomainSettingsCursor.getLong(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_END_DATE)));
4369 // Close `currentHostDomainSettingsCursor`.
4370 currentHostDomainSettingsCursor.close();
4372 // Apply the domain settings.
4373 mainWebView.getSettings().setJavaScriptEnabled(javaScriptEnabled);
4374 cookieManager.setAcceptCookie(firstPartyCookiesEnabled);
4375 mainWebView.getSettings().setDomStorageEnabled(domStorageEnabled);
4377 // Apply the form data setting if the API < 26.
4378 if (Build.VERSION.SDK_INT < 26) {
4379 mainWebView.getSettings().setSaveFormData(saveFormDataEnabled);
4382 // Apply the font size.
4383 if (fontSize == 0) { // Apply the default font size.
4384 mainWebView.getSettings().setTextZoom(Integer.valueOf(defaultFontSizeString));
4385 } else { // Apply the specified font size.
4386 mainWebView.getSettings().setTextZoom(fontSize);
4389 // Set third-party cookies status if API >= 21.
4390 if (Build.VERSION.SDK_INT >= 21) {
4391 cookieManager.setAcceptThirdPartyCookies(mainWebView, thirdPartyCookiesEnabled);
4394 // 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.
4395 // <https://redmine.stoutner.com/issues/160>
4396 if (!urlIsLoading) {
4397 // Set the user agent.
4398 if (userAgentName.equals(getString(R.string.system_default_user_agent))) { // Use the system default user agent.
4399 // Get the array position of the default user agent name.
4400 int defaultUserAgentArrayPosition = userAgentNamesArray.getPosition(defaultUserAgentName);
4402 // Set the user agent according to the system default.
4403 switch (defaultUserAgentArrayPosition) {
4404 case UNRECOGNIZED_USER_AGENT: // The default user agent name is not on the canonical list.
4405 // This is probably because it was set in an older version of Privacy Browser before the switch to persistent user agent names.
4406 mainWebView.getSettings().setUserAgentString(defaultUserAgentName);
4409 case SETTINGS_WEBVIEW_DEFAULT_USER_AGENT:
4410 // Set the user agent to `""`, which uses the default value.
4411 mainWebView.getSettings().setUserAgentString("");
4414 case SETTINGS_CUSTOM_USER_AGENT:
4415 // Set the custom user agent.
4416 mainWebView.getSettings().setUserAgentString(defaultCustomUserAgentString);
4420 // Get the user agent string from the user agent data array
4421 mainWebView.getSettings().setUserAgentString(userAgentDataArray[defaultUserAgentArrayPosition]);
4423 } else { // Set the user agent according to the stored name.
4424 // Get the array position of the user agent name.
4425 int userAgentArrayPosition = userAgentNamesArray.getPosition(userAgentName);
4427 switch (userAgentArrayPosition) {
4428 case UNRECOGNIZED_USER_AGENT: // The user agent name contains a custom user agent.
4429 mainWebView.getSettings().setUserAgentString(userAgentName);
4432 case SETTINGS_WEBVIEW_DEFAULT_USER_AGENT:
4433 // Set the user agent to `""`, which uses the default value.
4434 mainWebView.getSettings().setUserAgentString("");
4438 // Get the user agent string from the user agent data array.
4439 mainWebView.getSettings().setUserAgentString(userAgentDataArray[userAgentArrayPosition]);
4443 // Store the applied user agent string, which is used in the View Source activity.
4444 appliedUserAgentString = mainWebView.getSettings().getUserAgentString();
4446 // Update the user agent change tracker.
4447 userAgentChanged = !appliedUserAgentString.equals(initialUserAgent);
4450 // Set swipe to refresh.
4451 switch (swipeToRefreshInt) {
4452 case DomainsDatabaseHelper.SWIPE_TO_REFRESH_SYSTEM_DEFAULT:
4453 // Set swipe to refresh according to the default.
4454 swipeRefreshLayout.setEnabled(defaultSwipeToRefresh);
4457 case DomainsDatabaseHelper.SWIPE_TO_REFRESH_ENABLED:
4458 // Enable swipe to refresh.
4459 swipeRefreshLayout.setEnabled(true);
4462 case DomainsDatabaseHelper.SWIPE_TO_REFRESH_DISABLED:
4463 // Disable swipe to refresh.
4464 swipeRefreshLayout.setEnabled(false);
4467 // Set the loading of webpage images.
4468 switch (displayWebpageImagesInt) {
4469 case DomainsDatabaseHelper.DISPLAY_WEBPAGE_IMAGES_SYSTEM_DEFAULT:
4470 mainWebView.getSettings().setLoadsImagesAutomatically(displayWebpageImages);
4473 case DomainsDatabaseHelper.DISPLAY_WEBPAGE_IMAGES_ENABLED:
4474 mainWebView.getSettings().setLoadsImagesAutomatically(true);
4477 case DomainsDatabaseHelper.DISPLAY_WEBPAGE_IMAGES_DISABLED:
4478 mainWebView.getSettings().setLoadsImagesAutomatically(false);
4482 // 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.
4484 urlAppBarRelativeLayout.setBackground(getResources().getDrawable(R.drawable.url_bar_background_dark_blue));
4486 urlAppBarRelativeLayout.setBackground(getResources().getDrawable(R.drawable.url_bar_background_light_green));
4488 } else { // The new URL does not have custom domain settings. Load the defaults.
4489 // Store the values from `sharedPreferences` in variables.
4490 javaScriptEnabled = sharedPreferences.getBoolean("javascript", false);
4491 firstPartyCookiesEnabled = sharedPreferences.getBoolean("first_party_cookies", false);
4492 thirdPartyCookiesEnabled = sharedPreferences.getBoolean("third_party_cookies", false);
4493 domStorageEnabled = sharedPreferences.getBoolean("dom_storage", false);
4494 saveFormDataEnabled = sharedPreferences.getBoolean("save_form_data", false); // Form data can be removed once the minimum API >= 26.
4495 easyListEnabled = sharedPreferences.getBoolean("easylist", true);
4496 easyPrivacyEnabled = sharedPreferences.getBoolean("easyprivacy", true);
4497 fanboysAnnoyanceListEnabled = sharedPreferences.getBoolean("fanboys_annoyance_list", true);
4498 fanboysSocialBlockingListEnabled = sharedPreferences.getBoolean("fanboys_social_blocking_list", true);
4499 ultraPrivacyEnabled = sharedPreferences.getBoolean("ultraprivacy", true);
4500 blockAllThirdPartyRequests = sharedPreferences.getBoolean("block_all_third_party_requests", false);
4502 // Set `javaScriptEnabled` to be `true` if `night_mode` is `true`.
4504 javaScriptEnabled = true;
4507 // Apply the default settings.
4508 mainWebView.getSettings().setJavaScriptEnabled(javaScriptEnabled);
4509 cookieManager.setAcceptCookie(firstPartyCookiesEnabled);
4510 mainWebView.getSettings().setDomStorageEnabled(domStorageEnabled);
4511 mainWebView.getSettings().setTextZoom(Integer.valueOf(defaultFontSizeString));
4512 swipeRefreshLayout.setEnabled(defaultSwipeToRefresh);
4514 // Apply the form data setting if the API < 26.
4515 if (Build.VERSION.SDK_INT < 26) {
4516 mainWebView.getSettings().setSaveFormData(saveFormDataEnabled);
4519 // Reset the pinned variables.
4520 domainSettingsDatabaseId = -1;
4521 pinnedSslCertificate = false;
4522 pinnedSslIssuedToCName = "";
4523 pinnedSslIssuedToOName = "";
4524 pinnedSslIssuedToUName = "";
4525 pinnedSslIssuedByCName = "";
4526 pinnedSslIssuedByOName = "";
4527 pinnedSslIssuedByUName = "";
4528 pinnedSslStartDate = null;
4529 pinnedSslEndDate = null;
4530 pinnedIpAddresses = false;
4531 pinnedHostIpAddresses = "";
4533 // Set third-party cookies status if API >= 21.
4534 if (Build.VERSION.SDK_INT >= 21) {
4535 cookieManager.setAcceptThirdPartyCookies(mainWebView, thirdPartyCookiesEnabled);
4538 // 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.
4539 // <https://redmine.stoutner.com/issues/160>
4540 if (!urlIsLoading) {
4541 // Get the array position of the user agent name.
4542 int userAgentArrayPosition = userAgentNamesArray.getPosition(defaultUserAgentName);
4544 // Set the user agent.
4545 switch (userAgentArrayPosition) {
4546 case UNRECOGNIZED_USER_AGENT: // The default user agent name is not on the canonical list.
4547 // This is probably because it was set in an older version of Privacy Browser before the switch to persistent user agent names.
4548 mainWebView.getSettings().setUserAgentString(defaultUserAgentName);
4551 case SETTINGS_WEBVIEW_DEFAULT_USER_AGENT:
4552 // Set the user agent to `""`, which uses the default value.
4553 mainWebView.getSettings().setUserAgentString("");
4556 case SETTINGS_CUSTOM_USER_AGENT:
4557 // Set the custom user agent.
4558 mainWebView.getSettings().setUserAgentString(defaultCustomUserAgentString);
4562 // Get the user agent string from the user agent data array
4563 mainWebView.getSettings().setUserAgentString(userAgentDataArray[userAgentArrayPosition]);
4566 // Store the applied user agent string, which is used in the View Source activity.
4567 appliedUserAgentString = mainWebView.getSettings().getUserAgentString();
4569 // Update the user agent change tracker.
4570 userAgentChanged = !appliedUserAgentString.equals(initialUserAgent);
4573 // Set the loading of webpage images.
4574 mainWebView.getSettings().setLoadsImagesAutomatically(displayWebpageImages);
4576 // Set a transparent background on `urlTextBox`. The deprecated `.getDrawable()` must be used until the minimum API >= 21.
4577 urlAppBarRelativeLayout.setBackgroundDrawable(getResources().getDrawable(R.color.transparent));
4580 // Close the domains database helper.
4581 domainsDatabaseHelper.close();
4583 // Update the privacy icons, but only if `mainMenu` has already been populated.
4584 if (mainMenu != null) {
4585 updatePrivacyIcons(true);
4589 swipeRefreshLayout.setEnabled(false);
4592 // Reload the website if returning from the Domains activity.
4593 if (reloadWebsite) {
4594 mainWebView.reload();
4597 // Return the user agent changed status.
4598 return userAgentChanged;
4601 private void applyProxyThroughOrbot(boolean reloadWebsite) {
4602 // Get a handle for the shared preferences.
4603 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
4605 // Get the search preferences.
4606 String homepageString = sharedPreferences.getString("homepage", getString(R.string.homepage_default_value));
4607 String torHomepageString = sharedPreferences.getString("tor_homepage", getString(R.string.tor_homepage_default_value));
4608 String torSearchString = sharedPreferences.getString("tor_search", getString(R.string.tor_search_default_value));
4609 String torSearchCustomUrlString = sharedPreferences.getString("tor_search_custom_url", getString(R.string.tor_search_custom_url_default_value));
4610 String searchString = sharedPreferences.getString("search", getString(R.string.search_default_value));
4611 String searchCustomUrlString = sharedPreferences.getString("search_custom_url", getString(R.string.search_custom_url_default_value));
4613 // Get a handle for the action bar. `getSupportActionBar()` must be used until the minimum API >= 21.
4614 ActionBar actionBar = getSupportActionBar();
4616 // Remove the incorrect lint warning later that the action bar might be null.
4617 assert actionBar != null;
4619 // Set the homepage, search, and proxy options.
4620 if (proxyThroughOrbot) { // Set the Tor options.
4621 // Set `torHomepageString` as `homepage`.
4622 homepage = torHomepageString;
4624 // If formattedUrlString is null assign the homepage to it.
4625 if (formattedUrlString == null) {
4626 formattedUrlString = homepage;
4629 // Set the search URL.
4630 if (torSearchString.equals("Custom URL")) { // Get the custom URL string.
4631 searchURL = torSearchCustomUrlString;
4632 } else { // Use the string from the pre-built list.
4633 searchURL = torSearchString;
4636 // Set the proxy. `this` refers to the current activity where an `AlertDialog` might be displayed.
4637 OrbotProxyHelper.setProxy(getApplicationContext(), this, "localhost", "8118");
4639 // Set the `appBar` background to indicate proxying through Orbot is enabled. `this` refers to the context.
4641 actionBar.setBackgroundDrawable(ContextCompat.getDrawable(this, R.color.dark_blue_30));
4643 actionBar.setBackgroundDrawable(ContextCompat.getDrawable(this, R.color.blue_50));
4646 // Check to see if Orbot is ready.
4647 if (!orbotStatus.equals("ON")) { // Orbot is not ready.
4648 // Set `waitingForOrbot`.
4649 waitingForOrbot = true;
4651 // Disable the wide view port so that the waiting for Orbot text is displayed correctly.
4652 mainWebView.getSettings().setUseWideViewPort(false);
4654 // Load a waiting page. `null` specifies no encoding, which defaults to ASCII.
4655 mainWebView.loadData(waitingForOrbotHtmlString, "text/html", null);
4656 } else if (reloadWebsite) { // Orbot is ready and the website should be reloaded.
4657 // Reload the website.
4658 mainWebView.reload();
4660 } else { // Set the non-Tor options.
4661 // Set `homepageString` as `homepage`.
4662 homepage = homepageString;
4664 // If formattedUrlString is null assign the homepage to it.
4665 if (formattedUrlString == null) {
4666 formattedUrlString = homepage;
4669 // Set the search URL.
4670 if (searchString.equals("Custom URL")) { // Get the custom URL string.
4671 searchURL = searchCustomUrlString;
4672 } else { // Use the string from the pre-built list.
4673 searchURL = searchString;
4676 // Reset the proxy to default. The host is `""` and the port is `"0"`.
4677 OrbotProxyHelper.setProxy(getApplicationContext(), this, "", "0");
4679 // Set the default `appBar` background. `this` refers to the context.
4681 actionBar.setBackgroundDrawable(ContextCompat.getDrawable(this, R.color.gray_900));
4683 actionBar.setBackgroundDrawable(ContextCompat.getDrawable(this, R.color.gray_100));
4686 // Reset `waitingForOrbot.
4687 waitingForOrbot = false;
4689 // Reload the website if requested.
4690 if (reloadWebsite) {
4691 mainWebView.reload();
4696 private void updatePrivacyIcons(boolean runInvalidateOptionsMenu) {
4697 // Get handles for the menu items.
4698 MenuItem privacyMenuItem = mainMenu.findItem(R.id.toggle_javascript);
4699 MenuItem firstPartyCookiesMenuItem = mainMenu.findItem(R.id.toggle_first_party_cookies);
4700 MenuItem domStorageMenuItem = mainMenu.findItem(R.id.toggle_dom_storage);
4701 MenuItem refreshMenuItem = mainMenu.findItem(R.id.refresh);
4703 // Update the privacy icon.
4704 if (javaScriptEnabled) { // JavaScript is enabled.
4705 privacyMenuItem.setIcon(R.drawable.javascript_enabled);
4706 } else if (firstPartyCookiesEnabled) { // JavaScript is disabled but cookies are enabled.
4707 privacyMenuItem.setIcon(R.drawable.warning);
4708 } else { // All the dangerous features are disabled.
4709 privacyMenuItem.setIcon(R.drawable.privacy_mode);
4712 // Update the first-party cookies icon.
4713 if (firstPartyCookiesEnabled) { // First-party cookies are enabled.
4714 firstPartyCookiesMenuItem.setIcon(R.drawable.cookies_enabled);
4715 } else { // First-party cookies are disabled.
4717 firstPartyCookiesMenuItem.setIcon(R.drawable.cookies_disabled_dark);
4719 firstPartyCookiesMenuItem.setIcon(R.drawable.cookies_disabled_light);
4723 // Update the DOM storage icon.
4724 if (javaScriptEnabled && domStorageEnabled) { // Both JavaScript and DOM storage are enabled.
4725 domStorageMenuItem.setIcon(R.drawable.dom_storage_enabled);
4726 } else if (javaScriptEnabled) { // JavaScript is enabled but DOM storage is disabled.
4728 domStorageMenuItem.setIcon(R.drawable.dom_storage_disabled_dark);
4730 domStorageMenuItem.setIcon(R.drawable.dom_storage_disabled_light);
4732 } else { // JavaScript is disabled, so DOM storage is ghosted.
4734 domStorageMenuItem.setIcon(R.drawable.dom_storage_ghosted_dark);
4736 domStorageMenuItem.setIcon(R.drawable.dom_storage_ghosted_light);
4740 // Update the refresh icon.
4742 refreshMenuItem.setIcon(R.drawable.refresh_enabled_dark);
4744 refreshMenuItem.setIcon(R.drawable.refresh_enabled_light);
4747 // `invalidateOptionsMenu` calls `onPrepareOptionsMenu()` and redraws the icons in the `AppBar`.
4748 if (runInvalidateOptionsMenu) {
4749 invalidateOptionsMenu();
4753 private void openUrlWithExternalApp(String url) {
4754 // Create a download intent. Not specifying the action type will display the maximum number of options.
4755 Intent downloadIntent = new Intent();
4757 // Set the URI and the MIME type. Specifying `text/html` displays a good number of options.
4758 downloadIntent.setDataAndType(Uri.parse(url), "text/html");
4760 // Flag the intent to open in a new task.
4761 downloadIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
4763 // Show the chooser.
4764 startActivity(Intent.createChooser(downloadIntent, getString(R.string.open_with)));
4767 private void highlightUrlText() {
4768 // Only highlight the URL text if the box is not currently selected.
4769 if (!urlTextBox.hasFocus()) {
4770 // Get the URL string.
4771 String urlString = urlTextBox.getText().toString();
4773 // Highlight the URL according to the protocol.
4774 if (urlString.startsWith("file://")) { // This is a file URL.
4775 // De-emphasize only the protocol.
4776 urlTextBox.getText().setSpan(initialGrayColorSpan, 0, 7, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
4777 } else if (urlString.startsWith("content://")) {
4778 // De-emphasize only the protocol.
4779 urlTextBox.getText().setSpan(initialGrayColorSpan, 0, 10, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
4780 } else { // This is a web URL.
4781 // Get the index of the `/` immediately after the domain name.
4782 int endOfDomainName = urlString.indexOf("/", (urlString.indexOf("//") + 2));
4784 // Create a base URL string.
4787 // Get the base URL.
4788 if (endOfDomainName > 0) { // There is at least one character after the base URL.
4789 // Get the base URL.
4790 baseUrl = urlString.substring(0, endOfDomainName);
4791 } else { // There are no characters after the base URL.
4792 // Set the base URL to be the entire URL string.
4793 baseUrl = urlString;
4796 // Get the index of the last `.` in the domain.
4797 int lastDotIndex = baseUrl.lastIndexOf(".");
4799 // Get the index of the penultimate `.` in the domain.
4800 int penultimateDotIndex = baseUrl.lastIndexOf(".", lastDotIndex - 1);
4802 // Markup the beginning of the URL.
4803 if (urlString.startsWith("http://")) { // Highlight the protocol of connections that are not encrypted.
4804 urlTextBox.getText().setSpan(redColorSpan, 0, 7, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
4806 // De-emphasize subdomains.
4807 if (penultimateDotIndex > 0) { // There is more than one subdomain in the domain name.
4808 urlTextBox.getText().setSpan(initialGrayColorSpan, 7, penultimateDotIndex + 1, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
4810 } else if (urlString.startsWith("https://")) { // De-emphasize the protocol of connections that are encrypted.
4811 if (penultimateDotIndex > 0) { // There is more than one subdomain in the domain name.
4812 // De-emphasize the protocol and the additional subdomains.
4813 urlTextBox.getText().setSpan(initialGrayColorSpan, 0, penultimateDotIndex + 1, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
4814 } else { // There is only one subdomain in the domain name.
4815 // De-emphasize only the protocol.
4816 urlTextBox.getText().setSpan(initialGrayColorSpan, 0, 8, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
4820 // De-emphasize the text after the domain name.
4821 if (endOfDomainName > 0) {
4822 urlTextBox.getText().setSpan(finalGrayColorSpan, endOfDomainName, urlString.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
4828 private void loadBookmarksFolder() {
4829 // Update the bookmarks cursor with the contents of the bookmarks database for the current folder.
4830 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder);
4832 // Populate the bookmarks cursor adapter. `this` specifies the `Context`. `false` disables `autoRequery`.
4833 bookmarksCursorAdapter = new CursorAdapter(this, bookmarksCursor, false) {
4835 public View newView(Context context, Cursor cursor, ViewGroup parent) {
4836 // Inflate the individual item layout. `false` does not attach it to the root.
4837 return getLayoutInflater().inflate(R.layout.bookmarks_drawer_item_linearlayout, parent, false);
4841 public void bindView(View view, Context context, Cursor cursor) {
4842 // Get handles for the views.
4843 ImageView bookmarkFavoriteIcon = view.findViewById(R.id.bookmark_favorite_icon);
4844 TextView bookmarkNameTextView = view.findViewById(R.id.bookmark_name);
4846 // Get the favorite icon byte array from the cursor.
4847 byte[] favoriteIconByteArray = cursor.getBlob(cursor.getColumnIndex(BookmarksDatabaseHelper.FAVORITE_ICON));
4849 // Convert the byte array to a `Bitmap` beginning at the first byte and ending at the last.
4850 Bitmap favoriteIconBitmap = BitmapFactory.decodeByteArray(favoriteIconByteArray, 0, favoriteIconByteArray.length);
4852 // Display the bitmap in `bookmarkFavoriteIcon`.
4853 bookmarkFavoriteIcon.setImageBitmap(favoriteIconBitmap);
4855 // Get the bookmark name from the cursor and display it in `bookmarkNameTextView`.
4856 String bookmarkNameString = cursor.getString(cursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME));
4857 bookmarkNameTextView.setText(bookmarkNameString);
4859 // Make the font bold for folders.
4860 if (cursor.getInt(cursor.getColumnIndex(BookmarksDatabaseHelper.IS_FOLDER)) == 1) {
4861 bookmarkNameTextView.setTypeface(Typeface.DEFAULT_BOLD);
4862 } else { // Reset the font to default for normal bookmarks.
4863 bookmarkNameTextView.setTypeface(Typeface.DEFAULT);
4868 // Populate the `ListView` with the adapter.
4869 bookmarksListView.setAdapter(bookmarksCursorAdapter);
4871 // Set the bookmarks drawer title.
4872 if (currentBookmarksFolder.isEmpty()) {
4873 bookmarksTitleTextView.setText(R.string.bookmarks);
4875 bookmarksTitleTextView.setText(currentBookmarksFolder);
4879 private void openWithApp(String url) {
4880 // Create the open with intent with `ACTION_VIEW`.
4881 Intent openWithAppIntent = new Intent(Intent.ACTION_VIEW);
4883 // Set the URI but not the MIME type. This should open all available apps.
4884 openWithAppIntent.setData(Uri.parse(url));
4886 // Flag the intent to open in a new task.
4887 openWithAppIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
4889 // Show the chooser.
4890 startActivity(openWithAppIntent);
4893 private void openWithBrowser(String url) {
4894 // Create the open with intent with `ACTION_VIEW`.
4895 Intent openWithBrowserIntent = new Intent(Intent.ACTION_VIEW);
4897 // Set the URI and the MIME type. `"text/html"` should load browser options.
4898 openWithBrowserIntent.setDataAndType(Uri.parse(url), "text/html");
4900 // Flag the intent to open in a new task.
4901 openWithBrowserIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
4903 // Show the chooser.
4904 startActivity(openWithBrowserIntent);
4907 private static void checkPinnedMismatch() {
4908 if ((pinnedSslCertificate || pinnedIpAddresses) && !ignorePinnedDomainInformation) {
4909 // Initialize the current SSL certificate variables.
4910 String currentWebsiteIssuedToCName = "";
4911 String currentWebsiteIssuedToOName = "";
4912 String currentWebsiteIssuedToUName = "";
4913 String currentWebsiteIssuedByCName = "";
4914 String currentWebsiteIssuedByOName = "";
4915 String currentWebsiteIssuedByUName = "";
4916 Date currentWebsiteSslStartDate = null;
4917 Date currentWebsiteSslEndDate = null;
4920 // Extract the individual pieces of information from the current website SSL certificate if it is not null.
4921 if (sslCertificate != null) {
4922 currentWebsiteIssuedToCName = sslCertificate.getIssuedTo().getCName();
4923 currentWebsiteIssuedToOName = sslCertificate.getIssuedTo().getOName();
4924 currentWebsiteIssuedToUName = sslCertificate.getIssuedTo().getUName();
4925 currentWebsiteIssuedByCName = sslCertificate.getIssuedBy().getCName();
4926 currentWebsiteIssuedByOName = sslCertificate.getIssuedBy().getOName();
4927 currentWebsiteIssuedByUName = sslCertificate.getIssuedBy().getUName();
4928 currentWebsiteSslStartDate = sslCertificate.getValidNotBeforeDate();
4929 currentWebsiteSslEndDate = sslCertificate.getValidNotAfterDate();
4932 // 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`.
4933 String currentWebsiteSslStartDateString = "";
4934 String currentWebsiteSslEndDateString = "";
4935 String pinnedSslStartDateString = "";
4936 String pinnedSslEndDateString = "";
4938 // Convert the `Dates` to `Strings` if they are not `null`.
4939 if (currentWebsiteSslStartDate != null) {
4940 currentWebsiteSslStartDateString = currentWebsiteSslStartDate.toString();
4943 if (currentWebsiteSslEndDate != null) {
4944 currentWebsiteSslEndDateString = currentWebsiteSslEndDate.toString();
4947 if (pinnedSslStartDate != null) {
4948 pinnedSslStartDateString = pinnedSslStartDate.toString();
4951 if (pinnedSslEndDate != null) {
4952 pinnedSslEndDateString = pinnedSslEndDate.toString();
4955 // Check to see if the pinned information matches the current information.
4956 if ((pinnedIpAddresses && !currentHostIpAddresses.equals(pinnedHostIpAddresses)) || (pinnedSslCertificate && (!currentWebsiteIssuedToCName.equals(pinnedSslIssuedToCName) ||
4957 !currentWebsiteIssuedToOName.equals(pinnedSslIssuedToOName) || !currentWebsiteIssuedToUName.equals(pinnedSslIssuedToUName) ||
4958 !currentWebsiteIssuedByCName.equals(pinnedSslIssuedByCName) || !currentWebsiteIssuedByOName.equals(pinnedSslIssuedByOName) ||
4959 !currentWebsiteIssuedByUName.equals(pinnedSslIssuedByUName) || !currentWebsiteSslStartDateString.equals(pinnedSslStartDateString) ||
4960 !currentWebsiteSslEndDateString.equals(pinnedSslEndDateString)))) {
4962 // Get a handle for the pinned mismatch alert dialog.
4963 DialogFragment pinnedMismatchDialogFragment = PinnedMismatchDialog.displayDialog(pinnedSslCertificate, pinnedIpAddresses);
4965 // Show the pinned mismatch alert dialog.
4966 pinnedMismatchDialogFragment.show(fragmentManager, "Pinned Mismatch");
4971 // This must run asynchronously because it involves a network request. `String` declares the parameters. `Void` does not declare progress units. `String` contains the results.
4972 private static class GetHostIpAddresses extends AsyncTask<String, Void, String> {
4973 // The weak references are used to determine if the activity have disappeared while the AsyncTask is running.
4974 private final WeakReference<Activity> activityWeakReference;
4976 GetHostIpAddresses(Activity activity) {
4977 // Populate the weak references.
4978 activityWeakReference = new WeakReference<>(activity);
4981 // `onPreExecute()` operates on the UI thread.
4983 protected void onPreExecute() {
4984 // Get a handle for the activity.
4985 Activity activity = activityWeakReference.get();
4987 // Abort if the activity is gone.
4988 if ((activity == null) || activity.isFinishing()) {
4992 // Set the getting IP addresses tracker.
4993 gettingIpAddresses = true;
4998 protected String doInBackground(String... domainName) {
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()) {
5004 // Return an empty spannable string builder.
5008 // Initialize an IP address string builder.
5009 StringBuilder ipAddresses = new StringBuilder();
5011 // Get an array with the IP addresses for the host.
5013 // Get an array with all the IP addresses for the domain.
5014 InetAddress[] inetAddressesArray = InetAddress.getAllByName(domainName[0]);
5016 // Add each IP address to the string builder.
5017 for (InetAddress inetAddress : inetAddressesArray) {
5018 if (ipAddresses.length() == 0) { // This is the first IP address.
5019 // Add the IP address to the string builder.
5020 ipAddresses.append(inetAddress.getHostAddress());
5021 } else { // This is not the first IP address.
5022 // Add a line break to the string builder first.
5023 ipAddresses.append("\n");
5025 // Add the IP address to the string builder.
5026 ipAddresses.append(inetAddress.getHostAddress());
5029 } catch (UnknownHostException exception) {
5033 // Return the string.
5034 return ipAddresses.toString();
5037 // `onPostExecute()` operates on the UI thread.
5039 protected void onPostExecute(String ipAddresses) {
5040 // Get a handle for the activity.
5041 Activity activity = activityWeakReference.get();
5043 // Abort if the activity is gone.
5044 if ((activity == null) || activity.isFinishing()) {
5048 // Store the IP addresses.
5049 currentHostIpAddresses = ipAddresses;
5051 if (!urlIsLoading) {
5052 checkPinnedMismatch();
5055 // Reset the getting IP addresses tracker.
5056 gettingIpAddresses = false;