diff options
| -rw-r--r-- | res/menu/browser.xml | 5 | ||||
| -rw-r--r-- | res/values/arrays_custom.xml | 28 | ||||
| -rw-r--r-- | res/values/strings.xml | 16 | ||||
| -rw-r--r-- | res/xml/privacy_security_preferences.xml | 16 | ||||
| -rw-r--r-- | src/com/android/browser/BrowserSettings.java | 169 | ||||
| -rw-r--r-- | src/com/android/browser/Controller.java | 22 | ||||
| -rw-r--r-- | src/com/android/browser/PreferenceKeys.java | 2 | ||||
| -rw-r--r-- | src/com/android/browser/UiController.java | 2 | ||||
| -rw-r--r-- | src/com/android/browser/preferences/PrivacySecurityPreferencesFragment.java | 26 |
9 files changed, 286 insertions, 0 deletions
diff --git a/res/menu/browser.xml b/res/menu/browser.xml index 27c0c81d..d019546a 100644 --- a/res/menu/browser.xml +++ b/res/menu/browser.xml @@ -63,6 +63,11 @@ android:id="@+id/ua_desktop_menu_id" android:checkable="true" android:title="@string/ua_switcher_desktop" /> + <item + android:id="@+id/cookies_whitelisted_menu_id" + android:checkable="true" + android:title="@string/site_cookies_whitelisted" + android:visible="false" /> </group> <group android:id="@+id/SNAPSHOT_MENU"> diff --git a/res/values/arrays_custom.xml b/res/values/arrays_custom.xml new file mode 100644 index 00000000..243a1229 --- /dev/null +++ b/res/values/arrays_custom.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2012-2014 The CyanogenMod Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="pref_security_site_whitelist_entries" translatable="false"> + <item>@string/pref_security_site_whitelist_cookies_never</item> + <item>@string/pref_security_site_whitelist_cookies_only</item> + <item>@string/pref_security_site_whitelist_cookies_and_data</item> + </string-array> + <string-array name="pref_security_site_whitelist_values" translatable="false"> + <item>0</item> + <item>1</item> + <item>2</item> + </string-array> +</resources> diff --git a/res/values/strings.xml b/res/values/strings.xml index d71ddac7..4614a89b 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -1015,4 +1015,20 @@ <!-- Content description for navigating up in the bookmark folder hierarchy [CHAR LIMIT=NONE] --> <string name="accessibility_button_bookmarks_folder_up">Previous folder</string> + <!-- Browser security/privacy anti-tracking label --> + <string name="pref_security_site_whitelist_cookies">Reduce tracking. Frequently delete cookies</string> + <!-- Browser security/privacy anti-tracking options --> + <string name="pref_security_site_whitelist_cookies_never">Never delete</string> + <string name="pref_security_site_whitelist_cookies_only">Delete from all sites that aren\'t in my whitelist</string> + <string name="pref_security_site_whitelist_cookies_and_data">Also delete all localstorage databases</string> + <!-- Browser security/privacy anti-tracking verbose checkbox label --> + <string name="pref_security_site_whitelist_cookies_verbose">Cookie count</string> + <!-- Browser security/privacy anti-tracking verbose checkbox summary --> + <string name="pref_security_site_whitelist_cookies_verbose_summary">Show the number of deleted and remaining cookies</string> + <!-- Browser popup menu checkbox that allows the user to whitelist cookies from this site [CHAR LIMIT=50] --> + <string name="site_cookies_whitelisted">Keep site\'s cookies</string> + <!-- Toast to inform the user of the number of cookies deleted and remaining [CHAR LIMIT=50] --> + <string name="cookies_deleted_info">cookies deleted</string> + <string name="cookies_remain_info">cookies remain</string> + </resources> diff --git a/res/xml/privacy_security_preferences.xml b/res/xml/privacy_security_preferences.xml index 26336000..26c19ffc 100644 --- a/res/xml/privacy_security_preferences.xml +++ b/res/xml/privacy_security_preferences.xml @@ -44,6 +44,22 @@ android:title="@string/pref_security_accept_cookies" android:summary="@string/pref_security_accept_cookies_summary" /> + <ListPreference + android:key="site_whitelist_cookies" + android:dependency="accept_cookies" + android:dialogTitle="@string/pref_security_site_whitelist_cookies" + android:title="@string/pref_security_site_whitelist_cookies" + android:entries="@array/pref_security_site_whitelist_entries" + android:entryValues="@array/pref_security_site_whitelist_values" + android:defaultValue="0" /> + + <CheckBoxPreference + android:key="site_whitelist_cookies_verbose" + android:defaultValue="false" + android:dependency="accept_cookies" + android:title="@string/pref_security_site_whitelist_cookies_verbose" + android:summary="@string/pref_security_site_whitelist_cookies_verbose_summary" /> + <com.android.browser.BrowserYesNoPreference android:key="privacy_clear_cookies" android:title="@string/pref_privacy_clear_cookies" diff --git a/src/com/android/browser/BrowserSettings.java b/src/com/android/browser/BrowserSettings.java index 24cc0766..fe916564 100644 --- a/src/com/android/browser/BrowserSettings.java +++ b/src/com/android/browser/BrowserSettings.java @@ -51,6 +51,14 @@ import java.lang.ref.WeakReference; import java.util.Iterator; import java.util.LinkedList; import java.util.WeakHashMap; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Set; +import java.util.Arrays; + +import android.util.Log; +import android.net.WebAddress; +import android.widget.Toast; /** * Class for managing settings @@ -58,6 +66,8 @@ import java.util.WeakHashMap; public class BrowserSettings implements OnSharedPreferenceChangeListener, PreferenceKeys { + private final static String TAG = "BrowserSettings"; + // TODO: Do something with this UserAgent stuff private static final String DESKTOP_USERAGENT = "Mozilla/5.0 (X11; " + "Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) " + @@ -502,6 +512,151 @@ public class BrowserSettings implements OnSharedPreferenceChangeListener, } } + private Set<String> getCookieWhitelist() { + requireInitialization(); + return mPrefs.getStringSet("cookies_whitelist", new HashSet<String>()); + } + + private void setCookieWhitelist(Set<String> wl) { + mPrefs.edit().putStringSet("cookies_whitelist", wl).apply(); + } + + private boolean addToCookieWhitelist(String s) { + Set<String> wl = getCookieWhitelist(); + boolean ok = wl.add(s); + setCookieWhitelist(wl); + return ok; + } + + private boolean removeFromCookieWhitelist(String s) { + Set<String> wl = getCookieWhitelist(); + boolean ok = wl.remove(s); + setCookieWhitelist(wl); + return ok; + } + + public boolean hasCookiesWhitelisted(WebView view) { + if (view == null) { + return false; + } + String url = view.getUrl(); + if (url == null) { // page hasn't finished loading yet + return false; + } + String host = new WebAddress(url).getHost(); + Set<String> wl = getCookieWhitelist(); + return wl.contains(host); + } + + public void toggleCookiesWhitelisted(WebView view) { + if (view == null) { + return; + } + if (!enableDeleteCookies()) { + return; + } + + String url = view.getUrl(); + if (url == null) { // page hasn't finished loading yet + return; + } + String host = new WebAddress(url).getHost(); + if (hasCookiesWhitelisted(view)) { + removeFromCookieWhitelist(host); + clearCookiesExceptWhitelist(); + } + else { + addToCookieWhitelist(host); + } + } + + public void clearCookiesExceptWhitelist() { + if (!enableDeleteCookies()) { + return; + } + + Set<String> domains = getCookieWhitelist(); + HashMap cookies = new HashMap(); + + // look for both domain and host cookies (domain cookies have a '.' prefix). + // query by https as this returns both http and https cookies. + String[] prefixes = { "https://.", "https://" }; + + // find all cookies in the whitelist + int savedCookies = 0; + for (String domain : domains) { + for (int p = 0; p < 2; p++) { + String url = prefixes[p] + domain; + String c = CookieManager.getInstance().getCookie(url); + if (c != null && !(c.equals("") || c.equals(null))) { + String[] cc = c.split(";"); // split into individual cookies + for (int i = 0; i < cc.length; i++) // get rid of leading blanks + cc[i] = cc[i].trim(); + Set<String> ccUniq = new HashSet<String>(Arrays.asList(cc)); + if (p == 1) { // remove domain cookies duplicated to the host + String urlDom = prefixes[0] + domain; + if (cookies.containsKey(urlDom)) { + Set<String> ccDom = (HashSet<String>)cookies.get(urlDom); + ccUniq.removeAll(ccDom); + } + } + savedCookies += ccUniq.size(); + cookies.put(url, ccUniq); + } + } + } + + int cookiesBefore = CookieManager.getInstance().countCookies(); + if (savedCookies == cookiesBefore) { + // all cookies are whitelisted cookies. our job is done. + return; + } + + // delete all cookies + CookieManager.getInstance().removeAllCookie(); + + // re-add all the whitelisted cookies + for (String domain : domains) { + for (String prefix : prefixes) { + String url = prefix + domain; + if (cookies.containsKey(url)) { + Set<String> cc = (HashSet<String>)cookies.get(url); + for (String i : cc) { + CookieManager.getInstance().setCookie(url, i + ";"); + } + } + } + } + + int cookiesAfter = CookieManager.getInstance().countCookies(); + int cookiesDeleted = cookiesBefore - cookiesAfter; + boolean munched = cookiesDeleted > 0; + + if (!munched) { // no cookies were deleted + return; + } + + // optionally clear all localstorage too + if (enableDeleteLocaldata()) { + clearDatabases(); + } + + Log.d(TAG, "clearCookiesExceptWhitelist: " + + cookiesDeleted + " cookies deleted, " + + cookiesAfter + " cookies remain"); + + // toast if we have munched cookies + if (munchToast()) { + String deleted_str = mContext.getResources() + .getString(R.string.cookies_deleted_info); + String remain_str = mContext.getResources() + .getString(R.string.cookies_remain_info); + CharSequence text = "" + cookiesDeleted + " " + deleted_str + + "\n" + cookiesAfter + " " + remain_str; + Toast.makeText(mContext, text, Toast.LENGTH_SHORT).show(); + } + } + public static int getAdjustedMinimumFontSize(int rawValue) { rawValue++; // Preference starts at 0, min font at 1 if (rawValue > 1) { @@ -809,6 +964,20 @@ public class BrowserSettings implements OnSharedPreferenceChangeListener, return mPrefs.getBoolean(PREF_ACCEPT_COOKIES, true); } + public boolean enableDeleteCookies() { + int value = Integer.valueOf(mPrefs.getString(PREF_SITE_WHITELIST_COOKIES, "0")); + return value != 0; + } + + public boolean enableDeleteLocaldata() { + int value = Integer.valueOf(mPrefs.getString(PREF_SITE_WHITELIST_COOKIES, "0")); + return value == 2; + } + + public boolean munchToast() { + return mPrefs.getBoolean(PREF_SITE_WHITELIST_COOKIES_VERBOSE, false); + } + public boolean saveFormdata() { return mPrefs.getBoolean(PREF_SAVE_FORMDATA, true); } diff --git a/src/com/android/browser/Controller.java b/src/com/android/browser/Controller.java index 2d716a8d..f01f58e8 100644 --- a/src/com/android/browser/Controller.java +++ b/src/com/android/browser/Controller.java @@ -688,6 +688,11 @@ public class Controller return; } mSettings.setLastRunPaused(false); + + // delete cookies (and localstorage) early in the resume to + // avoid potential races with regular cookie reads and writes. + mSettings.clearCookiesExceptWhitelist(); + mActivityPaused = false; Tab current = mTabControl.getCurrentTab(); if (current != null) { @@ -1483,12 +1488,14 @@ public class Controller boolean canGoForward = false; boolean isHome = false; boolean isDesktopUa = false; + boolean hasCookiesWhitelisted = false; boolean isLive = false; if (tab != null) { canGoBack = tab.canGoBack(); canGoForward = tab.canGoForward(); isHome = mSettings.getHomePage().equals(tab.getUrl()); isDesktopUa = mSettings.hasDesktopUseragent(tab.getWebView()); + hasCookiesWhitelisted = mSettings.hasCookiesWhitelisted(tab.getWebView()); isLive = !tab.isSnapshot(); } final MenuItem back = menu.findItem(R.id.back_menu_id); @@ -1529,6 +1536,11 @@ public class Controller menu.setGroupVisible(R.id.SNAPSHOT_MENU, !isLive); menu.setGroupVisible(R.id.COMBO_MENU, false); + // individual Visible needs to be after the group setting + final MenuItem cwSwitcher = menu.findItem(R.id.cookies_whitelisted_menu_id); + cwSwitcher.setChecked(hasCookiesWhitelisted); + cwSwitcher.setVisible(isLive && mSettings.enableDeleteCookies()); + mUi.updateMenuState(tab, menu); } @@ -1656,6 +1668,10 @@ public class Controller toggleUserAgent(); break; + case R.id.cookies_whitelisted_menu_id: + toggleCookiesWhitelisted(); + break; + case R.id.window_one_menu_id: case R.id.window_two_menu_id: case R.id.window_three_menu_id: @@ -1693,6 +1709,12 @@ public class Controller } @Override + public void toggleCookiesWhitelisted() { + WebView web = getCurrentWebView(); + mSettings.toggleCookiesWhitelisted(web); + } + + @Override public void findOnPage() { getCurrentTopWebView().showFindDialog(null, true); } diff --git a/src/com/android/browser/PreferenceKeys.java b/src/com/android/browser/PreferenceKeys.java index 18280327..8b6d95ca 100644 --- a/src/com/android/browser/PreferenceKeys.java +++ b/src/com/android/browser/PreferenceKeys.java @@ -88,6 +88,8 @@ public interface PreferenceKeys { // Keys for privacy_security_preferences.xml // ---------------------- static final String PREF_ACCEPT_COOKIES = "accept_cookies"; + static final String PREF_SITE_WHITELIST_COOKIES = "site_whitelist_cookies"; + static final String PREF_SITE_WHITELIST_COOKIES_VERBOSE = "site_whitelist_cookies_verbose"; static final String PREF_ENABLE_GEOLOCATION = "enable_geolocation"; static final String PREF_PRIVACY_CLEAR_CACHE = "privacy_clear_cache"; static final String PREF_PRIVACY_CLEAR_COOKIES = "privacy_clear_cookies"; diff --git a/src/com/android/browser/UiController.java b/src/com/android/browser/UiController.java index f00f1a23..53830d5a 100644 --- a/src/com/android/browser/UiController.java +++ b/src/com/android/browser/UiController.java @@ -105,6 +105,8 @@ public interface UiController { void toggleUserAgent(); + void toggleCookiesWhitelisted(); + BrowserSettings getSettings(); boolean supportsVoice(); diff --git a/src/com/android/browser/preferences/PrivacySecurityPreferencesFragment.java b/src/com/android/browser/preferences/PrivacySecurityPreferencesFragment.java index 35e6e432..24301cb7 100644 --- a/src/com/android/browser/preferences/PrivacySecurityPreferencesFragment.java +++ b/src/com/android/browser/preferences/PrivacySecurityPreferencesFragment.java @@ -23,6 +23,7 @@ import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.preference.Preference; +import android.preference.ListPreference; import android.preference.PreferenceFragment; public class PrivacySecurityPreferencesFragment extends PreferenceFragment @@ -37,6 +38,24 @@ public class PrivacySecurityPreferencesFragment extends PreferenceFragment Preference e = findPreference(PreferenceKeys.PREF_PRIVACY_CLEAR_HISTORY); e.setOnPreferenceChangeListener(this); + + ListPreference lp = (ListPreference) findPreference(PreferenceKeys.PREF_SITE_WHITELIST_COOKIES); + lp.setOnPreferenceChangeListener(this); + updateListPreferenceSummary(lp); + cookiesVerboseEnable(lp.getValue()); + } + + private void cookiesVerboseEnable(String str) { + // if never, disable verbose option + int value = Integer.valueOf(str); + Preference pref = findPreference(PreferenceKeys.PREF_SITE_WHITELIST_COOKIES_VERBOSE); + if (pref != null) { + pref.setEnabled(value != 0); + } + } + + void updateListPreferenceSummary(ListPreference e) { + e.setSummary(e.getEntry()); } @Override @@ -54,6 +73,13 @@ public class PrivacySecurityPreferencesFragment extends PreferenceFragment pref.getKey())); return true; } + else if (pref.getKey().equals(PreferenceKeys.PREF_SITE_WHITELIST_COOKIES)) { + ListPreference lp = (ListPreference) pref; + lp.setValue((String) objValue); + updateListPreferenceSummary(lp); + cookiesVerboseEnable((String) objValue); + return false; + } return false; } |
