Add import and export of settings. https://redmine.stoutner.com/issues/23
authorSoren Stoutner <soren@stoutner.com>
Thu, 25 Oct 2018 18:48:11 +0000 (11:48 -0700)
committerSoren Stoutner <soren@stoutner.com>
Thu, 25 Oct 2018 18:48:11 +0000 (11:48 -0700)
44 files changed:
.idea/assetWizardSettings.xml
.idea/dictionaries/soren.xml
app/build.gradle
app/src/free/java/com/stoutner/privacybrowser/dialogs/AdConsentDialog.java
app/src/free/java/com/stoutner/privacybrowser/helpers/AdHelper.java
app/src/free/res/layout/main_webview.xml
app/src/free/res/values/strings.xml
app/src/main/AndroidManifest.xml
app/src/main/assets/de/about_licenses_dark.html
app/src/main/assets/de/about_licenses_light.html
app/src/main/assets/en/about_licenses_dark.html
app/src/main/assets/en/about_licenses_light.html
app/src/main/assets/es/about_licenses_dark.html
app/src/main/assets/es/about_licenses_light.html
app/src/main/assets/it/about_licenses_dark.html
app/src/main/assets/it/about_licenses_light.html
app/src/main/assets/ru/about_licenses_dark.html
app/src/main/assets/ru/about_licenses_light.html
app/src/main/assets/shared_images/import_export_dark.png [new file with mode: 0644]
app/src/main/assets/shared_images/import_export_light.png [new file with mode: 0644]
app/src/main/java/com/stoutner/privacybrowser/activities/BookmarksActivity.java
app/src/main/java/com/stoutner/privacybrowser/activities/BookmarksDatabaseViewActivity.java
app/src/main/java/com/stoutner/privacybrowser/activities/DomainsActivity.java
app/src/main/java/com/stoutner/privacybrowser/activities/ImportExportActivity.java [new file with mode: 0644]
app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java
app/src/main/java/com/stoutner/privacybrowser/activities/RequestsActivity.java
app/src/main/java/com/stoutner/privacybrowser/dialogs/DownloadLocationPermissionDialog.java
app/src/main/java/com/stoutner/privacybrowser/dialogs/ImportExportStoragePermissionDialog.java [new file with mode: 0644]
app/src/main/java/com/stoutner/privacybrowser/helpers/BookmarksDatabaseHelper.java
app/src/main/java/com/stoutner/privacybrowser/helpers/DomainsDatabaseHelper.java
app/src/main/java/com/stoutner/privacybrowser/helpers/ImportExportDatabaseHelper.java [new file with mode: 0644]
app/src/main/res/drawable/images_disabled_dark.xml
app/src/main/res/drawable/images_disabled_light.xml
app/src/main/res/drawable/images_enabled_dark.xml
app/src/main/res/drawable/images_enabled_light.xml
app/src/main/res/drawable/import_export_dark.xml [new file with mode: 0644]
app/src/main/res/drawable/import_export_light.xml [new file with mode: 0644]
app/src/main/res/layout/import_export_coordinatorlayout.xml [new file with mode: 0644]
app/src/main/res/layout/requests_coordinatorlayout.xml
app/src/main/res/menu/webview_navigation_menu.xml
app/src/main/res/values/strings.xml
app/src/standard/java/com/stoutner/privacybrowser/helpers/AdHelper.java
build.gradle
gradle/wrapper/gradle-wrapper.properties

index 58477f9467fc9f1e075a6d64417456dbeadc1761..9f20b9ba886d31928ff5c11533ef6d8536fd6daf 100644 (file)
@@ -68,7 +68,7 @@
                                 <PersistentState>
                                   <option name="values">
                                     <map>
-                                      <entry key="url" value="jar:file:/home/soren/Android/android-studio/plugins/android/lib/android.jar!/images/material_design_icons/av/ic_new_releases_black_24dp.xml" />
+                                      <entry key="url" value="jar:file:/home/soren/Android/android-studio/plugins/android/lib/android.jar!/images/material_design_icons/communication/ic_import_export_black_24dp.xml" />
                                     </map>
                                   </option>
                                 </PersistentState>
@@ -78,9 +78,8 @@
                         </option>
                         <option name="values">
                           <map>
-                            <entry key="assetSourceType" value="FILE" />
                             <entry key="autoMirrored" value="true" />
-                            <entry key="outputName" value="bookmarks_light" />
+                            <entry key="outputName" value="import_export" />
                             <entry key="sourceFile" value="$USER_HOME$/ownCloud/Android/Privacy Browser/Icons/Icons/bookmarks_light.svg" />
                           </map>
                         </option>
index 5bed189257fd58586990633402facdb10476ee5e..3a9ec86ae8db4cc11d09686ee958a7817a213b2c 100644 (file)
@@ -54,6 +54,7 @@
       <w>enableformdata</w>
       <w>enablejavascript</w>
       <w>enablethirdpartycookies</w>
+      <w>externalstorage</w>
       <w>exynos</w>
       <w>fanboy</w>
       <w>fanboys</w>
       <w>snackbar</w>
       <w>snackbars</w>
       <w>softkeyboard</w>
+      <w>sqlite</w>
       <w>sslenddate</w>
       <w>sslissuedbycommonname</w>
       <w>sslissuedbyorganization</w>
index 25eacaff377b8a85d5edfcb959b5cfb888fb83cb..88ecb083c9af19bf35a53eb71f219c5494da409c 100644 (file)
@@ -21,7 +21,7 @@ apply plugin: 'com.android.application'
 
 android {
     compileSdkVersion 28
-    buildToolsVersion '28.0.2'
+    buildToolsVersion '28.0.3'
 
     defaultConfig {
         minSdkVersion 19
@@ -71,10 +71,10 @@ android {
 
 dependencies {
     implementation fileTree(include: ['*.jar'], dir: 'libs')
-    implementation 'com.android.support:design:28.0.0-rc02'
+    implementation 'com.android.support:design:28.0.0'
 
     // Only compile Firebase ads for the free flavor.
-    freeImplementation 'com.google.firebase:firebase-ads:15.0.1'
+    freeImplementation 'com.google.firebase:firebase-ads:17.0.0'
 
     // Only compile the consent library for the free flavor.  It is used to comply with the GDPR in Europe.
     freeImplementation 'com.google.android.ads.consent:consent-library:1.0.6'
index 6b771d6a1c40dc31f355e4dc4eb61befac9d897f..8808923a641be83c0591ba8941c00853560e24e2 100644 (file)
@@ -81,7 +81,7 @@ public class AdConsentDialog extends DialogFragment {
             consentInformation.setTagForUnderAgeOfConsent(true);
 
             // Load an ad.
-            AdHelper.loadAd(getActivity().findViewById(R.id.adview), getActivity().getApplicationContext(), getString(R.string.ad_id));
+            AdHelper.loadAd(getActivity().findViewById(R.id.adview), getActivity().getApplicationContext(), getString(R.string.ad_unit_id));
         });
 
         // Return the alert dialog.
index a4f5b038608484ab3404a24609dad5bf83c81579..241120700ae911fe5ab6654e61050d317e121d21 100644 (file)
@@ -40,10 +40,10 @@ import com.stoutner.privacybrowser.dialogs.AdConsentDialog;
 public class AdHelper {
     private static boolean initialized;
 
-    public static void initializeAds (View view, Context applicationContext, FragmentManager fragmentManager, String adId) {
+    public static void initializeAds (View view, Context applicationContext, FragmentManager fragmentManager, String googleAppId, String adUnitId) {
         if (!initialized) {  // This is the first run.
             // Initialize mobile ads.
-            MobileAds.initialize(applicationContext, adId);
+            MobileAds.initialize(applicationContext, googleAppId);
 
             // Store the publisher ID in a string array.
             String[] publisherIds = {"pub-5962503714887045"};
@@ -62,7 +62,7 @@ public class AdHelper {
                         consentInformation.setTagForUnderAgeOfConsent(true);
 
                         // Load an ad.
-                        loadAd(view, applicationContext, adId);
+                        loadAd(view, applicationContext, adUnitId);
                     }
                 }
 
@@ -72,7 +72,7 @@ public class AdHelper {
                     consentInformation.setTagForUnderAgeOfConsent(true);
 
                     // Load an ad.
-                    loadAd(view, applicationContext, adId);
+                    loadAd(view, applicationContext, adUnitId);
                 }
             });
 
@@ -80,11 +80,11 @@ public class AdHelper {
             initialized = true;
         } else {  // Ads have previously been initialized.
             // Load an ad.
-            loadAd(view, applicationContext, adId);
+            loadAd(view, applicationContext, adUnitId);
         }
     }
 
-    public static void loadAd (View view, Context applicationContext, String adId) {
+    public static void loadAd (View view, Context applicationContext, String adUnitId) {
         // Cast the generic view to an AdView.
         AdView adView = (AdView) view;
 
@@ -98,7 +98,7 @@ public class AdHelper {
         // Setup the new AdView.  This is necessary because the size of the banner ad can change on rotate.
         adView = new AdView(applicationContext);
         adView.setAdSize(AdSize.SMART_BANNER);
-        adView.setAdUnitId(adId);
+        adView.setAdUnitId(adUnitId);
         adView.setId(R.id.adview);
         adView.setLayoutParams(adViewLayoutParameters);
 
@@ -111,6 +111,8 @@ public class AdHelper {
 
         // Request a new ad.
         AdRequest adRequest = new AdRequest.Builder().addNetworkExtrasBundle(AdMobAdapter.class, adSettingsBundle).build();
+        // Pixel 2 XL test ads.
+        // AdRequest adRequest = new AdRequest.Builder().addTestDevice("137D42984218CEECDFD11927BB7D6416").addNetworkExtrasBundle(AdMobAdapter.class, adSettingsBundle).build();
         adView.loadAd(adRequest);
     }
 
index fb80533e6b8fbbb706d3cbc2fc33f8efb49f6101..49c7ed83a1058fad0d2a130077a06ec424aea7c4 100644 (file)
@@ -39,7 +39,7 @@
         android:layout_centerHorizontal="true"
         android:layout_alignParentBottom="true"
         ads:adSize="SMART_BANNER"
-        ads:adUnitId="@string/ad_id" >
+        ads:adUnitId="@string/ad_unit_id" >
     </com.google.android.gms.ads.AdView>
 
     <android.support.v4.widget.SwipeRefreshLayout
index f0005d89afdbedd0137043a57bf742d0dce039fe..9aae5fc55dfb199539b0da9d03843477a5aab06b 100644 (file)
     <string name="privacy_browser">Privacy Browser Free</string>
 
     <!-- Ad Control. -->
-    <string name="ad_id" translatable="false">ca-app-pub-5962503714887045/2738552414</string>
+    <string name="google_app_id" translatable="false">ca-app-pub-5962503714887045~2738552414</string>
+    <string name="ad_unit_id" translatable="false">ca-app-pub-5962503714887045/2738552414</string>
 
     <!-- Test Ad Control.
-    <string name="ad_id" translatable="false">ca-app-pub-3940256099942544/6300978111</string>
-    -->
+    <string name="google_app_id" translatable="false">ca-app-pub-3940256099942544~3347511713</string>
+    <string name="ad_unit_id" translatable="false">ca-app-pub-3940256099942544/6300978111</string> -->
 
     <!-- Ad Consent. -->
     <string name="ad_consent_text">Privacy Browser Free displays a banner ad on the bottom of the screen.
index fc0dc95ac758e41933282783ed5204a4132c9a3d..028a7e3b6405e6c7bc918d17dd799dedca49040b 100644 (file)
     <!-- Required to create home screen shortcuts. -->
     <uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT" />
 
-    <!-- Required to save files to the public download folder. -->
+    <!-- Required to import settings from external storage. -->
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+
+    <!-- Required to export settings and save files to public storage. -->
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
 
 
             android:name="android.webkit.WebView.EnableSafeBrowsing"
             android:value="false" />
 
+        <!-- Specify the Application ID used by the ads in the free flavor. -->
+        <meta-data
+            android:name="com.google.android.gms.ads.APPLICATION_ID"
+            android:value="@string/google_app_id" />
+
         <!-- The theme has to be defined here or an ugly title bar is displayed when the app launches.
              `android:configChanges="orientation|screenSize"` makes the activity not reload when the orientation changes, which preserves scroll location in the WebView.
              `android:launchMode="singleTask"` makes the app launch in a new task instead of inside the task of the program that sends it an intent.
             android:persistableMode="persistNever"
             tools:ignore="UnusedAttribute" />
 
+        <!-- `android:configChanges="orientation|screenSize"` makes the activity not reload when the orientation changes.
+             `android:persistableMode="persistNever"` removes Privacy Browser from the recent apps list on a device reboot.
+             `tools:ignore="unusedAttribute"` removes the lint warning that `persistableMode` does not apply to API < 21. -->
+        <activity
+            android:name=".activities.ImportExportActivity"
+            android:label="@string/import_export"
+            android:parentActivityName=".activities.MainWebViewActivity"
+            android:configChanges="orientation|screenSize"
+            android:screenOrientation="fullUser"
+            android:persistableMode="persistNever"
+            tools:ignore="UnusedAttribute" />
+
         <!-- `android:configChanges="orientation|screenSize"` makes the activity not reload when the orientation changes.
              `android:persistableMode="persistNever"` removes Privacy Browser from the recent apps list on a device reboot.
              `tools:ignore="unusedAttribute"` removes the lint warning that `persistableMode` does not apply to API < 21. -->
index 0d1cccb7c1f3e89a28868f8d5e35e73aad604ee7..a2c3052a300fe3f255ed657e48e93dc040f19df5 100644 (file)
         <p><img class="icon" src="../shared_images/home_dark.png"> home.</p>
         <p><img class="icon" src="../shared_images/image_dark.png"> image.</p>
         <p><img class="icon" src="../shared_images/import_contacts_dark.png"> import_contacts.</p>
+        <p><img class="icon" src="../shared_images/import_export_dark.png"> import_export.</p>
         <p><img class="icon" src="../shared_images/important_devices_dark.png"> important_devices.</p>
         <p><img class="icon" src="../shared_images/info_outline_dark.png"> info_outline.</p>
         <p><img class="icon" src="../shared_images/language_dark.png"> language.</p>
index 460cc407a05f419b43fab9450d90e481c560aee6..5e7008d23d2146bbeb4ad37d398a983910fc79e5 100644 (file)
         <p><img class="icon" src="../shared_images/home_light.png"> home.</p>
         <p><img class="icon" src="../shared_images/image_light.png"> image.</p>
         <p><img class="icon" src="../shared_images/import_contacts_light.png"> import_contacts.</p>
+        <p><img class="icon" src="../shared_images/import_export_light.png"> import_export.</p>
         <p><img class="icon" src="../shared_images/important_devices_light.png"> important_devices.</p>
         <p><img class="icon" src="../shared_images/info_outline_light.png"> info_outline.</p>
         <p><img class="icon" src="../shared_images/language_light.png"> language.</p>
index ff72afdd52d508d2e949ae9cf5a30b7fef6ebfe3..453f70117c6e32bf0a47e9c4d502645c7ec74689 100644 (file)
         <p><img class="icon" src="../shared_images/home_dark.png"> home.</p>
         <p><img class="icon" src="../shared_images/image_dark.png"> image.</p>
         <p><img class="icon" src="../shared_images/import_contacts_dark.png"> import_contacts.</p>
+        <p><img class="icon" src="../shared_images/import_export_dark.png"> import_export.</p>
         <p><img class="icon" src="../shared_images/important_devices_dark.png"> important_devices.</p>
         <p><img class="icon" src="../shared_images/info_outline_dark.png"> info_outline.</p>
         <p><img class="icon" src="../shared_images/language_dark.png"> language.</p>
index ba326c75661a80677e9c1a2e5b4f6af1e1534815..63c3af1769c775b79bf3d179e9b2e93dfdea9e9c 100644 (file)
         <p><img class="icon" src="../shared_images/home_light.png"> home.</p>
         <p><img class="icon" src="../shared_images/image_light.png"> image.</p>
         <p><img class="icon" src="../shared_images/import_contacts_light.png"> import_contacts.</p>
+        <p><img class="icon" src="../shared_images/import_export_light.png"> import_export.</p>
         <p><img class="icon" src="../shared_images/important_devices_light.png"> important_devices.</p>
         <p><img class="icon" src="../shared_images/info_outline_light.png"> info_outline.</p>
         <p><img class="icon" src="../shared_images/language_light.png"> language.</p>
index 4c005c08cf540a7ae207b8d2f204d7128328bb50..a85702282ea0be7971ea087f15376bfe9a4bf6a4 100644 (file)
         <p><img class="icon" src="../shared_images/home_dark.png"> home.</p>
         <p><img class="icon" src="../shared_images/image_dark.png"> image.</p>
         <p><img class="icon" src="../shared_images/import_contacts_dark.png"> import_contacts.</p>
+        <p><img class="icon" src="../shared_images/import_export_dark.png"> import_export.</p>
         <p><img class="icon" src="../shared_images/important_devices_dark.png"> important_devices.</p>
         <p><img class="icon" src="../shared_images/info_outline_dark.png"> info_outline.</p>
         <p><img class="icon" src="../shared_images/language_dark.png"> language.</p>
index 3a70f4591b125a52703aecb3ebd790645e45a80f..5a8eadb99cef7a2f5943660c7a2c041688fad79b 100644 (file)
         <p><img class="icon" src="../shared_images/home_light.png"> home.</p>
         <p><img class="icon" src="../shared_images/image_light.png"> image.</p>
         <p><img class="icon" src="../shared_images/import_contacts_light.png"> import_contacts.</p>
+        <p><img class="icon" src="../shared_images/import_export_light.png"> import_export.</p>
         <p><img class="icon" src="../shared_images/important_devices_light.png"> important_devices.</p>
         <p><img class="icon" src="../shared_images/info_outline_light.png"> info_outline.</p>
         <p><img class="icon" src="../shared_images/language_light.png"> language.</p>
index 2cba6359722525927b9686306d9eab73c136fa4c..bffcdc26bec26bae2d90493d9d0fdc317b9699f5 100644 (file)
         <p><img class="icon" src="../shared_images/home_dark.png"> home.</p>
         <p><img class="icon" src="../shared_images/image_dark.png"> image.</p>
         <p><img class="icon" src="../shared_images/import_contacts_dark.png"> import_contacts.</p>
+        <p><img class="icon" src="../shared_images/import_export_dark.png"> import_export.</p>
         <p><img class="icon" src="../shared_images/important_devices_dark.png"> important_devices.</p>
         <p><img class="icon" src="../shared_images/info_outline_dark.png"> info_outline.</p>
         <p><img class="icon" src="../shared_images/language_dark.png"> language.</p>
index dcc575cf32a710dc4500eaa60dc708002bead022..f8cf92dc9b8d4f272966f19fd9ebc6a0f48b1d2b 100644 (file)
         <p><img class="icon" src="../shared_images/home_light.png"> home.</p>
         <p><img class="icon" src="../shared_images/image_light.png"> image.</p>
         <p><img class="icon" src="../shared_images/import_contacts_light.png"> import_contacts.</p>
+        <p><img class="icon" src="../shared_images/import_export_light.png"> import_export.</p>
         <p><img class="icon" src="../shared_images/important_devices_light.png"> important_devices.</p>
         <p><img class="icon" src="../shared_images/info_outline_light.png"> info_outline.</p>
         <p><img class="icon" src="../shared_images/language_light.png"> language.</p>
index e2819da5f64753534441b08ac93ccb1a628cb775..b5fe7ae5459b7303c117adb245fd0d5c28ce5578 100644 (file)
         <p><img class="icon" src="../shared_images/home_dark.png"> home.</p>
         <p><img class="icon" src="../shared_images/image_dark.png"> image.</p>
         <p><img class="icon" src="../shared_images/import_contacts_dark.png"> import_contacts.</p>
+        <p><img class="icon" src="../shared_images/import_export_dark.png"> import_export.</p>
         <p><img class="icon" src="../shared_images/important_devices_dark.png"> important_devices.</p>
         <p><img class="icon" src="../shared_images/info_outline_dark.png"> info_outline.</p>
         <p><img class="icon" src="../shared_images/language_dark.png"> language.</p>
index 6a419a621e17e1a0b526dea0acc09ca181240290..264f0d520f82f9e3df878241ffe40f3f16552cd0 100644 (file)
         <p><img class="icon" src="../shared_images/home_light.png"> home.</p>
         <p><img class="icon" src="../shared_images/image_light.png"> image.</p>
         <p><img class="icon" src="../shared_images/import_contacts_light.png"> import_contacts.</p>
+        <p><img class="icon" src="../shared_images/import_export_light.png"> import_export.</p>
         <p><img class="icon" src="../shared_images/important_devices_light.png"> important_devices.</p>
         <p><img class="icon" src="../shared_images/info_outline_light.png"> info_outline.</p>
         <p><img class="icon" src="../shared_images/language_light.png"> language.</p>
diff --git a/app/src/main/assets/shared_images/import_export_dark.png b/app/src/main/assets/shared_images/import_export_dark.png
new file mode 100644 (file)
index 0000000..aae1e03
Binary files /dev/null and b/app/src/main/assets/shared_images/import_export_dark.png differ
diff --git a/app/src/main/assets/shared_images/import_export_light.png b/app/src/main/assets/shared_images/import_export_light.png
new file mode 100644 (file)
index 0000000..2b70cd7
Binary files /dev/null and b/app/src/main/assets/shared_images/import_export_light.png differ
index d556b305c11c6778a814eb599285d646791446ce..d48fe2b32aac1f2964ef47f03b20ecf8168bb56d 100644 (file)
@@ -66,22 +66,25 @@ public class BookmarksActivity extends AppCompatActivity implements CreateBookma
         EditBookmarkFolderDialog.EditBookmarkFolderListener, MoveToFolderDialog.MoveToFolderListener {
 
     // `currentFolder` is public static so it can be accessed from `MoveToFolderDialog`.
-    // It is used in `onCreate`, `onOptionsItemSelected()`, `onBackPressed()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onSaveBookmark()`, `onSaveBookmarkFolder()`, `onMoveToFolder()`, and `loadFolder()`.
+    // It is used in `onCreate`, `onOptionsItemSelected()`, `onBackPressed()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onSaveBookmark()`, `onSaveBookmarkFolder()`, `onMoveToFolder()`,
+    // and `loadFolder()`.
     public static String currentFolder;
 
-    // `checkedItemIds` is public static so it can be accessed from `MoveToFolderDialog`.  It is also used in `onCreate()`, `onSaveBookmark()`, `onSaveBookmarkFolder()`, `onMoveToFolder()`, and `updateMoveIcons()`.
+    // `checkedItemIds` is public static so it can be accessed from `MoveToFolderDialog`.  It is also used in `onCreate()`, `onSaveBookmark()`, `onSaveBookmarkFolder()`, `onMoveToFolder()`,
+    // and `updateMoveIcons()`.
     public static long[] checkedItemIds;
 
 
-    // `bookmarksDatabaseHelper` is used in `onCreate()`, `onOptionsItemSelected()`, `onBackPressed()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onSaveBookmark()`, `onSaveBookmarkFolder()`, `onMoveToFolder()`, `deleteBookmarkFolderContents()`,
-    // and `loadFolder().
+    // `bookmarksDatabaseHelper` is used in `onCreate()`, `onOptionsItemSelected()`, `onBackPressed()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onSaveBookmark()`, `onSaveBookmarkFolder()`,
+    // `onMoveToFolder()`, `deleteBookmarkFolderContents()`, `loadFolder()`, and `onDestroy()`.
     private BookmarksDatabaseHelper bookmarksDatabaseHelper;
 
-    // `bookmarksListView` is used in `onCreate()`, `onOptionsItemSelected()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onSaveBookmark()`, `onSaveBookmarkFolder()`, `onMoveToFolder()`, `updateMoveIcons()`, `scrollBookmarks()`,
-    // and `loadFolder()`.
+    // `bookmarksListView` is used in `onCreate()`, `onOptionsItemSelected()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onSaveBookmark()`, `onSaveBookmarkFolder()`, `onMoveToFolder()`,
+    // `updateMoveIcons()`, `scrollBookmarks()`, and `loadFolder()`.
     private ListView bookmarksListView;
 
-    // `bookmarksCursor` is used in `onCreate()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onSaveBookmark()`, `onSaveBookmarkFolder()`, `onMoveToFolder()`, `deleteBookmarkFolderContents()`, and `loadFolder()`.
+    // `bookmarksCursor` is used in `onCreate()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onSaveBookmark()`, `onSaveBookmarkFolder()`, `onMoveToFolder()`, `deleteBookmarkFolderContents()`,
+    // `loadFolder()`, and `onDestroy()`.
     private Cursor bookmarksCursor;
 
     // `bookmarksCursorAdapter` is used in `onCreate(), `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onSaveBookmark()`, `onSaveBookmarkFolder()`, `onMoveToFolder()`, and `onLoadFolder()`.
@@ -1072,4 +1075,14 @@ public class BookmarksActivity extends AppCompatActivity implements CreateBookma
             appBar.setTitle(currentFolder);
         }
     }
+
+    @Override
+    public void onDestroy() {
+        // Close the bookmarks cursor and database.
+        bookmarksCursor.close();
+        bookmarksDatabaseHelper.close();
+
+        // Run the default commands.
+        super.onDestroy();
+    }
 }
\ No newline at end of file
index 006af07a94c36b590ce86ee7b72d265774f3e5f0..620715acd7d2741e6ad8c567d037ace6d6257a8c 100644 (file)
@@ -60,10 +60,10 @@ public class BookmarksDatabaseViewActivity extends AppCompatActivity implements
     private static final int ALL_FOLDERS_DATABASE_ID = -2;
     private static final int HOME_FOLDER_DATABASE_ID = -1;
 
-    // `bookmarksDatabaseHelper` is used in `onCreate()` and `updateBookmarksListView()`.
+    // `bookmarksDatabaseHelper` is used in `onCreate()`, `updateBookmarksListView()`, and `onDestroy()`.
     private BookmarksDatabaseHelper bookmarksDatabaseHelper;
 
-    // `bookmarksCursor` is used in `onCreate()`, `updateBookmarksListView()`, `onSaveBookmark()`, and `onSaveBookmarkFolder()`.
+    // `bookmarksCursor` is used in `onCreate()`, `updateBookmarksListView()`, `onSaveBookmark()`, `onSaveBookmarkFolder()`, and `onDestroy()`.
     private Cursor bookmarksCursor;
 
     // `bookmarksCursorAdapter` is used in `onCreate()`, `onSaveBookmark()`, `onSaveBookmarkFolder()`.
@@ -435,4 +435,14 @@ public class BookmarksDatabaseViewActivity extends AppCompatActivity implements
         // Update the `ListView`.
         bookmarksCursorAdapter.changeCursor(bookmarksCursor);
     }
+
+    @Override
+    public void onDestroy() {
+        // Close the bookmarks cursor and database.
+        bookmarksCursor.close();
+        bookmarksDatabaseHelper.close();
+
+        // Run the default commands.
+        super.onDestroy();
+    }
 }
\ No newline at end of file
index 479b47a7332526c30e6d8440f51a7bb06bfb1c38..1d9a62a6aadd858273001888b6c064a0c80315fa 100644 (file)
@@ -55,6 +55,8 @@ import com.stoutner.privacybrowser.fragments.DomainSettingsFragment;
 import com.stoutner.privacybrowser.fragments.DomainsListFragment;
 import com.stoutner.privacybrowser.helpers.DomainsDatabaseHelper;
 
+import java.util.Objects;
+
 public class DomainsActivity extends AppCompatActivity implements AddDomainDialog.AddDomainListener {
     // `twoPanedMode` is public static so it can be accessed from `DomainsListFragment`.  It is also used in `onCreate()`, `onCreateOptionsMenu()`, and `populateDomainsListView()`.
     public static boolean twoPanedMode;
@@ -80,7 +82,7 @@ public class DomainsActivity extends AppCompatActivity implements AddDomainDialo
     // `supportFragmentManager` is used in `onCreate()` and `onCreateOptionsMenu()`.
     private FragmentManager supportFragmentManager;
 
-    // `domainsDatabaseHelper` is used in `onCreate()` and `saveDomainSettings()`.
+    // `domainsDatabaseHelper` is used in `onCreate()`, `saveDomainSettings()`, and `onDestroy()`.
     private static DomainsDatabaseHelper domainsDatabaseHelper;
 
     // `domainsListView` is used in `onCreate()` and `populateDomainsList()`.
@@ -159,12 +161,12 @@ public class DomainsActivity extends AppCompatActivity implements AddDomainDialo
         final Toolbar domainsAppBar = findViewById(R.id.domains_toolbar);
         setSupportActionBar(domainsAppBar);
 
-        // Display the home arrow on `SupportActionBar`.
+        // Display the home arrow on the support action bar.
         ActionBar appBar = getSupportActionBar();
         assert appBar != null;// This assert removes the incorrect warning in Android Studio on the following line that `appBar` might be null.
         appBar.setDisplayHomeAsUpEnabled(true);
 
-        // Initialize the database handler.  The two `nulls` do not specify the database name or a `CursorFactory`.  The `0` specifies the database version, but that is ignored and set instead using a constant in `DomainsDatabaseHelper`.
+        // Initialize the database handler.  The `0` specifies the database version, but that is ignored and set instead using a constant in `DomainsDatabaseHelper`.
         domainsDatabaseHelper = new DomainsDatabaseHelper(context, null, null, 0);
 
         // Determine if we are in two pane mode.  `domain_settings_fragment_container` does not exist on devices with a width less than 900dp.
@@ -352,7 +354,7 @@ public class DomainsActivity extends AppCompatActivity implements AddDomainDialo
                     deleteMenuItem.setIcon(R.drawable.delete_blue);
 
                     // Remove the domain settings fragment.
-                    supportFragmentManager.beginTransaction().remove(supportFragmentManager.findFragmentById(R.id.domain_settings_fragment_container)).commit();
+                    supportFragmentManager.beginTransaction().remove(Objects.requireNonNull(supportFragmentManager.findFragmentById(R.id.domain_settings_fragment_container))).commit();
                 } else {  // Single-paned mode.
                     // Display `DomainsListFragment`.
                     DomainsListFragment domainsListFragment = new DomainsListFragment();
@@ -813,4 +815,13 @@ public class DomainsActivity extends AppCompatActivity implements AddDomainDialo
             deleteMenuItem.setIcon(R.drawable.delete_blue);
         }
     }
+
+    @Override
+    public void onDestroy() {
+        // Close the domains database helper.
+        domainsDatabaseHelper.close();
+
+        // Run the default commands.
+        super.onDestroy();
+    }
 }
