/* * Copyright (C) 2011 The Android Open Source 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. */ package com.android.browser; import android.app.ActivityManager; import android.content.ContentResolver; import android.content.Context; import android.content.SharedPreferences; import android.content.SharedPreferences.OnSharedPreferenceChangeListener; import android.os.Build; import android.os.Message; import android.preference.PreferenceManager; import android.provider.Browser; import android.webkit.CookieManager; import android.webkit.GeolocationPermissions; import android.webkit.WebIconDatabase; import android.webkit.WebSettings; import android.webkit.WebSettings.AutoFillProfile; import android.webkit.WebSettings.LayoutAlgorithm; import android.webkit.WebSettings.PluginState; import android.webkit.WebSettings.TextSize; import android.webkit.WebSettings.ZoomDensity; import android.webkit.WebStorage; import android.webkit.WebView; import android.webkit.WebViewDatabase; import com.android.browser.homepages.HomeProvider; import com.android.browser.provider.BrowserProvider; import com.android.browser.search.SearchEngine; import com.android.browser.search.SearchEngines; import java.lang.ref.WeakReference; import java.util.Iterator; import java.util.LinkedList; import java.util.WeakHashMap; /** * Class for managing settings */ public class BrowserSettings implements OnSharedPreferenceChangeListener, PreferenceKeys { // TODO: Do something with this UserAgent stuff private static final String DESKTOP_USERAGENT = "Mozilla/5.0 (X11; " + "Linux x86_64) AppleWebKit/534.24 (KHTML, like Gecko) " + "Chrome/11.0.696.34 Safari/534.24"; private static final String IPHONE_USERAGENT = "Mozilla/5.0 (iPhone; U; " + "CPU iPhone OS 4_0 like Mac OS X; en-us) AppleWebKit/532.9 " + "(KHTML, like Gecko) Version/4.0.5 Mobile/8A293 Safari/6531.22.7"; private static final String IPAD_USERAGENT = "Mozilla/5.0 (iPad; U; " + "CPU OS 3_2 like Mac OS X; en-us) AppleWebKit/531.21.10 " + "(KHTML, like Gecko) Version/4.0.4 Mobile/7B367 Safari/531.21.10"; private static final String FROYO_USERAGENT = "Mozilla/5.0 (Linux; U; " + "Android 2.2; en-us; Nexus One Build/FRF91) AppleWebKit/533.1 " + "(KHTML, like Gecko) Version/4.0 Mobile Safari/533.1"; private static final String HONEYCOMB_USERAGENT = "Mozilla/5.0 (Linux; U; " + "Android 3.1; en-us; Xoom Build/HMJ25) AppleWebKit/534.13 " + "(KHTML, like Gecko) Version/4.0 Safari/534.13"; private static final String USER_AGENTS[] = { null, DESKTOP_USERAGENT, IPHONE_USERAGENT, IPAD_USERAGENT, FROYO_USERAGENT, HONEYCOMB_USERAGENT, }; // The minimum min font size // Aka, the lower bounds for the min font size range // which is 1:5..24 private static final int MIN_FONT_SIZE_OFFSET = 5; // The initial value in the text zoom range // This is what represents 100% in the SeekBarPreference range private static final int TEXT_ZOOM_START_VAL = 10; // The size of a single step in the text zoom range, in percent private static final int TEXT_ZOOM_STEP = 5; private static BrowserSettings sInstance; private Context mContext; private SharedPreferences mPrefs; private LinkedList> mManagedSettings; private Controller mController; private WebStorageSizeManager mWebStorageSizeManager; private AutofillHandler mAutofillHandler; private WeakHashMap mCustomUserAgents; private static boolean sInitialized = false; private boolean mNeedsSharedSync = true; // Cached values private int mPageCacheCapacity = 1; private String mAppCachePath; // Cached settings private SearchEngine mSearchEngine; private static String sFactoryResetUrl; public static void initialize(final Context context) { sInstance = new BrowserSettings(context); } public static BrowserSettings getInstance() { return sInstance; } private BrowserSettings(Context context) { mContext = context.getApplicationContext(); mPrefs = PreferenceManager.getDefaultSharedPreferences(mContext); mAutofillHandler = new AutofillHandler(mContext); mManagedSettings = new LinkedList>(); mCustomUserAgents = new WeakHashMap(); mPrefs.registerOnSharedPreferenceChangeListener(this); mAutofillHandler.asyncLoadFromDb(); BackgroundHandler.execute(mSetup); } public void setController(Controller controller) { mController = controller; if (sInitialized) { syncSharedSettings(); } if (mController != null && (mSearchEngine instanceof InstantSearchEngine)) { ((InstantSearchEngine) mSearchEngine).setController(mController); } } public void startManagingSettings(WebSettings settings) { if (mNeedsSharedSync) { syncSharedSettings(); } synchronized (mManagedSettings) { syncStaticSettings(settings); syncSetting(settings); mManagedSettings.add(new WeakReference(settings)); } } private Runnable mSetup = new Runnable() { @Override public void run() { // the cost of one cached page is ~3M (measured using nytimes.com). For // low end devices, we only cache one page. For high end devices, we try // to cache more pages, currently choose 5. if (ActivityManager.staticGetMemoryClass() > 16) { mPageCacheCapacity = 5; } mWebStorageSizeManager = new WebStorageSizeManager(mContext, new WebStorageSizeManager.StatFsDiskInfo(getAppCachePath()), new WebStorageSizeManager.WebKitAppCacheInfo(getAppCachePath())); if (Build.VERSION.CODENAME.equals("REL")) { // This is a release build, always startup with debug disabled setDebugEnabled(false); } if (mPrefs.contains(PREF_TEXT_SIZE)) { /* * Update from TextSize enum to zoom percent * SMALLEST is 50% * SMALLER is 75% * NORMAL is 100% * LARGER is 150% * LARGEST is 200% */ switch (getTextSize()) { case SMALLEST: setTextZoom(50); break; case SMALLER: setTextZoom(75); break; case LARGER: setTextZoom(150); break; case LARGEST: setTextZoom(200); break; } mPrefs.edit().remove(PREF_TEXT_SIZE).apply(); } sFactoryResetUrl = mContext.getResources().getString(R.string.homepage_base); if (sFactoryResetUrl.indexOf("{CID}") != -1) { sFactoryResetUrl = sFactoryResetUrl.replace("{CID}", BrowserProvider.getClientId(mContext.getContentResolver())); } synchronized (BrowserSettings.class) { sInitialized = true; BrowserSettings.class.notifyAll(); } } }; private static void requireInitialization() { synchronized (BrowserSettings.class) { while (!sInitialized) { try { BrowserSettings.class.wait(); } catch (InterruptedException e) { } } } } /** * Syncs all the settings that have a Preference UI */ private void syncSetting(WebSettings settings) { settings.setGeolocationEnabled(enableGeolocation()); settings.setJavaScriptEnabled(enableJavascript()); settings.setLightTouchEnabled(enableLightTouch()); settings.setNavDump(enableNavDump()); settings.setHardwareAccelSkiaEnabled(isSkiaHardwareAccelerated()); settings.setShowVisualIndicator(enableVisualIndicator()); settings.setDefaultTextEncodingName(getDefaultTextEncoding()); settings.setDefaultZoom(getDefaultZoom()); settings.setMinimumFontSize(getMinimumFontSize()); settings.setMinimumLogicalFontSize(getMinimumFontSize()); settings.setForceUserScalable(forceEnableUserScalable()); settings.setPluginState(getPluginState()); settings.setTextZoom(getTextZoom()); settings.setAutoFillEnabled(isAutofillEnabled()); settings.setLayoutAlgorithm(getLayoutAlgorithm()); settings.setJavaScriptCanOpenWindowsAutomatically(!blockPopupWindows()); settings.setLoadsImagesAutomatically(loadImages()); settings.setLoadWithOverviewMode(loadPageInOverviewMode()); settings.setSavePassword(rememberPasswords()); settings.setSaveFormData(saveFormdata()); settings.setUseWideViewPort(isWideViewport()); settings.setAutoFillProfile(getAutoFillProfile()); String ua = mCustomUserAgents.get(settings); if (ua != null) { settings.setUserAgentString(ua); } else { settings.setUserAgentString(USER_AGENTS[getUserAgent()]); } settings.setProperty(WebViewProperties.gfxInvertedScreen, useInvertedRendering() ? "true" : "false"); settings.setProperty(WebViewProperties.gfxInvertedScreenContrast, Float.toString(getInvertedContrast())); } /** * Syncs all the settings that have no UI * These cannot change, so we only need to set them once per WebSettings */ private void syncStaticSettings(WebSettings settings) { settings.setDefaultFontSize(16); settings.setDefaultFixedFontSize(13); settings.setPageCacheCapacity(getPageCacheCapacity()); // WebView inside Browser doesn't want initial focus to be set. settings.setNeedInitialFocus(false); // Browser supports multiple windows settings.setSupportMultipleWindows(true); // enable smooth transition for better performance during panning or // zooming settings.setEnableSmoothTransition(true); // disable content url access settings.setAllowContentAccess(false); // HTML5 API flags settings.setAppCacheEnabled(true); settings.setDatabaseEnabled(true); settings.setDomStorageEnabled(true); settings.setWorkersEnabled(true); // This only affects V8. // HTML5 configuration parametersettings. settings.setAppCacheMaxSize(getWebStorageSizeManager().getAppCacheMaxSize()); settings.setAppCachePath(getAppCachePath()); settings.setDatabasePath(mContext.getDir("databases", 0).getPath()); settings.setGeolocationDatabasePath(mContext.getDir("geolocation", 0).getPath()); } private void syncSharedSettings() { mNeedsSharedSync = false; CookieManager.getInstance().setAcceptCookie(acceptCookies()); if (mController != null) { mController.setShouldShowErrorConsole(enableJavascriptConsole()); } } private void syncManagedSettings() { syncSharedSettings(); synchronized (mManagedSettings) { Iterator> iter = mManagedSettings.iterator(); while (iter.hasNext()) { WeakReference ref = iter.next(); WebSettings settings = ref.get(); if (settings == null) { iter.remove(); continue; } syncSetting(settings); } } } @Override public void onSharedPreferenceChanged( SharedPreferences sharedPreferences, String key) { syncManagedSettings(); if (PREF_SEARCH_ENGINE.equals(key)) { updateSearchEngine(false); } if (PREF_USE_INSTANT_SEARCH.equals(key)) { updateSearchEngine(true); } if (PREF_FULLSCREEN.equals(key)) { if (mController.getUi() != null) { mController.getUi().setFullscreen(useFullscreen()); } } else if (PREF_ENABLE_QUICK_CONTROLS.equals(key)) { if (mController.getUi() != null) { mController.getUi().setUseQuickControls(sharedPreferences.getBoolean(key, false)); } } } public static String getFactoryResetHomeUrl(Context context) { requireInitialization(); return sFactoryResetUrl; } public LayoutAlgorithm getLayoutAlgorithm() { LayoutAlgorithm layoutAlgorithm = LayoutAlgorithm.NORMAL; if (autofitPages()) { layoutAlgorithm = LayoutAlgorithm.NARROW_COLUMNS; } if (isDebugEnabled()) { if (isSmallScreen()) { layoutAlgorithm = LayoutAlgorithm.SINGLE_COLUMN; } else { if (isNormalLayout()) { layoutAlgorithm = LayoutAlgorithm.NORMAL; } else { layoutAlgorithm = LayoutAlgorithm.NARROW_COLUMNS; } } } return layoutAlgorithm; } public int getPageCacheCapacity() { requireInitialization(); return mPageCacheCapacity; } public WebStorageSizeManager getWebStorageSizeManager() { requireInitialization(); return mWebStorageSizeManager; } private String getAppCachePath() { if (mAppCachePath == null) { mAppCachePath = mContext.getDir("appcache", 0).getPath(); } return mAppCachePath; } private void updateSearchEngine(boolean force) { String searchEngineName = getSearchEngineName(); if (force || mSearchEngine == null || !mSearchEngine.getName().equals(searchEngineName)) { if (mSearchEngine != null) { if (mSearchEngine.supportsVoiceSearch()) { // One or more tabs could have been in voice search mode. // Clear it, since the new SearchEngine may not support // it, or may handle it differently. for (int i = 0; i < mController.getTabControl().getTabCount(); i++) { mController.getTabControl().getTab(i).revertVoiceSearchMode(); } } mSearchEngine.close(); } mSearchEngine = SearchEngines.get(mContext, searchEngineName); if (mController != null && (mSearchEngine instanceof InstantSearchEngine)) { ((InstantSearchEngine) mSearchEngine).setController(mController); } } } public SearchEngine getSearchEngine() { if (mSearchEngine == null) { updateSearchEngine(false); } return mSearchEngine; } public boolean isDebugEnabled() { requireInitialization(); return mPrefs.getBoolean(PREF_DEBUG_MENU, false); } public void setDebugEnabled(boolean value) { mPrefs.edit().putBoolean(PREF_DEBUG_MENU, value).apply(); } public void clearCache() { WebIconDatabase.getInstance().removeAllIcons(); if (mController != null) { WebView current = mController.getCurrentWebView(); if (current != null) { current.clearCache(true); } } } public void clearCookies() { CookieManager.getInstance().removeAllCookie(); } public void clearHistory() { ContentResolver resolver = mContext.getContentResolver(); Browser.clearHistory(resolver); Browser.clearSearches(resolver); } public void clearFormData() { WebViewDatabase.getInstance(mContext).clearFormData(); if (mController!= null) { WebView currentTopView = mController.getCurrentTopWebView(); if (currentTopView != null) { currentTopView.clearFormData(); } } } public void clearPasswords() { WebViewDatabase db = WebViewDatabase.getInstance(mContext); db.clearUsernamePassword(); db.clearHttpAuthUsernamePassword(); } public void clearDatabases() { WebStorage.getInstance().deleteAllData(); } public void clearLocationAccess() { GeolocationPermissions.getInstance().clearAll(); } public void resetDefaultPreferences() { // Preserve autologin setting long gal = mPrefs.getLong(GoogleAccountLogin.PREF_AUTOLOGIN_TIME, -1); mPrefs.edit() .clear() .putLong(GoogleAccountLogin.PREF_AUTOLOGIN_TIME, gal) .apply(); syncManagedSettings(); } public AutoFillProfile getAutoFillProfile() { mAutofillHandler.waitForLoad(); return mAutofillHandler.getAutoFillProfile(); } public void setAutoFillProfile(AutoFillProfile profile, Message msg) { mAutofillHandler.waitForLoad(); mAutofillHandler.setAutoFillProfile(profile, msg); // Auto-fill will reuse the same profile ID when making edits to the profile, // so we need to force a settings sync (otherwise the SharedPreferences // manager will optimise out the call to onSharedPreferenceChanged(), as // it thinks nothing has changed). syncManagedSettings(); } public void toggleDebugSettings() { setDebugEnabled(!isDebugEnabled()); } public boolean hasDesktopUseragent(WebView view) { return view != null && mCustomUserAgents.get(view.getSettings()) != null; } public void toggleDesktopUseragent(WebView view) { if (view == null) { return; } WebSettings settings = view.getSettings(); if (mCustomUserAgents.get(settings) != null) { mCustomUserAgents.remove(settings); settings.setUserAgentString(USER_AGENTS[getUserAgent()]); } else { mCustomUserAgents.put(settings, DESKTOP_USERAGENT); settings.setUserAgentString(DESKTOP_USERAGENT); } } public static int getAdjustedMinimumFontSize(int rawValue) { rawValue++; // Preference starts at 0, min font at 1 if (rawValue > 1) { rawValue += (MIN_FONT_SIZE_OFFSET - 2); } return rawValue; } public static int getAdjustedTextZoom(int rawValue) { rawValue = (rawValue - TEXT_ZOOM_START_VAL) * TEXT_ZOOM_STEP; return rawValue + 100; } static int getRawTextZoom(int percent) { return (percent - 100) / TEXT_ZOOM_STEP + TEXT_ZOOM_START_VAL; } public SharedPreferences getPreferences() { return mPrefs; } // ----------------------------- // getter/setters for accessibility_preferences.xml // ----------------------------- @Deprecated private TextSize getTextSize() { String textSize = mPrefs.getString(PREF_TEXT_SIZE, "NORMAL"); return TextSize.valueOf(textSize); } public int getMinimumFontSize() { int minFont = mPrefs.getInt(PREF_MIN_FONT_SIZE, 0); return getAdjustedMinimumFontSize(minFont); } public boolean forceEnableUserScalable() { return mPrefs.getBoolean(PREF_FORCE_USERSCALABLE, false); } public int getTextZoom() { requireInitialization(); int textZoom = mPrefs.getInt(PREF_TEXT_ZOOM, 10); return getAdjustedTextZoom(textZoom); } public void setTextZoom(int percent) { mPrefs.edit().putInt(PREF_TEXT_ZOOM, getRawTextZoom(percent)).apply(); } // ----------------------------- // getter/setters for advanced_preferences.xml // ----------------------------- public String getSearchEngineName() { return mPrefs.getString(PREF_SEARCH_ENGINE, SearchEngine.GOOGLE); } public boolean openInBackground() { return mPrefs.getBoolean(PREF_OPEN_IN_BACKGROUND, false); } public boolean enableJavascript() { return mPrefs.getBoolean(PREF_ENABLE_JAVASCRIPT, true); } // TODO: Cache public PluginState getPluginState() { String state = mPrefs.getString(PREF_PLUGIN_STATE, "ON"); return PluginState.valueOf(state); } // TODO: Cache public ZoomDensity getDefaultZoom() { String zoom = mPrefs.getString(PREF_DEFAULT_ZOOM, "MEDIUM"); return ZoomDensity.valueOf(zoom); } public boolean loadPageInOverviewMode() { return mPrefs.getBoolean(PREF_LOAD_PAGE, true); } public boolean autofitPages() { return mPrefs.getBoolean(PREF_AUTOFIT_PAGES, true); } public boolean blockPopupWindows() { return mPrefs.getBoolean(PREF_BLOCK_POPUP_WINDOWS, true); } public boolean loadImages() { return mPrefs.getBoolean(PREF_LOAD_IMAGES, true); } public String getDefaultTextEncoding() { return mPrefs.getString(PREF_DEFAULT_TEXT_ENCODING, null); } // ----------------------------- // getter/setters for general_preferences.xml // ----------------------------- public String getHomePage() { return mPrefs.getString(PREF_HOMEPAGE, getFactoryResetHomeUrl(mContext)); } public void setHomePage(String value) { mPrefs.edit().putString(PREF_HOMEPAGE, value).apply(); } public boolean isAutofillEnabled() { return mPrefs.getBoolean(PREF_AUTOFILL_ENABLED, true); } public void setAutofillEnabled(boolean value) { mPrefs.edit().putBoolean(PREF_AUTOFILL_ENABLED, value).apply(); } // ----------------------------- // getter/setters for debug_preferences.xml // ----------------------------- public boolean isHardwareAccelerated() { if (!isDebugEnabled()) { return true; } return mPrefs.getBoolean(PREF_ENABLE_HARDWARE_ACCEL, true); } public boolean isSkiaHardwareAccelerated() { if (!isDebugEnabled()) { return false; } return mPrefs.getBoolean(PREF_ENABLE_HARDWARE_ACCEL_SKIA, false); } public int getUserAgent() { if (!isDebugEnabled()) { return 0; } return Integer.parseInt(mPrefs.getString(PREF_USER_AGENT, "0")); } // ----------------------------- // getter/setters for hidden_debug_preferences.xml // ----------------------------- public boolean enableVisualIndicator() { if (!isDebugEnabled()) { return false; } return mPrefs.getBoolean(PREF_ENABLE_VISUAL_INDICATOR, false); } public boolean enableJavascriptConsole() { if (!isDebugEnabled()) { return false; } return mPrefs.getBoolean(PREF_JAVASCRIPT_CONSOLE, true); } public boolean isSmallScreen() { if (!isDebugEnabled()) { return false; } return mPrefs.getBoolean(PREF_SMALL_SCREEN, false); } public boolean isWideViewport() { if (!isDebugEnabled()) { return true; } return mPrefs.getBoolean(PREF_WIDE_VIEWPORT, true); } public boolean isNormalLayout() { if (!isDebugEnabled()) { return false; } return mPrefs.getBoolean(PREF_NORMAL_LAYOUT, false); } public boolean isTracing() { if (!isDebugEnabled()) { return false; } return mPrefs.getBoolean(PREF_ENABLE_TRACING, false); } public boolean enableLightTouch() { if (!isDebugEnabled()) { return false; } return mPrefs.getBoolean(PREF_ENABLE_LIGHT_TOUCH, false); } public boolean enableNavDump() { if (!isDebugEnabled()) { return false; } return mPrefs.getBoolean(PREF_ENABLE_NAV_DUMP, false); } public String getJsEngineFlags() { if (!isDebugEnabled()) { return ""; } return mPrefs.getString(PREF_JS_ENGINE_FLAGS, ""); } // ----------------------------- // getter/setters for lab_preferences.xml // ----------------------------- public boolean useQuickControls() { return mPrefs.getBoolean(PREF_ENABLE_QUICK_CONTROLS, false); } public boolean useMostVisitedHomepage() { return HomeProvider.MOST_VISITED.equals(getHomePage()); } public boolean useInstantSearch() { return mPrefs.getBoolean(PREF_USE_INSTANT_SEARCH, false); } public boolean useFullscreen() { return mPrefs.getBoolean(PREF_FULLSCREEN, false); } public boolean useInvertedRendering() { return mPrefs.getBoolean(PREF_INVERTED, false); } public float getInvertedContrast() { return 1 + (mPrefs.getInt(PREF_INVERTED_CONTRAST, 0) / 10f); } // ----------------------------- // getter/setters for privacy_security_preferences.xml // ----------------------------- public boolean showSecurityWarnings() { return mPrefs.getBoolean(PREF_SHOW_SECURITY_WARNINGS, true); } public boolean acceptCookies() { return mPrefs.getBoolean(PREF_ACCEPT_COOKIES, true); } public boolean saveFormdata() { return mPrefs.getBoolean(PREF_SAVE_FORMDATA, true); } public boolean enableGeolocation() { return mPrefs.getBoolean(PREF_ENABLE_GEOLOCATION, true); } public boolean rememberPasswords() { return mPrefs.getBoolean(PREF_REMEMBER_PASSWORDS, true); } // ----------------------------- // getter/setters for bandwidth_preferences.xml // ----------------------------- public boolean isPreloadEnabled() { return mPrefs.getBoolean(PREF_DATA_PRELOAD, false); } }