\ No newline at end of file
diff --git a/app/src/main/java/com/stoutner/privacybrowser/activities/ImportExportActivity.java b/app/src/main/java/com/stoutner/privacybrowser/activities/ImportExportActivity.java
new file mode 100644 (file)
index 0000000..705a662
--- /dev/null
@@ -0,0 +1,490 @@
+/*
+ * Copyright © 2018 Soren Stoutner <soren@stoutner.com>.
+ *
+ * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
+ *
+ * Privacy Browser is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Privacy Browser is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Privacy Browser.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package com.stoutner.privacybrowser.activities;
+
+import android.Manifest;
+import android.app.Activity;
+import android.app.DialogFragment;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Environment;
+import android.provider.DocumentsContract;
+import android.support.annotation.NonNull;
+import android.support.design.widget.Snackbar;
+import android.support.v4.app.ActivityCompat;
+import android.support.v4.content.ContextCompat;
+import android.support.v7.app.ActionBar;
+import android.support.v7.app.AppCompatActivity;
+import android.support.v7.widget.Toolbar;
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.TextView;
+
+import com.stoutner.privacybrowser.R;
+import com.stoutner.privacybrowser.dialogs.ImportExportStoragePermissionDialog;
+import com.stoutner.privacybrowser.helpers.ImportExportDatabaseHelper;
+
+import java.io.File;
+
+public class ImportExportActivity extends AppCompatActivity implements ImportExportStoragePermissionDialog.ImportExportStoragePermissionDialogListener {
+    private final static int EXPORT_FILE_PICKER_REQUEST_CODE = 1;
+    private final static int IMPORT_FILE_PICKER_REQUEST_CODE = 2;
+    private final static int EXPORT_REQUEST_CODE = 3;
+    private final static int IMPORT_REQUEST_CODE = 4;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        // Disable screenshots if not allowed.
+        if (!MainWebViewActivity.allowScreenshots) {
+            getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE);
+        }
+
+        // Set the activity theme.
+        if (MainWebViewActivity.darkTheme) {
+            setTheme(R.style.PrivacyBrowserDark_SecondaryActivity);
+        } else {
+            setTheme(R.style.PrivacyBrowserLight_SecondaryActivity);
+        }
+
+        // Run the default commands.
+        super.onCreate(savedInstanceState);
+
+        // Set the content view.
+        setContentView(R.layout.import_export_coordinatorlayout);
+
+        // Use the `SupportActionBar` from `android.support.v7.app.ActionBar` until the minimum API is >= 21.
+        Toolbar importExportAppBar = findViewById(R.id.import_export_toolbar);
+        setSupportActionBar(importExportAppBar);
+
+        // Display the home arrow on the support action bar.
+        ActionBar appBar = getSupportActionBar();
+        assert appBar != null;// This assert removes the incorrect warning in Android Studio on the following line that `appBar` might be null.
+        appBar.setDisplayHomeAsUpEnabled(true);
+
+        // Get handles for the views that need to be modified.
+        EditText exportFileEditText = findViewById(R.id.export_file_edittext);
+        Button exportButton = findViewById(R.id.export_button);
+        EditText importFileEditText = findViewById(R.id.import_file_edittext);
+        Button importButton = findViewById(R.id.import_button);
+        TextView storagePermissionTextView = findViewById(R.id.import_export_storage_permission_textview);
+
+        // Initially disable the buttons.
+        exportButton.setEnabled(false);
+        importButton.setEnabled(false);
+
+        // Enable the export button when the export file EditText isn't empty.
+        exportFileEditText.addTextChangedListener(new TextWatcher() {
+            @Override
+            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+                // Do nothing.
+            }
+
+            @Override
+            public void onTextChanged(CharSequence s, int start, int before, int count) {
+                // Do nothing.
+            }
+
+            @Override
+            public void afterTextChanged(Editable s) {
+                exportButton.setEnabled(!exportFileEditText.getText().toString().isEmpty());
+            }
+        });
+
+        // Enable the import button when the export file EditText isn't empty.
+        importFileEditText.addTextChangedListener(new TextWatcher() {
+            @Override
+            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+                // Do nothing.
+            }
+
+            @Override
+            public void onTextChanged(CharSequence s, int start, int before, int count) {
+                // Do nothing.
+            }
+
+            @Override
+            public void afterTextChanged(Editable s) {
+                importButton.setEnabled(!importFileEditText.getText().toString().isEmpty());
+            }
+        });
+
+        // Set the default download file path if the storage permission has not been granted.
+        if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) {
+            // Create a string for the external private path.
+            String EXTERNAL_PRIVATE_PATH = getApplicationContext().getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS) + "/Privacy Browser Backup";
+
+            // Set the default path.
+            exportFileEditText.setText(EXTERNAL_PRIVATE_PATH);
+            importFileEditText.setText(EXTERNAL_PRIVATE_PATH);
+        }
+
+        // Hide the storage permissions TextView on API < 23 as permissions on older devices are automatically granted.
+        if (Build.VERSION.SDK_INT < 23) {
+            storagePermissionTextView.setVisibility(View.GONE);
+        }
+    }
+
+    public void exportBrowse(View view) {
+        // Create the file picker intent.
+        Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
+
+        // Set the intent MIME type to include all files.
+        intent.setType("*/*");
+
+        // Set the initial export file name.
+        intent.putExtra(Intent.EXTRA_TITLE, getString(R.string.privacy_browser_backup));
+
+        // Set the initial directory if API >= 26.
+        if (Build.VERSION.SDK_INT >= 26) {
+            intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, Environment.getExternalStorageDirectory());
+        }
+
+        // Specify that a file that can be opened is requested.
+        intent.addCategory(Intent.CATEGORY_OPENABLE);
+
+        // Launch the file picker.
+        startActivityForResult(intent, EXPORT_FILE_PICKER_REQUEST_CODE);
+    }
+
+    public void onClickExport(View view) {
+        // Check to see if the storage permission has been granted.
+        if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {  // Storage permission granted.
+            // Export the settings.
+            exportSettings();
+        } else {  // Storage permission not granted.
+            // Get a handle for the export file EditText.
+            EditText exportFileEditText = findViewById(R.id.export_file_edittext);
+
+            // Get the export file string.
+            String exportFileString = exportFileEditText.getText().toString();
+
+            // Get the external private directory `File`.
+            File externalPrivateDirectoryFile = getApplicationContext().getExternalFilesDir(null);
+
+            // Remove the lint error below that the `File` might be null.
+            assert externalPrivateDirectoryFile != null;
+
+            // Get the external private directory string.
+            String externalPrivateDirectory = externalPrivateDirectoryFile.toString();
+
+            // Check to see if the export file path is in the external private directory.
+            if (exportFileString.startsWith(externalPrivateDirectory)) {  // The export path is in the external private directory.
+                // Export the settings.
+                exportSettings();
+            } else {  // The export path is in a public directory.
+                // Check if the user has previously denied the storage permission.
+                if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {  // Show a dialog explaining the request first.
+                    // Instantiate the storage permission alert dialog and set the type to EXPORT_SETTINGS.
+                    DialogFragment importExportStoragePermissionDialogFragment = ImportExportStoragePermissionDialog.type(ImportExportStoragePermissionDialog.EXPORT_SETTINGS);
+
+                    // Show the storage permission alert dialog.  The permission will be requested when the dialog is closed.
+                    importExportStoragePermissionDialogFragment.show(getFragmentManager(), getString(R.string.storage_permission));
+                } else {  // Show the permission request directly.
+                    // Request the storage permission.  The export will be run when it finishes.
+                    ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, EXPORT_REQUEST_CODE);
+                }
+            }
+        }
+    }
+
+    public void importBrowse(View view) {
+        // Create the file picker intent.
+        Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
+
+        // Set the intent MIME type to include all files.
+        intent.setType("*/*");
+
+        // Set the initial directory if API >= 26.
+        if (Build.VERSION.SDK_INT >= 26) {
+            intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, Environment.getExternalStorageDirectory());
+        }
+
+        // Specify that a file that can be opened is requested.
+        intent.addCategory(Intent.CATEGORY_OPENABLE);
+
+        // Launch the file picker.
+        startActivityForResult(intent, IMPORT_FILE_PICKER_REQUEST_CODE);
+    }
+
+    public void onClickImport(View view) {
+        // Check to see if the storage permission has been granted.
+        if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {  // Storage permission granted.
+            // Import the settings.
+            importSettings();
+        } else {  // Storage permission not granted.
+            // Get a handle for the import file EditText.
+            EditText importFileEditText = findViewById(R.id.import_file_edittext);
+
+            // Get the import file string.
+            String importFileString = importFileEditText.getText().toString();
+
+            // Get the external private directory `File`.
+            File externalPrivateDirectoryFile = getApplicationContext().getExternalFilesDir(null);
+
+            // Remove the lint error below that `File` might be null.
+            assert externalPrivateDirectoryFile != null;
+
+            // Get the external private directory string.
+            String externalPrivateDirectory = externalPrivateDirectoryFile.toString();
+
+            // Check to see if the import file path is in the external private directory.
+            if (importFileString.startsWith(externalPrivateDirectory)) {  // The import path is in the external private directory.
+                // Import the settings.
+                importSettings();
+            } else {  // The import path is in a public directory.
+                // Check if the user has previously denied the storage permission.
+                if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.READ_EXTERNAL_STORAGE)) {  // Show a dialog explaining the request first.
+                    // Instantiate the storage permission alert dialog and set the type to IMPORT_SETTINGS.
+                    DialogFragment importExportStoragePermissionDialogFragment = ImportExportStoragePermissionDialog.type(ImportExportStoragePermissionDialog.IMPORT_SETTINGS);
+
+                    // Show the storage permission alert dialog.  The permission will be requested when the dialog is closed.
+                    importExportStoragePermissionDialogFragment.show(getFragmentManager(), getString(R.string.storage_permission));
+                } else {  // Show the permission request directly.
+                    // Request the storage permission.  The export will be run when it finishes.
+                    ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.READ_EXTERNAL_STORAGE}, IMPORT_REQUEST_CODE);
+                }
+            }
+        }
+    }
+
+    @Override
+    public void onActivityResult(int requestCode, int resultCode, Intent data) {
+        // Don't do anything if the user pressed back from the file picker.
+        if (resultCode == Activity.RESULT_OK) {
+            // Run the commands for the specific request code.
+            switch (requestCode) {
+                case EXPORT_FILE_PICKER_REQUEST_CODE:
+                    // Get a handle for the export file EditText.
+                    EditText exportFileEditText = findViewById(R.id.export_file_edittext);
+
+                    // Get the selected export file.
+                    Uri exportUri = data.getData();
+
+                    // Remove the lint warning that the export URI might be null.
+                    assert exportUri != null;
+
+                    // Get the raw export path.
+                    String rawExportPath = exportUri.getPath();
+
+                    // Remove the warning that the raw export path might be null.
+                    assert rawExportPath != null;
+
+                    // Check to see if the rawExportPath includes a valid storage location.
+                    if (rawExportPath.contains(":")) {  // The path is valid.
+                        // Split the path into the initial content uri and the path information.
+                        String exportContentPath = rawExportPath.substring(0, rawExportPath.indexOf(":"));
+                        String exportFilePath = rawExportPath.substring(rawExportPath.indexOf(":") + 1);
+
+                        // Create the export path string.
+                        String exportPath;
+
+                        // Construct the export path.
+                        switch (exportContentPath) {
+                            // The documents home has a special content path.
+                            case "/document/home":
+                                exportPath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS) + "/" + exportFilePath;
+                                break;
+
+                            // Everything else for the primary user should be in `/document/primary`.
+                            case "/document/primary":
+                                exportPath = Environment.getExternalStorageDirectory() + "/" + exportFilePath;
+                                break;
+
+                            // Just in case, catch everything else and place it in the external storage directory.
+                            default:
+                                exportPath = Environment.getExternalStorageDirectory() + "/" + exportFilePath;
+                                break;
+                        }
+
+                        // Set the export file URI as the text for the export file EditText.
+                        exportFileEditText.setText(exportPath);
+                    } else {  // The path is invalid.
+                        Snackbar.make(exportFileEditText, rawExportPath + " + " + getString(R.string.invalid_location), Snackbar.LENGTH_INDEFINITE).show();
+                    }
+                    break;
+
+                case IMPORT_FILE_PICKER_REQUEST_CODE:
+                    // Get a handle for the import file EditText.
+                    EditText importFileEditText = findViewById(R.id.import_file_edittext);
+
+                    // Get the selected import file.
+                    Uri importUri = data.getData();
+
+                    // Remove the lint warning that the import URI might be null.
+                    assert importUri != null;
+
+                    // Get the raw import path.
+                    String rawImportPath = importUri.getPath();
+
+                    // Remove the warning that the raw import path might be null.
+                    assert rawImportPath != null;
+
+                    // Check to see if the rawExportPath includes a valid storage location.
+                    if (rawImportPath.contains(":")) {  // The path is valid.
+                        // Split the path into the initial content uri and the path information.
+                        String importContentPath = rawImportPath.substring(0, rawImportPath.indexOf(":"));
+                        String importFilePath = rawImportPath.substring(rawImportPath.indexOf(":") + 1);
+
+                        // Create the export path string.
+                        String importPath;
+
+                        // Construct the export path.
+                        switch (importContentPath) {
+                            // The documents folder has a special content path.
+                            case "/document/home":
+                                importPath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS) + "/" + importFilePath;
+                                break;
+
+                            // Everything else for the primary user should be in `/document/primary`.
+                            case "/document/primary":
+                                importPath = Environment.getExternalStorageDirectory() + "/" + importFilePath;
+                                break;
+
+                            // Just in case, catch everything else and place it in the external storage directory.
+                            default:
+                                importPath = Environment.getExternalStorageDirectory() + "/" + importFilePath;
+                                break;
+                        }
+
+                        // Set the export file URI as the text for the export file EditText.
+                        importFileEditText.setText(importPath);
+                    } else {  // The path is invalid.
+                        Snackbar.make(importFileEditText, rawImportPath + " + " + getString(R.string.invalid_location), Snackbar.LENGTH_INDEFINITE).show();
+                    }
+                    break;
+            }
+        }
+    }
+
+    @Override
+    public void onCloseImportExportStoragePermissionDialog(int type) {
+        // Request the storage permission based on the button that was pressed.
+        switch (type) {
+            case ImportExportStoragePermissionDialog.EXPORT_SETTINGS:
+                // Request the storage permission.  The export will be run when it finishes.
+                ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, EXPORT_REQUEST_CODE);
+                break;
+
+            case ImportExportStoragePermissionDialog.IMPORT_SETTINGS:
+                // Request the storage permission.  The import will be run when it finishes.
+                ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.READ_EXTERNAL_STORAGE}, IMPORT_REQUEST_CODE);
+                break;
+        }
+    }
+
+    @Override
+    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
+        switch (requestCode) {
+            case EXPORT_REQUEST_CODE:
+                // Check to see if the storage permission was granted.  If the dialog was canceled the grant results will be empty.
+                if ((grantResults.length > 0) && (grantResults[0] == PackageManager.PERMISSION_GRANTED)) {  // The storage permission was granted.
+                    // Export the settings.
+                    exportSettings();
+                } else {  // The storage permission was not granted.
+                    // Get a handle for the export file EditText.
+                    EditText exportFileEditText = findViewById(R.id.export_file_edittext);
+
+                    // Display an error snackbar.
+                    Snackbar.make(exportFileEditText, getString(R.string.cannot_export), Snackbar.LENGTH_LONG).show();
+                }
+                break;
+
+            case IMPORT_REQUEST_CODE:
+                // Check to see if the storage permission was granted.  If the dialog was canceled the grant results will be empty.
+                if ((grantResults.length > 0) && (grantResults[0] == PackageManager.PERMISSION_GRANTED)) {  // The storage permission was granted.
+                    // Import the settings.
+                    importSettings();
+                } else {  // The storage permission was not granted.
+                    // Get a handle for the import file EditText.
+                    EditText importFileEditText = findViewById(R.id.import_file_edittext);
+
+                    // Display an error snackbar.
+                    Snackbar.make(importFileEditText, getString(R.string.cannot_import), Snackbar.LENGTH_LONG).show();
+                }
+                break;
+        }
+    }
+
+    private void exportSettings() {
+        // Get a handle for the export file EditText.
+        EditText exportFileEditText = findViewById(R.id.export_file_edittext);
+
+        // Get the export file string.
+        String exportFileString = exportFileEditText.getText().toString();
+
+        // Set the export file.
+        File exportFile = new File(exportFileString);
+
+        // Instantiate the import export database helper.
+        ImportExportDatabaseHelper importExportDatabaseHelper = new ImportExportDatabaseHelper();
+
+        // Export the unencrypted file.
+        String exportStatus = importExportDatabaseHelper.exportUnencrypted(exportFile, getApplicationContext());
+
+        // Show a disposition snackbar.
+        if (exportStatus.equals(ImportExportDatabaseHelper.EXPORT_SUCCESSFUL)) {
+            Snackbar.make(exportFileEditText, getString(R.string.export_successful), Snackbar.LENGTH_SHORT).show();
+        } else {
+            Snackbar.make(exportFileEditText, getString(R.string.export_failed) + "  " + exportStatus, Snackbar.LENGTH_INDEFINITE).show();
+        }
+    }
+
+    private void importSettings() {
+        // Get a handle for the import file EditText.
+        EditText importFileEditText = findViewById(R.id.import_file_edittext);
+
+        // Get the import file string.
+        String importFileString = importFileEditText.getText().toString();
+
+        // Set the import file.
+        File importFile = new File(importFileString);
+
+        // Instantiate the import export database helper.
+        ImportExportDatabaseHelper importExportDatabaseHelper = new ImportExportDatabaseHelper();
+
+        // Import the unencrypted file.
+        String importStatus = importExportDatabaseHelper.importUnencrypted(importFile, getApplicationContext());
+
+        // Respond to the import disposition.
+        if (importStatus.equals(ImportExportDatabaseHelper.IMPORT_SUCCESSFUL)) {  // The import was successful.
+            // Create an intent to restart Privacy Browser.
+            Intent restartIntent = getParentActivityIntent();
+
+            // Assert that the intent is not null to remove the lint error below.
+            assert restartIntent != null;
+
+            // `Intent.FLAG_ACTIVITY_CLEAR_TASK` removes all activities from the stack.  It requires `Intent.FLAG_ACTIVITY_NEW_TASK`.
+            restartIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+
+            // Make it so.
+            startActivity(restartIntent);
+        } else {  // The import was not successful.
+            // Display a snack bar with the import error.
+            Snackbar.make(importFileEditText, getString(R.string.import_failed) + "  " + importStatus, Snackbar.LENGTH_INDEFINITE).show();
+        }
+    }
+}
\ No newline at end of file
index 230fc499c49556bc84444cbc2d916ddfde0cc0a1..f2a422a27b781649e0ce00618ce62ce1addb8ee9 100644 (file)
@@ -202,13 +202,13 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
     // The request items are public static so they can be accessed by `BlockListHelper`, `RequestsArrayAdapter`, and `ViewRequestsDialog`.  They are also used in `onCreate()` and `onPrepareOptionsMenu()`.
     public static List<String[]> resourceRequests;
     public static String[] whiteListResultStringArray;
-    int blockedRequests;
-    int easyListBlockedRequests;
-    int easyPrivacyBlockedRequests;
-    int fanboysAnnoyanceListBlockedRequests;
-    int fanboysSocialBlockingListBlockedRequests;
-    int ultraPrivacyBlockedRequests;
-    int thirdPartyBlockedRequests;
+    private int blockedRequests;
+    private int easyListBlockedRequests;
+    private int easyPrivacyBlockedRequests;
+    private int fanboysAnnoyanceListBlockedRequests;
+    private int fanboysSocialBlockingListBlockedRequests;
+    private int ultraPrivacyBlockedRequests;
+    private int thirdPartyBlockedRequests;
 
     public final static int REQUEST_DISPOSITION = 0;
     public final static int REQUEST_URL = 1;
@@ -353,7 +353,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
     private MenuItem fanboysAnnoyanceListMenuItem;
     private MenuItem fanboysSocialBlockingListMenuItem;
     private MenuItem ultraPrivacyMenuItem;
-    private MenuItem blockAllThirdParyRequestsMenuItem;
+    private MenuItem blockAllThirdPartyRequestsMenuItem;
 
     // The blocklist variables are used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, and `applyAppSettings()`.
     private boolean easyListEnabled;
@@ -404,7 +404,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
     // `ignorePinnedSslCertificateForDomain` is used in `onCreate()`, `onSslMismatchProceed()`, and `applyDomainSettings()`.
     private boolean ignorePinnedSslCertificate;
 
-    // `orbotStatusBroadcastReciever` is used in `onCreate()` and `onDestroy()`.
+    // `orbotStatusBroadcastReceiver` is used in `onCreate()` and `onDestroy()`.
     private BroadcastReceiver orbotStatusBroadcastReceiver;
 
     // `waitingForOrbot` is used in `onCreate()`, `onResume()`, and `applyAppSettings()`.
@@ -469,7 +469,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
     // `pinnedDomainSslCertificate` is used in `onCreate()` and `applyDomainSettings()`.
     private boolean pinnedDomainSslCertificate;
 
-    // `bookmarksDatabaseHelper` is used in `onCreate()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and `loadBookmarksFolder()`.
+    // `bookmarksDatabaseHelper` is used in `onCreate()`, `onDestroy`, `onOptionsItemSelected()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`,
+    // and `loadBookmarksFolder()`.
     private BookmarksDatabaseHelper bookmarksDatabaseHelper;
 
     // `bookmarksListView` is used in `onCreate()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, and `loadBookmarksFolder()`.
@@ -478,7 +479,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
     // `bookmarksTitleTextView` is used in `onCreate()` and `loadBookmarksFolder()`.
     private TextView bookmarksTitleTextView;
 
-    // `bookmarksCursor` is used in `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and `loadBookmarksFolder()`.
+    // `bookmarksCursor` is used in `onDestroy()`, `onOptionsItemSelected()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and `loadBookmarksFolder()`.
     private Cursor bookmarksCursor;
 
     // `bookmarksCursorAdapter` is used in `onCreateBookmark()`, `onCreateBookmarkFolder()` `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and `loadBookmarksFolder()`.
@@ -728,7 +729,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                         // Show the `BannerAd` in the free flavor.
                         if (BuildConfig.FLAVOR.contentEquals("free")) {
                             // Reload the ad.  The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations.
-                            AdHelper.loadAd(findViewById(R.id.adview), getApplicationContext(), getString(R.string.ad_id));
+                            AdHelper.loadAd(findViewById(R.id.adview), getApplicationContext(), getString(R.string.ad_unit_id));
                         }
 
                         // Remove the translucent navigation bar flag if it is set.
@@ -949,10 +950,10 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                     mainWebView.evaluateJavascript("(function() {var parent = document.getElementsByTagName('head').item(0); var style = document.createElement('style'); style.type = 'text/css'; " +
                             "style.innerHTML = '* {background-color: #212121 !important; color: #BDBDBD !important; box-shadow: none !important; text-decoration: none !important;" +
                             "text-shadow: none !important; border: none !important;} a {color: #1565C0 !important;}'; parent.appendChild(style)})()", value -> {
-                                // Initialize a `Handler` to display `mainWebView`.
+                                // Initialize a handler to display `mainWebView`.
                                 Handler displayWebViewHandler = new Handler();
 
-                                // Setup a `Runnable` to display `mainWebView` after a delay to allow the CSS to be applied.
+                                // Setup a runnable to display `mainWebView` after a delay to allow the CSS to be applied.
                                 Runnable displayWebViewRunnable = () -> {
                                     // Only display `mainWebView` if the progress bar is one.  This prevents the display of the `WebView` while it is still loading.
                                     if (progressBar.getVisibility() == View.GONE) {
@@ -960,7 +961,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                                     }
                                 };
 
-                                // Use `displayWebViewHandler` to delay the displaying of `mainWebView` for 500 milliseconds.
+                                // Displaying of `mainWebView` after 500 milliseconds.
                                 displayWebViewHandler.postDelayed(displayWebViewRunnable, 500);
                             });
                 }
@@ -1097,7 +1098,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                     // Show the `BannerAd` in the free flavor.
                     if (BuildConfig.FLAVOR.contentEquals("free")) {
                         // Initialize the ad.  The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations.
-                        AdHelper.initializeAds(findViewById(R.id.adview), getApplicationContext(), getFragmentManager(), getString(R.string.ad_id));
+                        AdHelper.initializeAds(findViewById(R.id.adview), getApplicationContext(), getFragmentManager(), getString(R.string.google_app_id), getString(R.string.ad_unit_id));
                     }
 
                     // Remove any `SYSTEM_UI` flags from `rootCoordinatorLayout`.
@@ -1116,7 +1117,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 // Show the ad if this is the free flavor.
                 if (BuildConfig.FLAVOR.contentEquals("free")) {
                     // Reload the ad.  The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations.
-                    AdHelper.loadAd(findViewById(R.id.adview), getApplicationContext(), getString(R.string.ad_id));
+                    AdHelper.loadAd(findViewById(R.id.adview), getApplicationContext(), getString(R.string.ad_unit_id));
                 }
             }
 
@@ -1154,7 +1155,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
                 // Show a dialog if the user has previously denied the permission.
                 if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {  // Show a dialog explaining the request first.
-                    // Get a handle for the download location permission alert dialog and set the download type to DOWNLOAD_FILE.
+                    // Instantiate the download location permission alert dialog and set the download type to DOWNLOAD_FILE.
                     DialogFragment downloadLocationPermissionDialogFragment = DownloadLocationPermissionDialog.downloadType(DownloadLocationPermissionDialog.DOWNLOAD_FILE);
 
                     // Show the download location permission alert dialog.  The permission will be requested when the the dialog is closed.
@@ -1163,7 +1164,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                     // Request the permission.  The download dialog will be launched by `onRequestPermissionResult()`.
                     ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_FILE_REQUEST_CODE);
                 }
-            } else {  // The WRITE_EXTERNAL_STORAGE permission has already been granted.
+            } else {  // The storage permission has already been granted.
                 // Get a handle for the download file alert dialog.
                 AppCompatDialogFragment downloadFileDialogFragment = DownloadFileDialog.fromUrl(url, contentDisposition, contentLength);
 
@@ -1400,7 +1401,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                     activity.runOnUiThread(() -> {
                         navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
                         blocklistsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
-                        blockAllThirdParyRequestsMenuItem.setTitle(thirdPartyBlockedRequests + " - " + getString(R.string.block_all_third_party_requests));
+                        blockAllThirdPartyRequestsMenuItem.setTitle(thirdPartyBlockedRequests + " - " + getString(R.string.block_all_third_party_requests));
                     });
 
                     // Add the request to the log.
@@ -1526,7 +1527,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 // Add the request to the log because it hasn't been processed by any of the previous checks.
                 if (whiteListResultStringArray != null ) {  // The request was processed by a whitelist.
                     resourceRequests.add(whiteListResultStringArray);
-                } else {  // The request didn't match any blocklist entry.  Log it as a defult request.
+                } else {  // The request didn't match any blocklist entry.  Log it as a default request.
                     resourceRequests.add(new String[]{String.valueOf(REQUEST_DEFAULT), url});
                 }
 
@@ -1960,6 +1961,10 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         // Unregister the Orbot status broadcast receiver.
         this.unregisterReceiver(orbotStatusBroadcastReceiver);
 
+        // Close the bookmarks cursor and database.
+        bookmarksCursor.close();
+        bookmarksDatabaseHelper.close();
+
         // Run the default commands.
         super.onDestroy();
     }
@@ -1988,7 +1993,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         fanboysAnnoyanceListMenuItem = menu.findItem(R.id.fanboys_annoyance_list);
         fanboysSocialBlockingListMenuItem = menu.findItem(R.id.fanboys_social_blocking_list);
         ultraPrivacyMenuItem = menu.findItem(R.id.ultraprivacy);
-        blockAllThirdParyRequestsMenuItem = menu.findItem(R.id.block_all_third_party_requests);
+        blockAllThirdPartyRequestsMenuItem = menu.findItem(R.id.block_all_third_party_requests);
         MenuItem adConsentMenuItem = menu.findItem(R.id.ad_consent);
 
         // Only display third-party cookies if API >= 21
@@ -2070,7 +2075,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         fanboysAnnoyanceListMenuItem.setChecked(fanboysAnnoyanceListEnabled);
         fanboysSocialBlockingListMenuItem.setChecked(fanboysSocialBlockingListEnabled);
         ultraPrivacyMenuItem.setChecked(ultraPrivacyEnabled);
-        blockAllThirdParyRequestsMenuItem.setChecked(blockAllThirdPartyRequests);
+        blockAllThirdPartyRequestsMenuItem.setChecked(blockAllThirdPartyRequests);
         swipeToRefreshMenuItem.setChecked(swipeRefreshLayout.isEnabled());
         displayImagesMenuItem.setChecked(mainWebView.getSettings().getLoadsImagesAutomatically());
         nightModeMenuItem.setChecked(nightMode);
@@ -2123,7 +2128,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         fanboysAnnoyanceListMenuItem.setTitle(fanboysAnnoyanceListBlockedRequests + " - " + getString(R.string.fanboys_annoyance_list));
         fanboysSocialBlockingListMenuItem.setTitle(fanboysSocialBlockingListBlockedRequests + " - " + getString(R.string.fanboys_social_blocking_list));
         ultraPrivacyMenuItem.setTitle(ultraPrivacyBlockedRequests + " - " + getString(R.string.ultraprivacy));
-        blockAllThirdParyRequestsMenuItem.setTitle(thirdPartyBlockedRequests + " - " + getString(R.string.block_all_third_party_requests));
+        blockAllThirdPartyRequestsMenuItem.setTitle(thirdPartyBlockedRequests + " - " + getString(R.string.block_all_third_party_requests));
 
         // Get the current user agent.
         String currentUserAgent = mainWebView.getSettings().getUserAgentString();
@@ -2908,6 +2913,12 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 startActivity(settingsIntent);
                 break;
 
+            case R.id.import_export:
+                // Launch the import/export activity.
+                Intent importExportIntent = new Intent (this, ImportExportActivity.class);
+                startActivity(importExportIntent);
+                break;
+
             case R.id.guide:
                 // Launch `GuideActivity`.
                 Intent guideIntent = new Intent(this, GuideActivity.class);
@@ -2921,6 +2932,10 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 break;
 
             case R.id.clearAndExit:
+                // Close the bookmarks cursor and database.
+                bookmarksCursor.close();
+                bookmarksDatabaseHelper.close();
+
                 // Get a handle for `sharedPreferences`.  `this` references the current context.
                 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
 
@@ -3059,7 +3074,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         // Reload the ad for the free flavor if we not in full screen mode.
         if (BuildConfig.FLAVOR.contentEquals("free") && !inFullScreenBrowsingMode) {
             // Reload the ad.  The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations.
-            AdHelper.loadAd(findViewById(R.id.adview), getApplicationContext(), getString(R.string.ad_id));
+            AdHelper.loadAd(findViewById(R.id.adview), getApplicationContext(), getString(R.string.ad_unit_id));
         }
 
         // `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:
@@ -3120,7 +3135,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
                         // Show a dialog if the user has previously denied the permission.
                         if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {  // Show a dialog explaining the request first.
-                            // Get a handle for the download location permission alert dialog and set the download type to DOWNLOAD_FILE.
+                            // Instantiate the download location permission alert dialog and set the download type to DOWNLOAD_FILE.
                             DialogFragment downloadLocationPermissionDialogFragment = DownloadLocationPermissionDialog.downloadType(DownloadLocationPermissionDialog.DOWNLOAD_FILE);
 
                             // Show the download location permission alert dialog.  The permission will be requested when the the dialog is closed.
@@ -3205,7 +3220,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
                         // Show a dialog if the user has previously denied the permission.
                         if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {  // Show a dialog explaining the request first.
-                            // Get a handle for the download location permission alert dialog and set the download type to DOWNLOAD_IMAGE.
+                            // Instantiate the download location permission alert dialog and set the download type to DOWNLOAD_IMAGE.
                             DialogFragment downloadLocationPermissionDialogFragment = DownloadLocationPermissionDialog.downloadType(DownloadLocationPermissionDialog.DOWNLOAD_IMAGE);
 
                             // Show the download location permission alert dialog.  The permission will be requested when the dialog is closed.
@@ -3264,7 +3279,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
                         // Show a dialog if the user has previously denied the permission.
                         if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {  // Show a dialog explaining the request first.
-                            // Get a handle for the download location permission alert dialog and set the download type to DOWNLOAD_IMAGE.
+                            // Instantiate the download location permission alert dialog and set the download type to DOWNLOAD_IMAGE.
                             DialogFragment downloadLocationPermissionDialogFragment = DownloadLocationPermissionDialog.downloadType(DownloadLocationPermissionDialog.DOWNLOAD_IMAGE);
 
                             // Show the download location permission alert dialog.  The permission will be requested when the dialog is closed.
@@ -3417,7 +3432,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
     }
 
     @Override
-    public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[], @NonNull int[] grantResults) {
+    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
         switch (requestCode) {
             case DOWNLOAD_FILE_REQUEST_CODE:
                 // Show the download file alert dialog.  When the dialog closes, the correct command will be used based on the permission status.
@@ -3997,7 +4012,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             // Show the `BannerAd` in the free flavor.
             if (BuildConfig.FLAVOR.contentEquals("free")) {
                 // Initialize the ad.  The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations.
-                AdHelper.initializeAds(findViewById(R.id.adview), getApplicationContext(), getFragmentManager(), getString(R.string.ad_id));
+                AdHelper.initializeAds(findViewById(R.id.adview), getApplicationContext(), getFragmentManager(), getString(R.string.google_app_id), getString(R.string.ad_unit_id));
             }
 
             // Remove any `SYSTEM_UI` flags from `rootCoordinatorLayout`.
@@ -4364,7 +4379,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 urlAppBarRelativeLayout.setBackgroundDrawable(getResources().getDrawable(R.color.transparent));
             }
 
-            // Close `domainsDatabaseHelper`.
+            // Close the domains database helper.
             domainsDatabaseHelper.close();
 
             // Remove the `onTheFlyDisplayImagesSet` flag and set the display webpage images mode.  `true` indicates that custom domain settings are applied.
index 4b58053ac823fc94298e2957c44693ffeeb3ce99..a62708a3f70d144ba076192ee26147043f495aca 100644 (file)
@@ -45,7 +45,7 @@ import java.util.List;
 
 public class RequestsActivity extends AppCompatActivity implements ViewRequestDialog.ViewRequestListener {
     // The list view is used in `onCreate()` and `launchViewRequestDialog()`.
-    private ListView resourceRequestsListView;
+    private ListView requestsListView;
 
     @Override
     public void onCreate(Bundle savedInstanceState) {
@@ -68,12 +68,12 @@ public class RequestsActivity extends AppCompatActivity implements ViewRequestDi
         setContentView(R.layout.requests_coordinatorlayout);
 
         // Use the `SupportActionBar` from `android.support.v7.app.ActionBar` until the minimum API is >= 21.
-        Toolbar blockListsAppBar = findViewById(R.id.blocklists_toolbar);
-        setSupportActionBar(blockListsAppBar);
+        Toolbar requestsAppBar = findViewById(R.id.requests_toolbar);
+        setSupportActionBar(requestsAppBar);
 
         // Get a handle for the app bar and the list view.
         ActionBar appBar = getSupportActionBar();
-        resourceRequestsListView = findViewById(R.id.resource_requests_listview);
+        requestsListView = findViewById(R.id.requests_listview);
 
         // Remove the incorrect lint warning that `appBar` might be null.
         assert appBar != null;
@@ -165,7 +165,7 @@ public class RequestsActivity extends AppCompatActivity implements ViewRequestDi
                         ArrayAdapter<String[]> allResourceRequestsArrayAdapter = new RequestsArrayAdapter(getApplicationContext(), allResourceRequests);
 
                         // Display the adapter in the list view.
-                        resourceRequestsListView.setAdapter(allResourceRequestsArrayAdapter);
+                        requestsListView.setAdapter(allResourceRequestsArrayAdapter);
                         break;
 
                     case 1:  // Default requests.
@@ -173,7 +173,7 @@ public class RequestsActivity extends AppCompatActivity implements ViewRequestDi
                         ArrayAdapter<String[]> defaultResourceRequestsArrayAdapter = new RequestsArrayAdapter(getApplicationContext(), defaultResourceRequests);
 
                         // Display the adapter in the list view.
-                        resourceRequestsListView.setAdapter(defaultResourceRequestsArrayAdapter);
+                        requestsListView.setAdapter(defaultResourceRequestsArrayAdapter);
                         break;
 
                     case 2:  // Allowed requests.
@@ -181,7 +181,7 @@ public class RequestsActivity extends AppCompatActivity implements ViewRequestDi
                         ArrayAdapter<String[]> allowedResourceRequestsArrayAdapter = new RequestsArrayAdapter(getApplicationContext(), allowedResourceRequests);
 
                         // Display the adapter in the list view.
-                        resourceRequestsListView.setAdapter(allowedResourceRequestsArrayAdapter);
+                        requestsListView.setAdapter(allowedResourceRequestsArrayAdapter);
                         break;
 
                     case 3:  // Third-party requests.
@@ -189,7 +189,7 @@ public class RequestsActivity extends AppCompatActivity implements ViewRequestDi
                         ArrayAdapter<String[]> thirdPartyResourceRequestsArrayAdapter = new RequestsArrayAdapter(getApplicationContext(), thirdPartyResourceRequests);
 
                         //Display the adapter in the list view.
-                        resourceRequestsListView.setAdapter(thirdPartyResourceRequestsArrayAdapter);
+                        requestsListView.setAdapter(thirdPartyResourceRequestsArrayAdapter);
                         break;
 
                     case 4:  // Blocked requests.
@@ -197,7 +197,7 @@ public class RequestsActivity extends AppCompatActivity implements ViewRequestDi
                         ArrayAdapter<String[]> blockedResourceRequestsArrayAdapter = new RequestsArrayAdapter(getApplicationContext(), blockedResourceRequests);
 
                         // Display the adapter in the list view.
-                        resourceRequestsListView.setAdapter(blockedResourceRequestsArrayAdapter);
+                        requestsListView.setAdapter(blockedResourceRequestsArrayAdapter);
                         break;
                 }
             }
@@ -212,10 +212,10 @@ public class RequestsActivity extends AppCompatActivity implements ViewRequestDi
         ArrayAdapter<String[]> resourceRequestsArrayAdapter = new RequestsArrayAdapter(getApplicationContext(), allResourceRequests);
 
         // Populate the list view with the resource requests adapter.
-        resourceRequestsListView.setAdapter(resourceRequestsArrayAdapter);
+        requestsListView.setAdapter(resourceRequestsArrayAdapter);
 
         // Listen for taps on entries in the list view.
-        resourceRequestsListView.setOnItemClickListener((AdapterView<?> parent, View view, int position, long id) -> {
+        requestsListView.setOnItemClickListener((AdapterView<?> parent, View view, int position, long id) -> {
             // Display the view request dialog.  The list view is 0 based, so the position must be incremented by 1.
             launchViewRequestDialog(position + 1);
         });
@@ -235,10 +235,10 @@ public class RequestsActivity extends AppCompatActivity implements ViewRequestDi
 
     private void launchViewRequestDialog(int id) {
         // Determine if this is the last request in the list.
-        boolean isLastRequest = (id == resourceRequestsListView.getCount());
+        boolean isLastRequest = (id == requestsListView.getCount());
 
         // Get the string array for the selected resource request.  The resource requests list view is zero based.
-        String[] selectedRequestStringArray = (String[]) resourceRequestsListView.getItemAtPosition(id - 1);
+        String[] selectedRequestStringArray = (String[]) requestsListView.getItemAtPosition(id - 1);
 
         // Remove the warning that `selectedRequest` might be null.
         assert selectedRequestStringArray != null;
index 117f000bef2cfdf9f5d958594d3da4c3d3637cb1..a232058886580ef5eea61260e648ad0f63233eba 100644 (file)
@@ -31,10 +31,11 @@ import com.stoutner.privacybrowser.R;
 import com.stoutner.privacybrowser.activities.MainWebViewActivity;
 
 public class DownloadLocationPermissionDialog extends DialogFragment {
+    // The constants are used to differentiate between the two download types.
     public static final int DOWNLOAD_FILE = 1;
     public static final int DOWNLOAD_IMAGE = 2;
 
-    // `downloadLocationPermissionDialogListener` is used in `onAttach()` and `onCreateDialog()`.
+    // The listener is used in `onAttach()` and `onCreateDialog()`.
     private DownloadLocationPermissionDialogListener downloadLocationPermissionDialogListener;
 
     // The public interface is used to send information back to the parent activity.
@@ -47,7 +48,7 @@ public class DownloadLocationPermissionDialog extends DialogFragment {
         // Run the default commands.
         super.onAttach(context);
 
-        // Get a handle for `DownloadLocationPermissionDialogListener` from the launching context.
+        // Get a handle for the listener from the launching context.
         downloadLocationPermissionDialogListener = (DownloadLocationPermissionDialogListener) context;
     }
 
@@ -56,9 +57,9 @@ public class DownloadLocationPermissionDialog extends DialogFragment {
         Bundle argumentsBundle = new Bundle();
 
         // Store the download type in the bundle.
-        argumentsBundle.putInt("Download_Type", type);
+        argumentsBundle.putInt("download_type", type);
 
-        // Add the arguments bundle to this instance of `DownloadLocationPermissionDialog`.
+        // Add the arguments bundle to this instance of the dialog.
         DownloadLocationPermissionDialog thisDownloadLocationPermissionDialog = new DownloadLocationPermissionDialog();
         thisDownloadLocationPermissionDialog.setArguments(argumentsBundle);
         return thisDownloadLocationPermissionDialog;
@@ -66,8 +67,8 @@ public class DownloadLocationPermissionDialog extends DialogFragment {
 
     @Override
     public Dialog onCreateDialog(Bundle savedInstanceState) {
-        // Store the download type in the local class variable.
-        int downloadType = getArguments().getInt("Download_Type");
+        // Store the download type in a local variable.
+        int downloadType = getArguments().getInt("download_type");
 
         // Use a builder to create the alert dialog.
         AlertDialog.Builder dialogBuilder;
@@ -81,19 +82,19 @@ public class DownloadLocationPermissionDialog extends DialogFragment {
             dialogBuilder.setIcon(R.drawable.downloads_light);
         }
 
-        // Set an `onClick` listener on the negative button.  Using `null` as the listener closes the dialog without doing anything else.
-        dialogBuilder.setNegativeButton(R.string.ok, (DialogInterface dialog, int which) -> {
-            // Inform the parent activity that the dialog was closed.
-            downloadLocationPermissionDialogListener.onCloseDownloadLocationPermissionDialog(downloadType);
-        });
-
         // Set the title.
         dialogBuilder.setTitle(R.string.download_location);
 
         // Set the text.
         dialogBuilder.setMessage(R.string.download_location_message);
 
-        // Create an alert dialog from the alert dialog builder.
+        // Set an `onClick` listener on the negative button.
+        dialogBuilder.setNegativeButton(R.string.ok, (DialogInterface dialog, int which) -> {
+            // Inform the parent activity that the dialog was closed.
+            downloadLocationPermissionDialogListener.onCloseDownloadLocationPermissionDialog(downloadType);
+        });
+
+        // Create an alert dialog from the builder.
         final AlertDialog alertDialog = dialogBuilder.create();
 
         // Disable screenshots if not allowed.
@@ -105,7 +106,7 @@ public class DownloadLocationPermissionDialog extends DialogFragment {
             alertDialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE);
         }
 
-        // `onCreateDialog` requires the return of an `AlertDialog`.
+        // `onCreateDialog()` requires the return of an `AlertDialog`.
         return alertDialog;
     }
 }
\ No newline at end of file
diff --git a/app/src/main/java/com/stoutner/privacybrowser/dialogs/ImportExportStoragePermissionDialog.java b/app/src/main/java/com/stoutner/privacybrowser/dialogs/ImportExportStoragePermissionDialog.java
new file mode 100644 (file)
index 0000000..d5b9390
--- /dev/null
@@ -0,0 +1,112 @@
+/*
+ * Copyright © 2018 Soren Stoutner <soren@stoutner.com>.
+ *
+ * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
+ *
+ * Privacy Browser is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Privacy Browser is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Privacy Browser.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package com.stoutner.privacybrowser.dialogs;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import android.view.WindowManager;
+
+import com.stoutner.privacybrowser.R;
+import com.stoutner.privacybrowser.activities.MainWebViewActivity;
+
+public class ImportExportStoragePermissionDialog extends DialogFragment {
+    // The constants are used to differentiate between the two commands.
+    public static final int EXPORT_SETTINGS = 1;
+    public static final int IMPORT_SETTINGS = 2;
+
+    // The listener is used in `onAttach()` and `onCreateDialog()`.
+    private ImportExportStoragePermissionDialogListener importExportStoragePermissionDialogListener;
+
+    // The public interface is used to send information back to the parent activity.
+    public interface ImportExportStoragePermissionDialogListener {
+        void onCloseImportExportStoragePermissionDialog(int type);
+    }
+
+    @Override
+    public void onAttach(Context context) {
+        // Run the default commands.
+        super.onAttach(context);
+
+        // Get a handle for the listener from the launching context.
+        importExportStoragePermissionDialogListener = (ImportExportStoragePermissionDialogListener) context;
+    }
+
+    public static ImportExportStoragePermissionDialog type(int type) {
+        // Create an arguments bundle.
+        Bundle argumentsBundle = new Bundle();
+
+        // Store the download type in the bundle.
+        argumentsBundle.putInt("type", type);
+
+        // Add the arguments bundle to this instance of the dialog.
+        ImportExportStoragePermissionDialog thisImportExportStoragePermissionDialog = new ImportExportStoragePermissionDialog();
+        thisImportExportStoragePermissionDialog.setArguments(argumentsBundle);
+        return thisImportExportStoragePermissionDialog;
+    }
+
+    @Override
+    public Dialog onCreateDialog(Bundle savedInstanceState) {
+        // Store the download type in a local variable.
+        int type = getArguments().getInt("type");
+
+        // Use a builder to create the alert dialog.
+        AlertDialog.Builder dialogBuilder;
+
+        // Set the style and the icon according to the theme.
+        if (MainWebViewActivity.darkTheme) {
+            dialogBuilder = new AlertDialog.Builder(getActivity(), R.style.PrivacyBrowserAlertDialogDark);
+            dialogBuilder.setIcon(R.drawable.import_export_dark);
+        } else {
+            dialogBuilder = new AlertDialog.Builder(getActivity(), R.style.PrivacyBrowserAlertDialogLight);
+            dialogBuilder.setIcon(R.drawable.import_export_light);
+        }
+
+        // Set the title.
+        dialogBuilder.setTitle(R.string.storage_permission);
+
+        // Set the text.
+        dialogBuilder.setMessage(R.string.storage_permission_message);
+
+        // Set an `onClick` listener on the negative button.
+        dialogBuilder.setNegativeButton(R.string.ok, (DialogInterface dialog, int which) -> {
+            // Inform the parent activity that the dialog was closed.
+            importExportStoragePermissionDialogListener.onCloseImportExportStoragePermissionDialog(type);
+        });
+
+        // Create an alert dialog from the builder.
+        final AlertDialog alertDialog = dialogBuilder.create();
+
+        // Disable screenshots if not allowed.
+        if (!MainWebViewActivity.allowScreenshots) {
+            // Remove the warning below that `getWindow()` might be null.
+            assert alertDialog.getWindow() != null;
+
+            // Disable screenshots.
+            alertDialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE);
+        }
+
+        // `onCreateDialog()` requires the return of an `AlertDialog`.
+        return alertDialog;
+    }
+}
index e152b14881afb8ff82c182da69ee439d6c2f5691..ac70f2c8a6de2e4be4638ef7aa9e90b75bff1f7f 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2016-2017 Soren Stoutner <soren@stoutner.com>.
+ * Copyright © 2016-2018 Soren Stoutner <soren@stoutner.com>.
  *
  * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
  *
@@ -28,8 +28,8 @@ import android.database.sqlite.SQLiteOpenHelper;
 
 public class BookmarksDatabaseHelper extends SQLiteOpenHelper {
     private static final int SCHEMA_VERSION = 1;
-    private static final String BOOKMARKS_DATABASE = "bookmarks.db";
-    private static final String BOOKMARKS_TABLE = "bookmarks";
+    static final String BOOKMARKS_DATABASE = "bookmarks.db";
+    static final String BOOKMARKS_TABLE = "bookmarks";
 
     public static final String _ID = "_id";
     public static final String BOOKMARK_NAME = "bookmarkname";
@@ -39,6 +39,15 @@ public class BookmarksDatabaseHelper extends SQLiteOpenHelper {
     public static final String IS_FOLDER = "isfolder";
     public static final String FAVORITE_ICON = "favoriteicon";
 
+    static final String CREATE_BOOKMARKS_TABLE = "CREATE TABLE " + BOOKMARKS_TABLE + " (" +
+            _ID + " INTEGER PRIMARY KEY, " +
+            BOOKMARK_NAME + " TEXT, " +
+            BOOKMARK_URL + " TEXT, " +
+            PARENT_FOLDER + " TEXT, " +
+            DISPLAY_ORDER + " INTEGER, " +
+            IS_FOLDER + " BOOLEAN, " +
+            FAVORITE_ICON + " BLOB)";
+
     // Initialize the database.  The lint warnings for the unused parameters are suppressed.
     public BookmarksDatabaseHelper(Context context, @SuppressWarnings("UnusedParameters") String name, SQLiteDatabase.CursorFactory cursorFactory, @SuppressWarnings("UnusedParameters") int version) {
         super(context, BOOKMARKS_DATABASE, cursorFactory, SCHEMA_VERSION);
@@ -46,17 +55,7 @@ public class BookmarksDatabaseHelper extends SQLiteOpenHelper {
 
     @Override
     public void onCreate(SQLiteDatabase bookmarksDatabase) {
-        // Setup the SQL string to create the `bookmarks` table.
-        final String CREATE_BOOKMARKS_TABLE = "CREATE TABLE " + BOOKMARKS_TABLE + " (" +
-                _ID + " integer primary key, " +
-                BOOKMARK_NAME + " text, " +
-                BOOKMARK_URL + " text, " +
-                PARENT_FOLDER + " text, " +
-                DISPLAY_ORDER + " integer, " +
-                IS_FOLDER + " boolean, " +
-                FAVORITE_ICON + " blob);";
-
-        // Create the `bookmarks` table.
+        // Create the bookmarks table.
         bookmarksDatabase.execSQL(CREATE_BOOKMARKS_TABLE);
     }
 
@@ -67,7 +66,7 @@ public class BookmarksDatabaseHelper extends SQLiteOpenHelper {
 
     // Create a bookmark.
     public void createBookmark(String bookmarkName, String bookmarkURL, String parentFolder, int displayOrder, byte[] favoriteIcon) {
-        // We need to store the bookmark data in a `ContentValues`.
+        // Store the bookmark data in a `ContentValues`.
         ContentValues bookmarkContentValues = new ContentValues();
 
         // ID is created automatically.
@@ -88,6 +87,18 @@ public class BookmarksDatabaseHelper extends SQLiteOpenHelper {
         bookmarksDatabase.close();
     }
 
+    // Create a bookmark from content values.
+    void createBookmark(ContentValues contentValues) {
+        // Get a writable database.
+        SQLiteDatabase bookmarksDatabase = this.getWritableDatabase();
+
+        // Insert a new row.  The second argument is `null`, which makes it so that a completely null row cannot be created.
+        bookmarksDatabase.insert(BOOKMARKS_TABLE, null, contentValues);
+
+        // Close the database handle.
+        bookmarksDatabase.close();
+    }
+
     // Create a folder.
     public void createFolder(String folderName, String parentFolder, byte[] favoriteIcon) {
         ContentValues bookmarkContentValues = new ContentValues();
@@ -272,11 +283,10 @@ public class BookmarksDatabaseHelper extends SQLiteOpenHelper {
         // Get a readable database handle.
         SQLiteDatabase bookmarksDatabase = this.getReadableDatabase();
 
-        // Get everything in `BOOKMARKS_TABLE`.
+        // Get everything in in the bookmarks table.
         final String GET_ALL_BOOKMARKS = "SELECT * FROM " + BOOKMARKS_TABLE;
 
-        // Return the results as a Cursor.  The second argument is `null` because there are no selectionArgs.
-        // We can't close the Cursor because we need to use it in the parent activity.
+        // Return the results as a Cursor.  The second argument is `null` because there are no selectionArgs.  The Cursor cannot be closed because it is used in the parent activity.
         return bookmarksDatabase.rawQuery(GET_ALL_BOOKMARKS, null);
     }
 
@@ -288,11 +298,11 @@ public class BookmarksDatabaseHelper extends SQLiteOpenHelper {
         // SQL escape `folderName`.
         folderName = DatabaseUtils.sqlEscapeString(folderName);
 
-        // Get everything in the `BOOKMARKS_TABLE` with `folderName` as the `PARENT_FOLDER`.
+        // Get everything in the bookmarks table with `folderName` as the `PARENT_FOLDER`.
         final String GET_ALL_BOOKMARKS = "SELECT * FROM " + BOOKMARKS_TABLE +
                 " WHERE " + PARENT_FOLDER + " = " + folderName;
 
-        // Return the results as a `Cursor`.  The second argument is `null` because there are no `selectionArgs`.  We can't close the `Cursor` because we need to use it in the parent activity.
+        // Return the results as a `Cursor`.  The second argument is `null` because there are no `selectionArgs`.  The Cursor cannot be closed because it is used in the parent activity.
         return bookmarksDatabase.rawQuery(GET_ALL_BOOKMARKS, null);
     }
 
@@ -309,7 +319,7 @@ public class BookmarksDatabaseHelper extends SQLiteOpenHelper {
                 " WHERE " + PARENT_FOLDER + " = " + folderName +
                 " ORDER BY " + DISPLAY_ORDER + " ASC";
 
-        // Return the results as a `Cursor`.  The second argument is `null` because there are no `selectionArgs`.  We can't close the `Cursor` because we need to use it in the parent activity.
+        // Return the results as a `Cursor`.  The second argument is `null` because there are no `selectionArgs`.  The Cursor cannot be closed because it is used in the parent activity.
         return bookmarksDatabase.rawQuery(GET_ALL_BOOKMARKS, null);
     }
 
index 0af0390a719452411ad931b42806dcf80cf970cb..2ed01cb424b81f2944f1dc1ae9ad4c280de8859c 100644 (file)
@@ -29,8 +29,8 @@ import android.preference.PreferenceManager;
 
 public class DomainsDatabaseHelper extends SQLiteOpenHelper {
     private static final int SCHEMA_VERSION = 8;
-    private static final String DOMAINS_DATABASE = "domains.db";
-    private static final String DOMAINS_TABLE = "domains";
+    static final String DOMAINS_DATABASE = "domains.db";
+    static final String DOMAINS_TABLE = "domains";
 
     public static final String _ID = "_id";
     public static final String DOMAIN_NAME = "domainname";
@@ -75,6 +75,35 @@ public class DomainsDatabaseHelper extends SQLiteOpenHelper {
     public static final int DISPLAY_WEBPAGE_IMAGES_ENABLED = 1;
     public static final int DISPLAY_WEBPAGE_IMAGES_DISABLED = 2;
 
+    static final String CREATE_DOMAINS_TABLE = "CREATE TABLE " + DOMAINS_TABLE + " (" +
+            _ID + " INTEGER PRIMARY KEY, " +
+            DOMAIN_NAME + " TEXT, " +
+            ENABLE_JAVASCRIPT + " BOOLEAN, " +
+            ENABLE_FIRST_PARTY_COOKIES + " BOOLEAN, " +
+            ENABLE_THIRD_PARTY_COOKIES + " BOOLEAN, " +
+            ENABLE_DOM_STORAGE + " BOOLEAN, " +
+            ENABLE_FORM_DATA + " BOOLEAN, " +
+            ENABLE_EASYLIST + " BOOLEAN, " +
+            ENABLE_EASYPRIVACY + " BOOLEAN, " +
+            ENABLE_FANBOYS_ANNOYANCE_LIST + " BOOLEAN, " +
+            ENABLE_FANBOYS_SOCIAL_BLOCKING_LIST + " BOOLEAN, " +
+            ENABLE_ULTRAPRIVACY + " BOOLEAN, " +
+            BLOCK_ALL_THIRD_PARTY_REQUESTS + " BOOLEAN, " +
+            USER_AGENT + " TEXT, " +
+            FONT_SIZE + " INTEGER, " +
+            SWIPE_TO_REFRESH + " INTEGER, " +
+            NIGHT_MODE + " INTEGER, " +
+            DISPLAY_IMAGES + " INTEGER, " +
+            PINNED_SSL_CERTIFICATE + " BOOLEAN, " +
+            SSL_ISSUED_TO_COMMON_NAME + " TEXT, " +
+            SSL_ISSUED_TO_ORGANIZATION + " TEXT, " +
+            SSL_ISSUED_TO_ORGANIZATIONAL_UNIT + " TEXT, " +
+            SSL_ISSUED_BY_COMMON_NAME + " TEXT, " +
+            SSL_ISSUED_BY_ORGANIZATION + " TEXT, " +
+            SSL_ISSUED_BY_ORGANIZATIONAL_UNIT + " TEXT, " +
+            SSL_START_DATE + " INTEGER, " +
+            SSL_END_DATE + " INTEGER)";
+
     private final Context appContext;
 
     // Initialize the database.  The lint warnings for the unused parameters are suppressed.
@@ -87,37 +116,7 @@ public class DomainsDatabaseHelper extends SQLiteOpenHelper {
 
     @Override
     public void onCreate(SQLiteDatabase domainsDatabase) {
-        // Setup the SQL string to create the `domains` table.
-        String CREATE_DOMAINS_TABLE = "CREATE TABLE " + DOMAINS_TABLE + " (" +
-                _ID + " INTEGER PRIMARY KEY, " +
-                DOMAIN_NAME + " TEXT, " +
-                ENABLE_JAVASCRIPT + " BOOLEAN, " +
-                ENABLE_FIRST_PARTY_COOKIES + " BOOLEAN, " +
-                ENABLE_THIRD_PARTY_COOKIES + " BOOLEAN, " +
-                ENABLE_DOM_STORAGE + " BOOLEAN, " +
-                ENABLE_FORM_DATA + " BOOLEAN, " +
-                ENABLE_EASYLIST + " BOOLEAN, " +
-                ENABLE_EASYPRIVACY + " BOOLEAN, " +
-                ENABLE_FANBOYS_ANNOYANCE_LIST + " BOOLEAN, " +
-                ENABLE_FANBOYS_SOCIAL_BLOCKING_LIST + " BOOLEAN, " +
-                ENABLE_ULTRAPRIVACY + " BOOLEAN, " +
-                BLOCK_ALL_THIRD_PARTY_REQUESTS + " BOOLEAN, " +
-                USER_AGENT + " TEXT, " +
-                FONT_SIZE + " INTEGER, " +
-                SWIPE_TO_REFRESH + " INTEGER, " +
-                NIGHT_MODE + " INTEGER, " +
-                DISPLAY_IMAGES + " INTEGER, " +
-                PINNED_SSL_CERTIFICATE + " BOOLEAN, " +
-                SSL_ISSUED_TO_COMMON_NAME + " TEXT, " +
-                SSL_ISSUED_TO_ORGANIZATION + " TEXT, " +
-                SSL_ISSUED_TO_ORGANIZATIONAL_UNIT + " TEXT, " +
-                SSL_ISSUED_BY_COMMON_NAME + " TEXT, " +
-                SSL_ISSUED_BY_ORGANIZATION + " TEXT, " +
-                SSL_ISSUED_BY_ORGANIZATIONAL_UNIT + " TEXT, " +
-                SSL_START_DATE + " INTEGER, " +
-                SSL_END_DATE + " INTEGER);";
-
-        // Make it so.
+        // Create the domains table.
         domainsDatabase.execSQL(CREATE_DOMAINS_TABLE);
     }
 
@@ -213,11 +212,19 @@ public class DomainsDatabaseHelper extends SQLiteOpenHelper {
         }
     }
 
+    Cursor getCompleteCursorOrderedByDomain() {
+        // Get a readable database handle.
+        SQLiteDatabase domainsDatabase = this.getReadableDatabase();
+
+        // Return everything in the domains table ordered by the domain name.  The second argument is `null` because there are no `selectionArgs`.
+        return domainsDatabase.rawQuery("SELECT * FROM " + DOMAINS_TABLE + " ORDER BY " + DOMAIN_NAME + " ASC", null);
+    }
+
     public Cursor getDomainNameCursorOrderedByDomain() {
         // Get a readable database handle.
         SQLiteDatabase domainsDatabase = this.getReadableDatabase();
 
-        // Get everything in `DOMAINS_TABLE` ordered by `DOMAIN_NAME`.
+        // Get everything in the domains table ordered by the domain name.
         String GET_CURSOR_ORDERED_BY_DOMAIN = "SELECT " + _ID + ", " + DOMAIN_NAME +
                 " FROM " + DOMAINS_TABLE +
                 " ORDER BY " + DOMAIN_NAME + " ASC";
@@ -313,6 +320,17 @@ public class DomainsDatabaseHelper extends SQLiteOpenHelper {
         return newDomainDatabaseId;
     }
 
+    void addDomain(ContentValues contentValues) {
+        // Get a writable database handle.
+        SQLiteDatabase domainsDatabase = this.getWritableDatabase();
+
+        // Add the new domain.
+        domainsDatabase.insert(DOMAINS_TABLE, null, contentValues);
+
+        // Close the database handle.
+        domainsDatabase.close();
+    }
+
     public void updateDomainExceptCertificate(int databaseId, String domainName, boolean javaScriptEnabled, boolean firstPartyCookiesEnabled, boolean thirdPartyCookiesEnabled, boolean domStorageEnabled,
                                               boolean formDataEnabled, boolean easyListEnabled, boolean easyPrivacyEnabled, boolean fanboysAnnoyanceEnabled, boolean fanboysSocialBlockingEnabled,
                                               boolean ultraPrivacyEnabled, boolean blockAllThirdPartyRequests, String userAgent, int fontSize, int swipeToRefresh, int nightMode, int displayImages,
diff --git a/app/src/main/java/com/stoutner/privacybrowser/helpers/ImportExportDatabaseHelper.java b/app/src/main/java/com/stoutner/privacybrowser/helpers/ImportExportDatabaseHelper.java
new file mode 100644 (file)
index 0000000..3388700
--- /dev/null
@@ -0,0 +1,498 @@
+/*
+ * Copyright © 2018 Soren Stoutner <soren@stoutner.com>.
+ *
+ * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
+ *
+ * Privacy Browser is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Privacy Browser is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Privacy Browser.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package com.stoutner.privacybrowser.helpers;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.preference.PreferenceManager;
+
+import java.io.File;
+
+public class ImportExportDatabaseHelper {
+    public static final String EXPORT_SUCCESSFUL = "Export Successful";
+    public static final String IMPORT_SUCCESSFUL = "Import Successful";
+
+    private static final int SCHEMA_VERSION = 1;
+    private static final String PREFERENCES_TABLE = "preferences";
+
+    // The preferences constants.
+    private static final String _ID = "_id";
+    private static final String JAVASCRIPT = "javascript";
+    private static final String FIRST_PARTY_COOKIES = "first_party_cookies";
+    private static final String THIRD_PARTY_COOKIES = "third_party_cookies";
+    private static final String DOM_STORAGE = "dom_storage";
+    private static final String SAVE_FORM_DATA = "save_form_data";
+    private static final String USER_AGENT = "user_agent";
+    private static final String CUSTOM_USER_AGENT = "custom_user_agent";
+    private static final String INCOGNITO_MODE = "incognito_mode";
+    private static final String DO_NOT_TRACK = "do_not_track";
+    private static final String ALLOW_SCREENSHOTS = "allow_screenshots";
+    private static final String EASYLIST = "easylist";
+    private static final String EASYPRIVACY = "easyprivacy";
+    private static final String FANBOYS_ANNOYANCE_LIST = "fanboys_annoyance_list";
+    private static final String FANBOYS_SOCIAL_BLOCKING_LIST = "fanboys_social_blocking_list";
+    private static final String ULTRAPRIVACY = "ultraprivacy";
+    private static final String BLOCK_ALL_THIRD_PARTY_REQUESTS = "block_all_third_party_requests";
+    private static final String PROXY_THROUGH_ORBOT = "proxy_through_orbot";
+    private static final String TOR_HOMEPAGE = "tor_homepage";
+    private static final String TOR_SEARCH = "tor_search";
+    private static final String TOR_SEARCH_CUSTOM_URL = "tor_search_custom_url";
+    private static final String SEARCH = "search";
+    private static final String SEARCH_CUSTOM_URL = "search_custom_url";
+    private static final String FULL_SCREEN_BROWSING_MODE = "full_screen_browsing_mode";
+    private static final String HIDE_SYSTEM_BARS = "hide_system_bars";
+    private static final String TRANSLUCENT_NAVIGATION_BAR = "translucent_navigation_bar";
+    private static final String CLEAR_EVERYTHING = "clear_everything";
+    private static final String CLEAR_COOKIES = "clear_cookies";
+    private static final String CLEAR_DOM_STORAGE = "clear_dom_storage";
+    private static final String CLEAR_FORM_DATA = "clear_form_data";
+    private static final String CLEAR_CACHE = "clear_cache";
+    private static final String HOMEPAGE = "homepage";
+    private static final String DEFAULT_FONT_SIZE = "default_font_size";
+    private static final String SWIPE_TO_REFRESH = "swipe_to_refresh";
+    private static final String DISPLAY_ADDITIONAL_APP_BAR_ICONS = "display_additional_app_bar_icons";
+    private static final String DARK_THEME = "dark_theme";
+    private static final String NIGHT_MODE = "night_mode";
+    private static final String DISPLAY_WEBPAGE_IMAGES = "display_webpage_images";
+
+    public String exportUnencrypted(File databaseFile, Context context) {
+        try {
+            // Delete the current file if it exists.
+            if (databaseFile.exists()) {
+                //noinspection ResultOfMethodCallIgnored
+                databaseFile.delete();
+            }
+
+            // Create the export database.
+            SQLiteDatabase exportDatabase = SQLiteDatabase.openOrCreateDatabase(databaseFile, null);
+
+            // Set the export database version number.
+            exportDatabase.setVersion(SCHEMA_VERSION);
+
+            // Create the export database domains table.
+            exportDatabase.execSQL(DomainsDatabaseHelper.CREATE_DOMAINS_TABLE);
+
+            // Open the domains database.  The `0` specifies the database version, but that is ignored and set instead using a constant in `DomainsDatabaseHelper`.
+            DomainsDatabaseHelper domainsDatabaseHelper = new DomainsDatabaseHelper(context, null, null, 0);
+
+            // Get a full domains database cursor.
+            Cursor domainsCursor = domainsDatabaseHelper.getCompleteCursorOrderedByDomain();
+
+            // Move to the first domain.
+            domainsCursor.moveToFirst();
+
+            // Copy the data from the domains cursor into the export database.
+            for (int i = 0; i < domainsCursor.getCount(); i++) {
+                // Extract the record from the cursor and store the data in a ContentValues.
+                ContentValues domainsContentValues = new ContentValues();
+                domainsContentValues.put(DomainsDatabaseHelper.DOMAIN_NAME, domainsCursor.getString(domainsCursor.getColumnIndex(DomainsDatabaseHelper.DOMAIN_NAME)));
+                domainsContentValues.put(DomainsDatabaseHelper.ENABLE_JAVASCRIPT, domainsCursor.getInt(domainsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_JAVASCRIPT)));
+                domainsContentValues.put(DomainsDatabaseHelper.ENABLE_FIRST_PARTY_COOKIES, domainsCursor.getInt(domainsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FIRST_PARTY_COOKIES)));
+                domainsContentValues.put(DomainsDatabaseHelper.ENABLE_THIRD_PARTY_COOKIES, domainsCursor.getInt(domainsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_THIRD_PARTY_COOKIES)));
+                domainsContentValues.put(DomainsDatabaseHelper.ENABLE_DOM_STORAGE, domainsCursor.getInt(domainsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_DOM_STORAGE)));
+                domainsContentValues.put(DomainsDatabaseHelper.ENABLE_FORM_DATA, domainsCursor.getInt(domainsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FORM_DATA)));
+                domainsContentValues.put(DomainsDatabaseHelper.ENABLE_EASYLIST, domainsCursor.getInt(domainsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_EASYLIST)));
+                domainsContentValues.put(DomainsDatabaseHelper.ENABLE_EASYPRIVACY, domainsCursor.getInt(domainsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_EASYPRIVACY)));
+                domainsContentValues.put(DomainsDatabaseHelper.ENABLE_FANBOYS_ANNOYANCE_LIST, domainsCursor.getInt(domainsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FANBOYS_ANNOYANCE_LIST)));
+                domainsContentValues.put(DomainsDatabaseHelper.ENABLE_FANBOYS_SOCIAL_BLOCKING_LIST, domainsCursor.getInt(domainsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FANBOYS_SOCIAL_BLOCKING_LIST)));
+                domainsContentValues.put(DomainsDatabaseHelper.ENABLE_ULTRAPRIVACY, domainsCursor.getInt(domainsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_ULTRAPRIVACY)));
+                domainsContentValues.put(DomainsDatabaseHelper.BLOCK_ALL_THIRD_PARTY_REQUESTS, domainsCursor.getInt(domainsCursor.getColumnIndex(DomainsDatabaseHelper.BLOCK_ALL_THIRD_PARTY_REQUESTS)));
+                domainsContentValues.put(DomainsDatabaseHelper.USER_AGENT, domainsCursor.getString(domainsCursor.getColumnIndex(DomainsDatabaseHelper.USER_AGENT)));
+                domainsContentValues.put(DomainsDatabaseHelper.FONT_SIZE, domainsCursor.getInt(domainsCursor.getColumnIndex(DomainsDatabaseHelper.FONT_SIZE)));
+                domainsContentValues.put(DomainsDatabaseHelper.SWIPE_TO_REFRESH, domainsCursor.getInt(domainsCursor.getColumnIndex(DomainsDatabaseHelper.SWIPE_TO_REFRESH)));
+                domainsContentValues.put(DomainsDatabaseHelper.NIGHT_MODE, domainsCursor.getInt(domainsCursor.getColumnIndex(DomainsDatabaseHelper.NIGHT_MODE)));
+                domainsContentValues.put(DomainsDatabaseHelper.DISPLAY_IMAGES, domainsCursor.getInt(domainsCursor.getColumnIndex(DomainsDatabaseHelper.DISPLAY_IMAGES)));
+                domainsContentValues.put(DomainsDatabaseHelper.PINNED_SSL_CERTIFICATE, domainsCursor.getInt(domainsCursor.getColumnIndex(DomainsDatabaseHelper.PINNED_SSL_CERTIFICATE)));
+                domainsContentValues.put(DomainsDatabaseHelper.SSL_ISSUED_TO_COMMON_NAME, domainsCursor.getString(domainsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_TO_COMMON_NAME)));
+                domainsContentValues.put(DomainsDatabaseHelper.SSL_ISSUED_TO_ORGANIZATION, domainsCursor.getString(domainsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_TO_ORGANIZATION)));
+                domainsContentValues.put(DomainsDatabaseHelper.SSL_ISSUED_TO_ORGANIZATIONAL_UNIT, domainsCursor.getString(domainsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_TO_ORGANIZATIONAL_UNIT)));
+                domainsContentValues.put(DomainsDatabaseHelper.SSL_ISSUED_BY_COMMON_NAME, domainsCursor.getString(domainsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_BY_COMMON_NAME)));
+                domainsContentValues.put(DomainsDatabaseHelper.SSL_ISSUED_BY_ORGANIZATION, domainsCursor.getString(domainsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_BY_ORGANIZATION)));
+                domainsContentValues.put(DomainsDatabaseHelper.SSL_ISSUED_BY_ORGANIZATIONAL_UNIT, domainsCursor.getString(domainsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_BY_ORGANIZATIONAL_UNIT)));
+                domainsContentValues.put(DomainsDatabaseHelper.SSL_START_DATE, domainsCursor.getLong(domainsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_START_DATE)));
+                domainsContentValues.put(DomainsDatabaseHelper.SSL_END_DATE, domainsCursor.getLong(domainsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_END_DATE)));
+
+                // Insert the record into the export database.
+                exportDatabase.insert(DomainsDatabaseHelper.DOMAINS_TABLE, null, domainsContentValues);
+
+                // Advance to the next record.
+                domainsCursor.moveToNext();
+            }
+
+            // Close the domains database.
+            domainsCursor.close();
+            domainsDatabaseHelper.close();
+
+
+            // Create the export database bookmarks table.
+            exportDatabase.execSQL(BookmarksDatabaseHelper.CREATE_BOOKMARKS_TABLE);
+
+            // Open the bookmarks database.  The `0` specifies the database version, but that is ignored and set instead using a constant in `BookmarksDatabaseHelper`.
+            BookmarksDatabaseHelper bookmarksDatabaseHelper = new BookmarksDatabaseHelper(context, null, null, 0);
+
+            // Get a full bookmarks cursor.
+            Cursor bookmarksCursor = bookmarksDatabaseHelper.getAllBookmarksCursor();
+
+            // Move to the first bookmark.
+            bookmarksCursor.moveToFirst();
+
+            // Copy the data from the bookmarks cursor into the export database.
+            for (int i = 0; i < bookmarksCursor.getCount(); i++) {
+                // Extract the record from the cursor and store the data in a ContentValues.
+                ContentValues bookmarksContentValues = new ContentValues();
+                bookmarksContentValues.put(BookmarksDatabaseHelper.BOOKMARK_NAME, bookmarksCursor.getString(bookmarksCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME)));
+                bookmarksContentValues.put(BookmarksDatabaseHelper.BOOKMARK_URL, bookmarksCursor.getString(bookmarksCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_URL)));
+                bookmarksContentValues.put(BookmarksDatabaseHelper.PARENT_FOLDER, bookmarksCursor.getString(bookmarksCursor.getColumnIndex(BookmarksDatabaseHelper.PARENT_FOLDER)));
+                bookmarksContentValues.put(BookmarksDatabaseHelper.DISPLAY_ORDER, bookmarksCursor.getInt(bookmarksCursor.getColumnIndex(BookmarksDatabaseHelper.DISPLAY_ORDER)));
+                bookmarksContentValues.put(BookmarksDatabaseHelper.IS_FOLDER, bookmarksCursor.getInt(bookmarksCursor.getColumnIndex(BookmarksDatabaseHelper.IS_FOLDER)));
+                bookmarksContentValues.put(BookmarksDatabaseHelper.FAVORITE_ICON, bookmarksCursor.getBlob(bookmarksCursor.getColumnIndex(BookmarksDatabaseHelper.FAVORITE_ICON)));
+
+                // Insert the record into the export database.
+                exportDatabase.insert(BookmarksDatabaseHelper.BOOKMARKS_TABLE, null, bookmarksContentValues);
+
+                // Advance to the next record.
+                bookmarksCursor.moveToNext();
+            }
+
+            // Close the bookmarks database.
+            bookmarksCursor.close();
+            bookmarksDatabaseHelper.close();
+
+
+            // Prepare the preferences table SQL creation string.
+            String CREATE_PREFERENCES_TABLE = "CREATE TABLE " + PREFERENCES_TABLE + " (" +
+                    _ID + " INTEGER PRIMARY KEY, " +
+                    JAVASCRIPT + " BOOLEAN, " +
+                    FIRST_PARTY_COOKIES + " BOOLEAN, " +
+                    THIRD_PARTY_COOKIES + " BOOLEAN, " +
+                    DOM_STORAGE + " BOOLEAN, " +
+                    SAVE_FORM_DATA + " BOOLEAN, " +
+                    USER_AGENT + " TEXT, " +
+                    CUSTOM_USER_AGENT + " TEXT, " +
+                    INCOGNITO_MODE + " BOOLEAN, " +
+                    DO_NOT_TRACK + " BOOLEAN, " +
+                    ALLOW_SCREENSHOTS + " BOOLEAN, " +
+                    EASYLIST + " BOOLEAN, " +
+                    EASYPRIVACY + " BOOLEAN, " +
+                    FANBOYS_ANNOYANCE_LIST + " BOOLEAN, " +
+                    FANBOYS_SOCIAL_BLOCKING_LIST + " BOOLEAN, " +
+                    ULTRAPRIVACY + " BOOLEAN, " +
+                    BLOCK_ALL_THIRD_PARTY_REQUESTS + " BOOLEAN, " +
+                    PROXY_THROUGH_ORBOT + " BOOLEAN, " +
+                    TOR_HOMEPAGE + " TEXT, " +
+                    TOR_SEARCH + " TEXT, " +
+                    TOR_SEARCH_CUSTOM_URL + " TEXT, " +
+                    SEARCH + " TEXT, " +
+                    SEARCH_CUSTOM_URL + " TEXT, " +
+                    FULL_SCREEN_BROWSING_MODE + " BOOLEAN, " +
+                    HIDE_SYSTEM_BARS + " BOOLEAN, " +
+                    TRANSLUCENT_NAVIGATION_BAR + " BOOLEAN, " +
+                    CLEAR_EVERYTHING + " BOOLEAN, " +
+                    CLEAR_COOKIES + " BOOLEAN, " +
+                    CLEAR_DOM_STORAGE + " BOOLEAN, " +
+                    CLEAR_FORM_DATA + " BOOLEAN, " +
+                    CLEAR_CACHE + " BOOLEAN, " +
+                    HOMEPAGE + " TEXT, " +
+                    DEFAULT_FONT_SIZE + " TEXT, " +
+                    SWIPE_TO_REFRESH + " BOOLEAN, " +
+                    DISPLAY_ADDITIONAL_APP_BAR_ICONS + " BOOLEAN, " +
+                    DARK_THEME + " BOOLEAN, " +
+                    NIGHT_MODE + " BOOLEAN, " +
+                    DISPLAY_WEBPAGE_IMAGES + " BOOLEAN)";
+
+            // Create the export database preferences table.
+            exportDatabase.execSQL(CREATE_PREFERENCES_TABLE);
+
+            // Get a handle for the shared preference.
+            SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
+
+            // Create a ContentValues with the preferences information.
+            ContentValues preferencesContentValues = new ContentValues();
+            preferencesContentValues.put(JAVASCRIPT, sharedPreferences.getBoolean("javascript_enabled", false));
+            preferencesContentValues.put(FIRST_PARTY_COOKIES, sharedPreferences.getBoolean("first_party_cookies_enabled", false));
+            preferencesContentValues.put(THIRD_PARTY_COOKIES, sharedPreferences.getBoolean("third_party_cookies_enabled", false));
+            preferencesContentValues.put(DOM_STORAGE, sharedPreferences.getBoolean("dom_storage_enabled", false));
+            preferencesContentValues.put(SAVE_FORM_DATA, sharedPreferences.getBoolean("save_form_data_enabled", false));  // Save form data can be removed once the minimum API >= 26.
+            preferencesContentValues.put(USER_AGENT, sharedPreferences.getString("user_agent", "Privacy Browser"));
+            preferencesContentValues.put(CUSTOM_USER_AGENT, sharedPreferences.getString("custom_user_agent", "PrivacyBrowser/1.0"));
+            preferencesContentValues.put(INCOGNITO_MODE, sharedPreferences.getBoolean("incognito_mode", false));
+            preferencesContentValues.put(DO_NOT_TRACK, sharedPreferences.getBoolean("do_not_track", false));
+            preferencesContentValues.put(ALLOW_SCREENSHOTS, sharedPreferences.getBoolean("allow_screenshots", false));
+            preferencesContentValues.put(EASYLIST, sharedPreferences.getBoolean("easylist", true));
+            preferencesContentValues.put(EASYPRIVACY, sharedPreferences.getBoolean("easyprivacy", true));
+            preferencesContentValues.put(FANBOYS_ANNOYANCE_LIST, sharedPreferences.getBoolean("fanboy_annoyance_list", true));
+            preferencesContentValues.put(FANBOYS_SOCIAL_BLOCKING_LIST, sharedPreferences.getBoolean("fanboy_social_blocking_list", true));
+            preferencesContentValues.put(ULTRAPRIVACY, sharedPreferences.getBoolean("ultraprivacy", true));
+            preferencesContentValues.put(BLOCK_ALL_THIRD_PARTY_REQUESTS, sharedPreferences.getBoolean("block_all_third_party_requests", false));
+            preferencesContentValues.put(PROXY_THROUGH_ORBOT, sharedPreferences.getBoolean("proxy_through_orbot", false));
+            preferencesContentValues.put(TOR_HOMEPAGE, sharedPreferences.getString("tor_homepage", "http://ulrn6sryqaifefld.onion/"));
+            preferencesContentValues.put(TOR_SEARCH, sharedPreferences.getString("tor_search", "http://ulrn6sryqaifefld.onion/?q="));
+            preferencesContentValues.put(TOR_SEARCH_CUSTOM_URL, sharedPreferences.getString("tor_search_custom_url", ""));
+            preferencesContentValues.put(SEARCH, sharedPreferences.getString("search", "https://searx.me/?q="));
+            preferencesContentValues.put(SEARCH_CUSTOM_URL, sharedPreferences.getString("search_custom_url", ""));
+            preferencesContentValues.put(FULL_SCREEN_BROWSING_MODE, sharedPreferences.getBoolean("full_screen_browsing_mode", false));
+            preferencesContentValues.put(HIDE_SYSTEM_BARS, sharedPreferences.getBoolean("hide_system_bars", false));
+            preferencesContentValues.put(TRANSLUCENT_NAVIGATION_BAR, sharedPreferences.getBoolean("translucent_navigation_bar", true));
+            preferencesContentValues.put(CLEAR_EVERYTHING, sharedPreferences.getBoolean("clear_everything", true));
+            preferencesContentValues.put(CLEAR_COOKIES, sharedPreferences.getBoolean("clear_cookies", true));
+            preferencesContentValues.put(CLEAR_DOM_STORAGE, sharedPreferences.getBoolean("clear_dom_storage", true));
+            preferencesContentValues.put(CLEAR_FORM_DATA, sharedPreferences.getBoolean("clear_form_data", true));  // Clear form data can be removed once the minimum API >= 26.
+            preferencesContentValues.put(CLEAR_CACHE, sharedPreferences.getBoolean("clear_cache", true));
+            preferencesContentValues.put(HOMEPAGE, sharedPreferences.getString("homepage", "https://searx.me"));
+            preferencesContentValues.put(DEFAULT_FONT_SIZE, sharedPreferences.getString("default_font_size", "100"));
+            preferencesContentValues.put(SWIPE_TO_REFRESH, sharedPreferences.getBoolean("swipe_to_refresh", true));
+            preferencesContentValues.put(DISPLAY_ADDITIONAL_APP_BAR_ICONS, sharedPreferences.getBoolean("display_additional_app_bar_icons", false));
+            preferencesContentValues.put(DARK_THEME, sharedPreferences.getBoolean("dark_theme", false));
+            preferencesContentValues.put(NIGHT_MODE, sharedPreferences.getBoolean("night_mode", false));
+            preferencesContentValues.put(DISPLAY_WEBPAGE_IMAGES, sharedPreferences.getBoolean("display_webpage_images", true));
+
+            // Insert the preferences into the export database.
+            exportDatabase.insert(PREFERENCES_TABLE, null, preferencesContentValues);
+
+            // Close the export database.
+            exportDatabase.close();
+
+            // Convert the database file to a string.
+            String databaseString = databaseFile.toString();
+
+            // Create strings for the temporary database files.
+            String journalFileString = databaseString + "-journal";
+
+            // Get `Files` for the temporary database files.
+            File journalFile = new File(journalFileString);
+
+            // Delete the Journal file if it exists.
+            if (journalFile.exists()) {
+                //noinspection ResultOfMethodCallIgnored
+                journalFile.delete();
+            }
+
+            // Export successful.
+            return EXPORT_SUCCESSFUL;
+        } catch (Exception exception) {
+            // Return the export error.
+            return exception.toString();
+        }
+    }
+
+    public String importUnencrypted(File databaseFile, Context context){
+        try {
+            // Convert the database file to a string.  Once API >= 27 the file can be opened directly.
+            String databaseString = databaseFile.toString();
+
+            // Open the import database.
+            SQLiteDatabase importDatabase = SQLiteDatabase.openDatabase(databaseString, null, SQLiteDatabase.OPEN_READONLY);
+
+            // Get a cursor for the domains table.
+            Cursor importDomainsCursor = importDatabase.rawQuery("SELECT * FROM " + DomainsDatabaseHelper.DOMAINS_TABLE + " ORDER BY " + DomainsDatabaseHelper.DOMAIN_NAME + " ASC", null);
+
+            // Delete the current domains database.
+            context.deleteDatabase(DomainsDatabaseHelper.DOMAINS_DATABASE);
+
+            // Create a new domains database.  The `0` specifies the database version, but that is ignored and set instead using a constant in `DomainsDatabaseHelper`.
+            DomainsDatabaseHelper domainsDatabaseHelper = new DomainsDatabaseHelper(context, null, null, 0);
+
+            // Move to the first domain.
+            importDomainsCursor.moveToFirst();
+
+            // Copy the data from the import domains cursor into the domains database.
+            for (int i = 0; i < importDomainsCursor.getCount(); i++) {
+                // Extract the record from the cursor and store the data in a ContentValues.
+                ContentValues domainsContentValues = new ContentValues();
+                domainsContentValues.put(DomainsDatabaseHelper.DOMAIN_NAME, importDomainsCursor.getString(importDomainsCursor.getColumnIndex(DomainsDatabaseHelper.DOMAIN_NAME)));
+                domainsContentValues.put(DomainsDatabaseHelper.ENABLE_JAVASCRIPT, importDomainsCursor.getInt(importDomainsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_JAVASCRIPT)));
+                domainsContentValues.put(DomainsDatabaseHelper.ENABLE_FIRST_PARTY_COOKIES, importDomainsCursor.getInt(importDomainsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FIRST_PARTY_COOKIES)));
+                domainsContentValues.put(DomainsDatabaseHelper.ENABLE_THIRD_PARTY_COOKIES, importDomainsCursor.getInt(importDomainsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_THIRD_PARTY_COOKIES)));
+                domainsContentValues.put(DomainsDatabaseHelper.ENABLE_DOM_STORAGE, importDomainsCursor.getInt(importDomainsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_DOM_STORAGE)));
+                domainsContentValues.put(DomainsDatabaseHelper.ENABLE_FORM_DATA, importDomainsCursor.getInt(importDomainsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FORM_DATA)));
+                domainsContentValues.put(DomainsDatabaseHelper.ENABLE_EASYLIST, importDomainsCursor.getInt(importDomainsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_EASYLIST)));
+                domainsContentValues.put(DomainsDatabaseHelper.ENABLE_EASYPRIVACY, importDomainsCursor.getInt(importDomainsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_EASYPRIVACY)));
+                domainsContentValues.put(DomainsDatabaseHelper.ENABLE_FANBOYS_ANNOYANCE_LIST,
+                        importDomainsCursor.getInt(importDomainsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FANBOYS_ANNOYANCE_LIST)));
+                domainsContentValues.put(DomainsDatabaseHelper.ENABLE_FANBOYS_SOCIAL_BLOCKING_LIST,
+                        importDomainsCursor.getInt(importDomainsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FANBOYS_SOCIAL_BLOCKING_LIST)));
+                domainsContentValues.put(DomainsDatabaseHelper.ENABLE_ULTRAPRIVACY, importDomainsCursor.getInt(importDomainsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_ULTRAPRIVACY)));
+                domainsContentValues.put(DomainsDatabaseHelper.BLOCK_ALL_THIRD_PARTY_REQUESTS,
+                        importDomainsCursor.getInt(importDomainsCursor.getColumnIndex(DomainsDatabaseHelper.BLOCK_ALL_THIRD_PARTY_REQUESTS)));
+                domainsContentValues.put(DomainsDatabaseHelper.USER_AGENT, importDomainsCursor.getString(importDomainsCursor.getColumnIndex(DomainsDatabaseHelper.USER_AGENT)));
+                domainsContentValues.put(DomainsDatabaseHelper.FONT_SIZE, importDomainsCursor.getInt(importDomainsCursor.getColumnIndex(DomainsDatabaseHelper.FONT_SIZE)));
+                domainsContentValues.put(DomainsDatabaseHelper.SWIPE_TO_REFRESH, importDomainsCursor.getInt(importDomainsCursor.getColumnIndex(DomainsDatabaseHelper.SWIPE_TO_REFRESH)));
+                domainsContentValues.put(DomainsDatabaseHelper.NIGHT_MODE, importDomainsCursor.getInt(importDomainsCursor.getColumnIndex(DomainsDatabaseHelper.NIGHT_MODE)));
+                domainsContentValues.put(DomainsDatabaseHelper.DISPLAY_IMAGES, importDomainsCursor.getInt(importDomainsCursor.getColumnIndex(DomainsDatabaseHelper.DISPLAY_IMAGES)));
+                domainsContentValues.put(DomainsDatabaseHelper.PINNED_SSL_CERTIFICATE, importDomainsCursor.getInt(importDomainsCursor.getColumnIndex(DomainsDatabaseHelper.PINNED_SSL_CERTIFICATE)));
+                domainsContentValues.put(DomainsDatabaseHelper.SSL_ISSUED_TO_COMMON_NAME, importDomainsCursor.getString(importDomainsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_TO_COMMON_NAME)));
+                domainsContentValues.put(DomainsDatabaseHelper.SSL_ISSUED_TO_ORGANIZATION, importDomainsCursor.getString(importDomainsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_TO_ORGANIZATION)));
+                domainsContentValues.put(DomainsDatabaseHelper.SSL_ISSUED_TO_ORGANIZATIONAL_UNIT,
+                        importDomainsCursor.getString(importDomainsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_TO_ORGANIZATIONAL_UNIT)));
+                domainsContentValues.put(DomainsDatabaseHelper.SSL_ISSUED_BY_COMMON_NAME, importDomainsCursor.getString(importDomainsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_BY_COMMON_NAME)));
+                domainsContentValues.put(DomainsDatabaseHelper.SSL_ISSUED_BY_ORGANIZATION, importDomainsCursor.getString(importDomainsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_BY_ORGANIZATION)));
+                domainsContentValues.put(DomainsDatabaseHelper.SSL_ISSUED_BY_ORGANIZATIONAL_UNIT,
+                        importDomainsCursor.getString(importDomainsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_BY_ORGANIZATIONAL_UNIT)));
+                domainsContentValues.put(DomainsDatabaseHelper.SSL_START_DATE, importDomainsCursor.getLong(importDomainsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_START_DATE)));
+                domainsContentValues.put(DomainsDatabaseHelper.SSL_END_DATE, importDomainsCursor.getLong(importDomainsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_END_DATE)));
+
+                // Insert the record into the export database.
+                domainsDatabaseHelper.addDomain(domainsContentValues);
+
+                // Advance to the next record.
+                importDomainsCursor.moveToNext();
+            }
+
+            // Close the domains cursor.
+            importDomainsCursor.close();
+
+            // Close the domains database.
+            domainsDatabaseHelper.close();
+
+
+            // Get a cursor for the bookmarks table.
+            Cursor importBookmarksCursor = importDatabase.rawQuery("SELECT * FROM " + BookmarksDatabaseHelper.BOOKMARKS_TABLE, null);
+
+            // Delete the current bookmarks database.
+            context.deleteDatabase(BookmarksDatabaseHelper.BOOKMARKS_DATABASE);
+
+            // Create a new bookmarks database.  The `0` specifies the database version, but that is ignored and set instead using a constant in `BookmarksDatabaseHelper`.
+            BookmarksDatabaseHelper bookmarksDatabaseHelper = new BookmarksDatabaseHelper(context, null, null, 0);
+
+            // Move to the first bookmark.
+            importBookmarksCursor.moveToFirst();
+
+            // Copy the data from the import bookmarks cursor into the bookmarks database.
+            for (int i = 0; i < importBookmarksCursor.getCount(); i++) {
+                // Extract the record from the cursor and store the data in a ContentValues.
+                ContentValues bookmarksContentValues = new ContentValues();
+                bookmarksContentValues.put(BookmarksDatabaseHelper.BOOKMARK_NAME, importBookmarksCursor.getString(importBookmarksCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME)));
+                bookmarksContentValues.put(BookmarksDatabaseHelper.BOOKMARK_URL, importBookmarksCursor.getString(importBookmarksCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_URL)));
+                bookmarksContentValues.put(BookmarksDatabaseHelper.PARENT_FOLDER, importBookmarksCursor.getString(importBookmarksCursor.getColumnIndex(BookmarksDatabaseHelper.PARENT_FOLDER)));
+                bookmarksContentValues.put(BookmarksDatabaseHelper.DISPLAY_ORDER, importBookmarksCursor.getInt(importBookmarksCursor.getColumnIndex(BookmarksDatabaseHelper.DISPLAY_ORDER)));
+                bookmarksContentValues.put(BookmarksDatabaseHelper.IS_FOLDER, importBookmarksCursor.getInt(importBookmarksCursor.getColumnIndex(BookmarksDatabaseHelper.IS_FOLDER)));
+                bookmarksContentValues.put(BookmarksDatabaseHelper.FAVORITE_ICON, importBookmarksCursor.getBlob(importBookmarksCursor.getColumnIndex(BookmarksDatabaseHelper.FAVORITE_ICON)));
+
+                // Insert the record into the export database.
+                bookmarksDatabaseHelper.createBookmark(bookmarksContentValues);
+
+                // Advance to the next record.
+                importBookmarksCursor.moveToNext();
+            }
+
+            // Close the bookmarks cursor.
+            importBookmarksCursor.close();
+
+            // Close the bookmarks database.
+            bookmarksDatabaseHelper.close();
+
+
+            // Get a handle for the shared preference.
+            SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
+
+            // Get a cursor for the bookmarks table.
+            Cursor importPreferencesCursor = importDatabase.rawQuery("SELECT * FROM " + PREFERENCES_TABLE, null);
+
+            // Move to the first preference.
+            importPreferencesCursor.moveToFirst();
+
+            // Import the preference data.
+            sharedPreferences.edit()
+                    .putBoolean("javascript_enabled", importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndex(JAVASCRIPT)) == 1)
+                    .putBoolean("first_party_cookies_enabled", importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndex(FIRST_PARTY_COOKIES)) == 1)
+                    .putBoolean("third_party_cookies_enabled", importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndex(THIRD_PARTY_COOKIES)) == 1)
+                    .putBoolean("dom_storage_enabled", importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndex(DOM_STORAGE)) == 1)
+                    // Save form data can be removed once the minimum API >= 26.
+                    .putBoolean("save_form_data_enabled", importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndex(SAVE_FORM_DATA)) == 1)
+                    .putString("user_agent", importPreferencesCursor.getString(importPreferencesCursor.getColumnIndex(USER_AGENT)))
+                    .putString("custom_user_agent", importPreferencesCursor.getString(importPreferencesCursor.getColumnIndex(CUSTOM_USER_AGENT)))
+                    .putBoolean("incognito_mode", importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndex(INCOGNITO_MODE)) == 1)
+                    .putBoolean("do_not_track", importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndex(DO_NOT_TRACK)) == 1)
+                    .putBoolean("allow_screenshots", importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndex(ALLOW_SCREENSHOTS)) == 1)
+                    .putBoolean("easylist", importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndex(EASYLIST)) == 1)
+                    .putBoolean("easyprivacy", importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndex(EASYPRIVACY)) == 1)
+                    .putBoolean("fanboy_annoyance_list", importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndex(FANBOYS_ANNOYANCE_LIST)) == 1)
+                    .putBoolean("fanboy_social_blocking_list", importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndex(FANBOYS_SOCIAL_BLOCKING_LIST)) == 1)
+                    .putBoolean("ultraprivacy", importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndex(ULTRAPRIVACY)) == 1)
+                    .putBoolean("block_all_third_party_requests", importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndex(BLOCK_ALL_THIRD_PARTY_REQUESTS)) == 1)
+                    .putBoolean("proxy_through_orbot", importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndex(PROXY_THROUGH_ORBOT)) == 1)
+                    .putString("tor_homepage", importPreferencesCursor.getString(importPreferencesCursor.getColumnIndex(TOR_HOMEPAGE)))
+                    .putString("tor_search", importPreferencesCursor.getString(importPreferencesCursor.getColumnIndex(TOR_SEARCH)))
+                    .putString("tor_search_custom_url", importPreferencesCursor.getString(importPreferencesCursor.getColumnIndex(TOR_SEARCH_CUSTOM_URL)))
+                    .putString("search", importPreferencesCursor.getString(importPreferencesCursor.getColumnIndex(SEARCH)))
+                    .putString("search_custom_url", importPreferencesCursor.getString(importPreferencesCursor.getColumnIndex(SEARCH_CUSTOM_URL)))
+                    .putBoolean("full_screen_browsing_mode", importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndex(FULL_SCREEN_BROWSING_MODE)) == 1)
+                    .putBoolean("hide_system_bars", importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndex(HIDE_SYSTEM_BARS)) == 1)
+                    .putBoolean("translucent_navigation_bar", importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndex(TRANSLUCENT_NAVIGATION_BAR)) == 1)
+                    .putBoolean("clear_everything", importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndex(CLEAR_EVERYTHING)) == 1)
+                    .putBoolean("clear_cookies", importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndex(CLEAR_COOKIES)) == 1)
+                    .putBoolean("clear_dom_storage", importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndex(CLEAR_DOM_STORAGE)) == 1)
+                    // Clear form data can be removed once the minimum API >= 26.
+                    .putBoolean("clear_form_data", importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndex(CLEAR_FORM_DATA)) == 1)
+                    .putBoolean("clear_cache", importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndex(CLEAR_CACHE)) == 1)
+                    .putString("homepage", importPreferencesCursor.getString(importPreferencesCursor.getColumnIndex(HOMEPAGE)))
+                    .putString("default_font_size", importPreferencesCursor.getString(importPreferencesCursor.getColumnIndex(DEFAULT_FONT_SIZE)))
+                    .putBoolean("swipe_to_refresh", importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndex(SWIPE_TO_REFRESH)) == 1)
+                    .putBoolean("display_additional_app_bar_icons", importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndex(DISPLAY_ADDITIONAL_APP_BAR_ICONS)) == 1)
+                    .putBoolean("dark_theme", importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndex(DARK_THEME)) == 1)
+                    .putBoolean("night_mode", importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndex(NIGHT_MODE)) == 1)
+                    .putBoolean("display_webpage_images", importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndex(DISPLAY_WEBPAGE_IMAGES)) == 1).apply();
+
+            // Close the preferences cursor.
+            importPreferencesCursor.close();
+
+
+            // Close the import database.
+            importDatabase.close();
+
+            // Create strings for the temporary database files.
+            String shmFileString = databaseString + "-shm";
+            String walFileString = databaseString + "-wal";
+            String journalFileString = databaseString + "-journal";
+
+            // Get `Files` for the temporary database files.
+            File shmFile = new File(shmFileString);
+            File walFile = new File(walFileString);
+            File journalFile = new File(journalFileString);
+
+            // Delete the Shared Memory file if it exists.
+            if (shmFile.exists()) {
+                //noinspection ResultOfMethodCallIgnored
+                shmFile.delete();
+            }
+
+            // Delete the Write Ahead Log file if it exists.
+            if (walFile.exists()) {
+                //noinspection ResultOfMethodCallIgnored
+                walFile.delete();
+            }
+
+            // Delete the Journal file if it exists.
+            if (journalFile.exists()) {
+                //noinspection ResultOfMethodCallIgnored
+                journalFile.delete();
+            }
+
+            // Import successful.
+            return IMPORT_SUCCESSFUL;
+        } catch (Exception exception) {
+            // Return the import error.
+            return exception.toString();
+        }
+    }
+}
index f22f38a99ba4f157e46f03722dd75f516a1fe495..d7947b1ef6162f7dcf0eaaebcfb2752a4975434b 100644 (file)
@@ -4,15 +4,15 @@
 <vector
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:tools="http://schemas.android.com/tools"
-    android:autoMirrored="true"
     android:height="24dp"
     android:width="24dp"
     android:viewportHeight="24.0"
     android:viewportWidth="24.0"
+    android:autoMirrored="true"
     tools:ignore="VectorRaster" >
 
-    <!-- We have to use a hard coded color until API >= 21.  Then we can use `@color`. -->
+    <!-- A hard coded color must be used until API >= 21.  Then `@color` may be used. -->
     <path
         android:fillColor="#FF9E9E9E"
-        android:pathData="M21,19V5c0,-1.1 -0.9,-2 -2,-2H5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2zM8.5,13.5l2.5,3.01L14.5,12l4.5,6H5l3.5,-4.5z"/>
+        android:pathData="M21,19V5c0,-1.1 -0.9,-2 -2,-2H5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2zM8.5,13.5l2.5,3.01L14.5,12l4.5,6H5l3.5,-4.5z" />
 </vector>
index e1baa4c486933bd94c1c142c24a9d073b4306e53..95f5b1a6e7036a093e0428e6230c03ae33f6e894 100644 (file)
@@ -4,15 +4,15 @@
 <vector
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:tools="http://schemas.android.com/tools"
-    android:autoMirrored="true"
     android:height="24dp"
     android:width="24dp"
     android:viewportHeight="24.0"
     android:viewportWidth="24.0"
+    android:autoMirrored="true"
     tools:ignore="VectorRaster" >
 
-    <!-- We have to use a hard coded color until API >= 21.  Then we can use `@color`. -->
+    <!-- A hard coded color must be used until API >= 21.  Then `@color` may be used. -->
     <path
         android:fillColor="#FF757575"
-        android:pathData="M21,19V5c0,-1.1 -0.9,-2 -2,-2H5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2zM8.5,13.5l2.5,3.01L14.5,12l4.5,6H5l3.5,-4.5z"/>
+        android:pathData="M21,19V5c0,-1.1 -0.9,-2 -2,-2H5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2zM8.5,13.5l2.5,3.01L14.5,12l4.5,6H5l3.5,-4.5z" />
 </vector>
index 9167e1e9e0a7d5d607dc6d703a2ebb842bd71307..cb45f47612685ac862eb30e4d5520008fb68a79e 100644 (file)
@@ -4,15 +4,15 @@
 <vector
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:tools="http://schemas.android.com/tools"
-    android:autoMirrored="true"
     android:height="24dp"
     android:width="24dp"
     android:viewportHeight="24.0"
     android:viewportWidth="24.0"
+    android:autoMirrored="true"
     tools:ignore="VectorRaster" >
 
-    <!-- We have to use a hard coded color until API >= 21.  Then we can use `@color`. -->
+    <!-- A hard coded color must be used until API >= 21.  Then `@color` may be used. -->
     <path
         android:fillColor="#FF1E88E5"
-        android:pathData="M21,19V5c0,-1.1 -0.9,-2 -2,-2H5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2zM8.5,13.5l2.5,3.01L14.5,12l4.5,6H5l3.5,-4.5z"/>
+        android:pathData="M21,19V5c0,-1.1 -0.9,-2 -2,-2H5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2zM8.5,13.5l2.5,3.01L14.5,12l4.5,6H5l3.5,-4.5z" />
 </vector>
index 94f0b289cad860367b06f93690691981fe16c938..6241eb9fd0956d2598dbe23b69e852313f23401f 100644 (file)
@@ -4,15 +4,15 @@
 <vector
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:tools="http://schemas.android.com/tools"
-    android:autoMirrored="true"
     android:height="24dp"
     android:width="24dp"
     android:viewportHeight="24.0"
     android:viewportWidth="24.0"
+    android:autoMirrored="true"
     tools:ignore="VectorRaster" >
 
-    <!-- We have to use a hard coded color until API >= 21.  Then we can use `@color`. -->
+    <!-- A hard coded color must be used until API >= 21.  Then `@color` may be used. -->
     <path
         android:fillColor="#FF1565C0"
-        android:pathData="M21,19V5c0,-1.1 -0.9,-2 -2,-2H5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2zM8.5,13.5l2.5,3.01L14.5,12l4.5,6H5l3.5,-4.5z"/>
+        android:pathData="M21,19V5c0,-1.1 -0.9,-2 -2,-2H5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2zM8.5,13.5l2.5,3.01L14.5,12l4.5,6H5l3.5,-4.5z" />
 </vector>
diff --git a/app/src/main/res/drawable/import_export_dark.xml b/app/src/main/res/drawable/import_export_dark.xml
new file mode 100644 (file)
index 0000000..21cdea8
--- /dev/null
@@ -0,0 +1,18 @@
+<!-- `import_export_dark.xml` comes from the Android Material icon set, where it is called `import_export`.  It is released under the Apache License 2.0. -->
+
+<!-- `tools:ignore="VectorRaster"` removes the lint warning about `android:autoMirrored="true"` not applying to API < 21. -->
+<vector
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:height="24dp"
+    android:width="24dp"
+    android:viewportHeight="24.0"
+    android:viewportWidth="24.0"
+    android:autoMirrored="true"
+    tools:ignore="VectorRaster" >
+
+    <!-- A hard coded color must be used until API >= 21.  Then `@color` may be used. -->
+    <path
+        android:fillColor="#FFE0E0E0"
+        android:pathData="M9,3L5,6.99h3L8,14h2L10,6.99h3L9,3zM16,17.01L16,10h-2v7.01h-3L15,21l4,-3.99h-3z" />
+</vector>
diff --git a/app/src/main/res/drawable/import_export_light.xml b/app/src/main/res/drawable/import_export_light.xml
new file mode 100644 (file)
index 0000000..602cf37
--- /dev/null
@@ -0,0 +1,18 @@
+<!-- `import_export_light.xml` comes from the Android Material icon set, where it is called `import_export`.  It is released under the Apache License 2.0. -->
+
+<!-- `tools:ignore="VectorRaster"` removes the lint warning about `android:autoMirrored="true"` not applying to API < 21. -->
+<vector
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:height="24dp"
+    android:width="24dp"
+    android:viewportHeight="24.0"
+    android:viewportWidth="24.0"
+    android:autoMirrored="true"
+    tools:ignore="VectorRaster" >
+
+    <!-- A hard coded color must be used until API >= 21.  Then `@color` may be used. -->
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M9,3L5,6.99h3L8,14h2L10,6.99h3L9,3zM16,17.01L16,10h-2v7.01h-3L15,21l4,-3.99h-3z" />
+</vector>
diff --git a/app/src/main/res/layout/import_export_coordinatorlayout.xml b/app/src/main/res/layout/import_export_coordinatorlayout.xml
new file mode 100644 (file)
index 0000000..76464fe
--- /dev/null
@@ -0,0 +1,210 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+  Copyright © 2018 Soren Stoutner <soren@stoutner.com>.
+
+  This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
+
+  Privacy Browser is free software: you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  Privacy Browser is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with Privacy Browser.  If not, see <http://www.gnu.org/licenses/>. -->
+
+<!-- `android:fitsSystemWindows="true"` moves the AppBar below the status bar.
+    When it is specified the theme should include `<item name="android:windowTranslucentStatus">true</item>` to make the status bar a transparent, darkened overlay. -->
+<android.support.design.widget.CoordinatorLayout
+    android:id="@+id/import_export_coordinatorlayout"
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_height="match_parent"
+    android:layout_width="match_parent"
+    android:fitsSystemWindows="true" >
+
+    <!-- the `LinearLayout` with `orientation="vertical"` moves the content below the `AppBarLayout`. -->
+    <LinearLayout
+        android:layout_height="match_parent"
+        android:layout_width="match_parent"
+        android:orientation="vertical" >
+
+        <android.support.design.widget.AppBarLayout
+            android:id="@+id/import_export_appbarlayout"
+            android:layout_height="wrap_content"
+            android:layout_width="match_parent" >
+
+            <android.support.v7.widget.Toolbar
+                android:id="@+id/import_export_toolbar"
+                android:layout_height="wrap_content"
+                android:layout_width="match_parent"
+                android:background="?attr/colorPrimaryDark"
+                android:theme="?attr/appBarTextTheme"
+                app:popupTheme="?attr/popupsTheme" />
+        </android.support.design.widget.AppBarLayout>
+
+        <ScrollView
+            android:layout_height="match_parent"
+            android:layout_width="match_parent" >
+
+            <!-- Align the cards vertically. -->
+            <LinearLayout
+                android:layout_height="wrap_content"
+                android:layout_width="match_parent"
+                android:orientation="vertical" >
+
+                <!-- The export card. -->
+                <android.support.v7.widget.CardView
+                    android:layout_height="wrap_content"
+                    android:layout_width="match_parent"
+                    android:layout_marginTop="10dp"
+                    android:layout_marginBottom="5dp"
+                    android:layout_marginStart="10dp"
+                    android:layout_marginEnd="10dp" >
+
+                    <!-- Align the contents of the card vertically. -->
+                    <LinearLayout
+                        android:layout_height="match_parent"
+                        android:layout_width="match_parent"
+                        android:orientation="vertical"
+                        android:layout_margin="10dp" >
+
+                        <TextView
+                            android:layout_width="wrap_content"
+                            android:layout_height="wrap_content"
+                            android:layout_gravity="center_horizontal"
+                            android:layout_marginBottom="6dp"
+                            android:text="@string/export_settings"
+                            android:textSize="25sp"
+                            android:textStyle="bold"
+                            android:textColor="?android:textColorPrimary" />
+
+                        <!-- Align the export EditText and the select file button horizontally. -->
+                        <LinearLayout
+                            android:layout_height="wrap_content"
+                            android:layout_width="match_parent"
+                            android:orientation="horizontal" >
+
+                            <!-- `android.support.design.widget.TextInputLayout` makes the `android:hint` float above the `EditText`. -->
+                            <android.support.design.widget.TextInputLayout
+                                android:layout_height="wrap_content"
+                                android:layout_width="0dp"
+                                android:layout_weight="1" >
+
+                                <!-- `android:inputType="textUri" disables spell check and places an `/` on the main keyboard. -->
+                                <android.support.design.widget.TextInputEditText
+                                    android:id="@+id/export_file_edittext"
+                                    android:layout_height="wrap_content"
+                                    android:layout_width="match_parent"
+                                    android:hint="@string/export_file_name"
+                                    android:inputType="textMultiLine|textUri" />
+                            </android.support.design.widget.TextInputLayout>
+
+                            <Button
+                                android:id="@+id/select_export_file"
+                                android:layout_height="wrap_content"
+                                android:layout_width="wrap_content"
+                                android:layout_gravity="center_vertical"
+                                android:text="@string/browse"
+                                android:onClick="exportBrowse" />
+                        </LinearLayout>
+
+                        <Button
+                            android:id="@+id/export_button"
+                            android:layout_height="wrap_content"
+                            android:layout_width="wrap_content"
+                            android:layout_gravity="center_horizontal"
+                            android:text="@string/export"
+                            android:textSize="18sp"
+                            android:onClick="onClickExport" />
+                    </LinearLayout>
+                </android.support.v7.widget.CardView>
+
+                <!-- The import card. -->
+                <android.support.v7.widget.CardView
+                    android:layout_height="wrap_content"
+                    android:layout_width="match_parent"
+                    android:layout_marginTop="5dp"
+                    android:layout_marginBottom="10dp"
+                    android:layout_marginStart="10dp"
+                    android:layout_marginEnd="10dp" >
+
+                    <!-- Align the contents of the card vertically. -->
+                    <LinearLayout
+                        android:layout_height="match_parent"
+                        android:layout_width="match_parent"
+                        android:orientation="vertical"
+                        android:layout_margin="10dp" >
+
+                        <TextView
+                            android:layout_width="wrap_content"
+                            android:layout_height="wrap_content"
+                            android:layout_gravity="center_horizontal"
+                            android:layout_marginBottom="6dp"
+                            android:text="@string/import_settings"
+                            android:textSize="25sp"
+                            android:textStyle="bold"
+                            android:textColor="?android:textColorPrimary" />
+
+                        <!-- Align the import EditText and the select file button horizontally. -->
+                        <LinearLayout
+                            android:layout_height="wrap_content"
+                            android:layout_width="match_parent"
+                            android:orientation="horizontal" >
+
+                            <!-- `android.support.design.widget.TextInputLayout` makes the `android:hint` float above the `EditText`. -->
+                            <android.support.design.widget.TextInputLayout
+                                android:layout_height="wrap_content"
+                                android:layout_width="0dp"
+                                android:layout_weight="1" >
+
+                                <!-- `android:inputType="textUri" disables spell check and places an `/` on the main keyboard. -->
+                                <android.support.design.widget.TextInputEditText
+                                    android:id="@+id/import_file_edittext"
+                                    android:layout_height="wrap_content"
+                                    android:layout_width="match_parent"
+                                    android:hint="@string/import_file_name"
+                                    android:inputType="textMultiLine|textUri" />
+                            </android.support.design.widget.TextInputLayout>
+
+                            <Button
+                                android:id="@+id/select_import_file"
+                                android:layout_height="wrap_content"
+                                android:layout_width="wrap_content"
+                                android:layout_gravity="center_vertical"
+                                android:text="@string/browse"
+                                android:onClick="importBrowse" />
+                        </LinearLayout>
+
+                        <!-- `import` is a reserved word and cannot be used for the `onClick` name. -->
+                        <Button
+                            android:id="@+id/import_button"
+                            android:layout_height="wrap_content"
+                            android:layout_width="wrap_content"
+                            android:layout_gravity="center_horizontal"
+                            android:text="@string/import_button"
+                            android:textSize="18sp"
+                            android:onClick="onClickImport" />
+                    </LinearLayout>
+                </android.support.v7.widget.CardView>
+
+                <TextView
+                    android:id="@+id/import_export_storage_permission_textview"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_gravity="center_horizontal"
+                    android:layout_marginBottom="10dp"
+                    android:layout_marginStart="15dp"
+                    android:layout_marginEnd="15dp"
+                    android:text="@string/file_permissions_explanation"
+                    android:textColor="?android:textColorPrimary"
+                    android:textAlignment="center" />
+            </LinearLayout>
+        </ScrollView>
+    </LinearLayout>
+</android.support.design.widget.CoordinatorLayout>
\ No newline at end of file
index 7f11c6cdc2ea1c45dc2118e0d4818071bd67b596..126fd5fa9755bca8e39f5ca57a87150aa4822316 100644 (file)
@@ -21,7 +21,7 @@
 <!-- `android:fitsSystemWindows="true"` moves the AppBar below the status bar.
     When it is specified the theme should include `<item name="android:windowTranslucentStatus">true</item>` to make the status bar a transparent, darkened overlay. -->
 <android.support.design.widget.CoordinatorLayout
-    android:id="@+id/blocklists_coordinatorlayout"
+    android:id="@+id/requests_coordinatorlayout"
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
     android:layout_height="match_parent"
         android:orientation="vertical" >
 
         <android.support.design.widget.AppBarLayout
-            android:id="@+id/blocklists_appbarlayout"
+            android:id="@+id/requests_appbarlayout"
             android:layout_height="wrap_content"
             android:layout_width="match_parent" >
 
             <android.support.v7.widget.Toolbar
-                android:id="@+id/blocklists_toolbar"
+                android:id="@+id/requests_toolbar"
                 android:layout_height="wrap_content"
                 android:layout_width="match_parent"
                 android:background="?attr/colorPrimaryDark"
@@ -50,7 +50,7 @@
 
         <!-- `android:dividerHeight` must be at least `1dp` or the `ListView` is inconsistent in calculating how many bookmarks are displayed. -->
         <ListView
-            android:id="@+id/resource_requests_listview"
+            android:id="@+id/requests_listview"
             android:layout_height="match_parent"
             android:layout_width="match_parent"
             android:divider="@color/transparent"
index 9032af7eed798bf921e201b907701f4649057424..d17d6585dcf4b3c91e898babcd1c61572b3633de 100644 (file)
@@ -46,8 +46,7 @@
 
     <!-- If a group has an id, a line is drawn above it in the navigation view. -->
     <group
-        android:id="@+id/navigationGroup1" >
-
+        android:id="@+id/features_group" >
         <item
             android:id="@+id/requests"
             android:title="@string/requests"
             android:title="@string/downloads"
             android:icon="@drawable/downloads_light"
             android:orderInCategory="60" />
+    </group>
 
+    <!-- If a group has an id, a line is drawn above it in the navigation view. -->
+    <group
+        android:id="@+id/settings_group" >
         <item
             android:id="@+id/domains"
             android:title="@string/domains"
             android:title="@string/settings"
             android:icon="@drawable/settings"
             android:orderInCategory="80" />
+
+        <item
+            android:id="@+id/import_export"
+            android:title="@string/import_export"
+            android:icon="@drawable/import_export_light"
+            android:orderInCategory="90" />
     </group>
 
     <!-- If a group has an id, a line is drawn above it in the navigation view. -->
     <group
-        android:id="@+id/navigationGroup2" >
+        android:id="@+id/info_group" >
         <item
             android:id="@+id/guide"
             android:title="@string/guide"
             android:icon="@drawable/guide"
-            android:orderInCategory="90" />
+            android:orderInCategory="100" />
 
         <item
             android:id="@+id/about"
             android:title="@string/about"
             android:icon="@drawable/about_light"
-            android:orderInCategory="100" />
+            android:orderInCategory="110" />
     </group>
 
     <!-- If a group has an id, a line is drawn above it in the navigation view. -->
     <group
-        android:id="@+id/navigationGroup3" >
+        android:id="@+id/exit_group" >
         <item
             android:id="@+id/clearAndExit"
             android:title="@string/clear_and_exit"
             android:icon="@drawable/clear_and_exit"
-            android:orderInCategory="110" />
+            android:orderInCategory="120" />
     </group>
 </menu>
\ No newline at end of file
index c4fcfb96e60a7fe981d55e8a84c02824789af695..1b011f47963856ae8caccef144e01aeb2bfa9289 100644 (file)
     <string name="bookmarks">Bookmarks</string>
     <string name="downloads">Downloads</string>
     <string name="settings">Settings</string>
+    <string name="import_export">Import/Export</string>
     <string name="guide">Guide</string>
     <string name="about">About</string>
     <string name="clear_and_exit">Clear and Exit</string>
     <string name="current_website_ssl_certificate">Current website SSL certificate</string>
     <string name="load_an_encrypted_website">Load an encrypted website before opening Domain Settings to populate the current website SSL certificate.</string>
 
+    <!-- Import/Export. -->
+    <string name="browse">Browse</string>
+    <string name="file_permissions_explanation">Accessing files in public directories requires the storage permission. Otherwise only private directories will work.</string>
+    <string name="privacy_browser_backup">Privacy Browser Backup</string>
+    <string name="export_settings">Export Settings</string>
+    <string name="import_settings">Import Settings</string>
+    <string name="export_file_name">Export file name</string>
+    <string name="import_file_name">Import file name</string>
+    <string name="export">Export</string>
+    <string name="import_button">Import</string>  <!-- `import` is a reserved word and cannot be used as the name -->
+    <string name="export_successful">Export successful.</string>
+    <string name="export_failed">Export failed:</string>
+    <string name="import_failed">Import failed:</string>
+    <string name="cannot_export">The settings cannot be exported to this location because the storage permission has not been granted.</string>
+    <string name="cannot_import">The settings cannot be imported to this location because the storage permission has not been granted.</string>
+    <string name="invalid_location">is not a valid location.</string>
+    <string name="storage_permission">Storage Permission</string>
+    <string name="storage_permission_message">Privacy Browser needs the storage permission to access public directories. If it is denied, the app’s private directory can still be used.</string>
+
     <!-- Guide. -->
     <string name="privacy_browser_guide">Privacy Browser Guide</string>
     <string name="overview">Overview</string>
     <string name="links">Links</string>
 
     <!-- Ad Control. There are no ads in the standard flavor, but these strings must exist because they are referenced in the code. -->
-    <string name="ad_id" translatable="false">Null</string>
+    <string name="google_app_id" translatable="false">Null</string>
+    <string name="ad_unit_id" translatable="false">Null</string>
     <string name="ad_consent">Ad Consent</string>
 </resources>
index 068d272d7219450675c654b0edf319164e76f61e..4da818e24c3ecbba0931d98c4acdbc69dc61f55a 100644 (file)
@@ -25,11 +25,11 @@ import android.view.View;
 
 @SuppressWarnings("unused")
 public class AdHelper {
-    public static void initializeAds(View view, Context applicationContext, FragmentManager fragmentManager, String ad_id) {
+    public static void initializeAds(View view, Context applicationContext, FragmentManager fragmentManager, String googleAppId, String adUnitId) {
         // Do nothing because this is the standard flavor.
     }
 
-    public static void loadAd(View view, Context applicationContext, String ad_id) {
+    public static void loadAd(View view, Context applicationContext, String adUnitId) {
         // Do nothing because this is the standard flavor.
     }
 
index b9bfa2cccab68cdbd2574d18bdb8f5f98cea4cd2..1ba5b9df5aa4db2512cb356713f85f712ab0389d 100644 (file)
@@ -25,7 +25,7 @@ buildscript {
         google()
     }
     dependencies {
-        classpath 'com.android.tools.build:gradle:3.1.4'
+        classpath 'com.android.tools.build:gradle:3.2.1'
 
         // NOTE: Do not place your application dependencies here; they belong
         // in the individual module build.gradle files
index 7e874e1cba4df6861a1b89cdd5cb3fe55f38c6af..bc261989891387a83e0b5bd665f1965a3cc6aaf9 100644 (file)
@@ -1,6 +1,6 @@
-#Mon Mar 26 23:47:31 MST 2018
+#Tue Sep 25 14:33:14 MST 2018
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip