summaryrefslogtreecommitdiffstats
path: root/src/com/android/browser/preferences
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/browser/preferences')
-rw-r--r--src/com/android/browser/preferences/AdvancedPreferencesFragment.java181
-rw-r--r--src/com/android/browser/preferences/DebugPreferencesFragment.java61
-rw-r--r--src/com/android/browser/preferences/GeneralPreferencesFragment.java263
-rw-r--r--src/com/android/browser/preferences/ImportWizard.java491
-rw-r--r--src/com/android/browser/preferences/LabPreferencesFragment.java71
-rw-r--r--src/com/android/browser/preferences/PrivacySecurityPreferencesFragment.java65
-rw-r--r--src/com/android/browser/preferences/WebsiteSettingsFragment.java672
7 files changed, 1804 insertions, 0 deletions
diff --git a/src/com/android/browser/preferences/AdvancedPreferencesFragment.java b/src/com/android/browser/preferences/AdvancedPreferencesFragment.java
new file mode 100644
index 000000000..e2e45f5d7
--- /dev/null
+++ b/src/com/android/browser/preferences/AdvancedPreferencesFragment.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2010 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.preferences;
+
+import com.android.browser.BrowserActivity;
+import com.android.browser.BrowserSettings;
+import com.android.browser.R;
+
+import android.content.Intent;
+import android.content.res.Resources;
+import android.os.Bundle;
+import android.preference.ListPreference;
+import android.preference.Preference;
+import android.preference.PreferenceFragment;
+import android.preference.PreferenceScreen;
+import android.util.Log;
+import android.webkit.GeolocationPermissions;
+import android.webkit.ValueCallback;
+import android.webkit.WebStorage;
+
+import java.util.Map;
+import java.util.Set;
+
+public class AdvancedPreferencesFragment extends PreferenceFragment
+ implements Preference.OnPreferenceChangeListener {
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ // Load the XML preferences file
+ addPreferencesFromResource(R.xml.advanced_preferences);
+
+ PreferenceScreen websiteSettings = (PreferenceScreen) findPreference(
+ BrowserSettings.PREF_WEBSITE_SETTINGS);
+ websiteSettings.setFragment(WebsiteSettingsFragment.class.getName());
+
+ Preference e = findPreference(BrowserSettings.PREF_TEXT_SIZE);
+ e.setOnPreferenceChangeListener(this);
+ e.setSummary(getVisualTextSizeName(
+ getPreferenceScreen().getSharedPreferences()
+ .getString(BrowserSettings.PREF_TEXT_SIZE, null)) );
+
+ e = findPreference(BrowserSettings.PREF_DEFAULT_ZOOM);
+ e.setOnPreferenceChangeListener(this);
+ e.setSummary(getVisualDefaultZoomName(
+ getPreferenceScreen().getSharedPreferences()
+ .getString(BrowserSettings.PREF_DEFAULT_ZOOM, null)) );
+
+ e = findPreference(BrowserSettings.PREF_DEFAULT_TEXT_ENCODING);
+ e.setOnPreferenceChangeListener(this);
+
+ e = findPreference(BrowserSettings.PREF_EXTRAS_RESET_DEFAULTS);
+ e.setOnPreferenceChangeListener(this);
+
+ e = findPreference(BrowserSettings.PREF_PLUGIN_STATE);
+ e.setOnPreferenceChangeListener(this);
+ updatePluginSummary((ListPreference) e);
+ }
+
+ void updatePluginSummary(ListPreference e) {
+ e.setSummary(e.getEntry());
+ }
+
+ /*
+ * We need to set the PreferenceScreen state in onResume(), as the number of
+ * origins with active features (WebStorage, Geolocation etc) could have
+ * changed after calling the WebsiteSettingsActivity.
+ */
+ @Override
+ public void onResume() {
+ super.onResume();
+ final PreferenceScreen websiteSettings = (PreferenceScreen) findPreference(
+ BrowserSettings.PREF_WEBSITE_SETTINGS);
+ websiteSettings.setEnabled(false);
+ WebStorage.getInstance().getOrigins(new ValueCallback<Map>() {
+ @Override
+ public void onReceiveValue(Map webStorageOrigins) {
+ if ((webStorageOrigins != null) && !webStorageOrigins.isEmpty()) {
+ websiteSettings.setEnabled(true);
+ }
+ }
+ });
+ GeolocationPermissions.getInstance().getOrigins(new ValueCallback<Set<String> >() {
+ @Override
+ public void onReceiveValue(Set<String> geolocationOrigins) {
+ if ((geolocationOrigins != null) && !geolocationOrigins.isEmpty()) {
+ websiteSettings.setEnabled(true);
+ }
+ }
+ });
+ }
+
+ @Override
+ public boolean onPreferenceChange(Preference pref, Object objValue) {
+ if (getActivity() == null) {
+ // We aren't attached, so don't accept preferences changes from the
+ // invisible UI.
+ Log.w("PageContentPreferencesFragment", "onPreferenceChange called from detached fragment!");
+ return false;
+ }
+
+ if (pref.getKey().equals(BrowserSettings.PREF_TEXT_SIZE)) {
+ pref.setSummary(getVisualTextSizeName((String) objValue));
+ return true;
+ } else if (pref.getKey().equals(BrowserSettings.PREF_DEFAULT_ZOOM)) {
+ pref.setSummary(getVisualDefaultZoomName((String) objValue));
+ return true;
+ } else if (pref.getKey().equals(BrowserSettings.PREF_DEFAULT_TEXT_ENCODING)) {
+ pref.setSummary((String) objValue);
+ return true;
+ } else if (pref.getKey().equals(BrowserSettings.PREF_EXTRAS_RESET_DEFAULTS)) {
+ Boolean value = (Boolean) objValue;
+ if (value.booleanValue() == true) {
+ startActivity(new Intent(BrowserActivity.ACTION_RESTART, null,
+ getActivity(), BrowserActivity.class));
+ return true;
+ }
+ } else if (pref.getKey().equals(BrowserSettings.PREF_PLUGIN_STATE)) {
+ ListPreference lp = (ListPreference) pref;
+ lp.setValue((String) objValue);
+ updatePluginSummary(lp);
+ return false;
+ }
+ return false;
+ }
+
+ private CharSequence getVisualTextSizeName(String enumName) {
+ Resources res = getActivity().getResources();
+ CharSequence[] visualNames = res.getTextArray(R.array.pref_text_size_choices);
+ CharSequence[] enumNames = res.getTextArray(R.array.pref_text_size_values);
+
+ // Sanity check
+ if (visualNames.length != enumNames.length) {
+ return "";
+ }
+
+ int length = enumNames.length;
+ for (int i = 0; i < length; i++) {
+ if (enumNames[i].equals(enumName)) {
+ return visualNames[i];
+ }
+ }
+
+ return "";
+ }
+
+ private CharSequence getVisualDefaultZoomName(String enumName) {
+ Resources res = getActivity().getResources();
+ CharSequence[] visualNames = res.getTextArray(R.array.pref_default_zoom_choices);
+ CharSequence[] enumNames = res.getTextArray(R.array.pref_default_zoom_values);
+
+ // Sanity check
+ if (visualNames.length != enumNames.length) {
+ return "";
+ }
+
+ int length = enumNames.length;
+ for (int i = 0; i < length; i++) {
+ if (enumNames[i].equals(enumName)) {
+ return visualNames[i];
+ }
+ }
+
+ return "";
+ }
+} \ No newline at end of file
diff --git a/src/com/android/browser/preferences/DebugPreferencesFragment.java b/src/com/android/browser/preferences/DebugPreferencesFragment.java
new file mode 100644
index 000000000..0a823716e
--- /dev/null
+++ b/src/com/android/browser/preferences/DebugPreferencesFragment.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2010 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.preferences;
+
+import com.android.browser.BrowserActivity;
+import com.android.browser.BrowserSettings;
+import com.android.browser.Controller;
+import com.android.browser.R;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.preference.Preference;
+import android.preference.Preference.OnPreferenceChangeListener;
+import android.preference.PreferenceActivity.Header;
+import android.preference.PreferenceFragment;
+import android.preference.PreferenceManager.OnActivityResultListener;
+
+import java.io.IOException;
+import java.io.Serializable;
+
+public class DebugPreferencesFragment extends PreferenceFragment
+ implements OnPreferenceChangeListener {
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ // Load the XML preferences file
+ addPreferencesFromResource(R.xml.debug_preferences);
+
+ if (BrowserSettings.getInstance().showDebugSettings()) {
+ addPreferencesFromResource(R.xml.hidden_debug_preferences);
+ }
+
+ Preference e = findPreference(BrowserSettings.PREF_HARDWARE_ACCEL);
+ e.setOnPreferenceChangeListener(this);
+ }
+
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ // Attempt to restart
+ startActivity(new Intent(BrowserActivity.ACTION_RESTART, null,
+ getActivity(), BrowserActivity.class));
+ return true;
+ }
+}
diff --git a/src/com/android/browser/preferences/GeneralPreferencesFragment.java b/src/com/android/browser/preferences/GeneralPreferencesFragment.java
new file mode 100644
index 000000000..0c63ab5e1
--- /dev/null
+++ b/src/com/android/browser/preferences/GeneralPreferencesFragment.java
@@ -0,0 +1,263 @@
+/*
+ * Copyright (C) 2010 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.preferences;
+
+import com.android.browser.BrowserBookmarksPage;
+import com.android.browser.BrowserHomepagePreference;
+import com.android.browser.BrowserPreferencesPage;
+import com.android.browser.BrowserSettings;
+import com.android.browser.R;
+import com.android.browser.widget.BookmarkThumbnailWidgetProvider;
+
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.accounts.AccountManagerCallback;
+import android.accounts.AccountManagerFuture;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.preference.Preference;
+import android.preference.Preference.OnPreferenceClickListener;
+import android.preference.PreferenceFragment;
+import android.preference.PreferenceManager;
+import android.preference.PreferenceScreen;
+import android.provider.BrowserContract;
+import android.util.Log;
+
+public class GeneralPreferencesFragment extends PreferenceFragment
+ implements OnPreferenceClickListener, Preference.OnPreferenceChangeListener {
+ static final String TAG = "PersonalPreferencesFragment";
+
+ static final String PREF_CHROME_SYNC = "sync_with_chrome";
+
+ Preference mChromeSync;
+ boolean mEnabled;
+ SharedPreferences mSharedPrefs;
+ Account[] mAccounts;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ // Load the XML preferences file
+ addPreferencesFromResource(R.xml.general_preferences);
+
+ Preference e = findPreference(BrowserSettings.PREF_HOMEPAGE);
+ e.setOnPreferenceChangeListener(this);
+ e.setSummary(getPreferenceScreen().getSharedPreferences()
+ .getString(BrowserSettings.PREF_HOMEPAGE, null));
+ ((BrowserHomepagePreference) e).setCurrentPage(
+ getActivity().getIntent().getStringExtra(BrowserPreferencesPage.CURRENT_PAGE));
+ mSharedPrefs = PreferenceManager.getDefaultSharedPreferences(getActivity());
+ mSharedPrefs.registerOnSharedPreferenceChangeListener(mListener);
+ }
+
+ @Override
+ public boolean onPreferenceChange(Preference pref, Object objValue) {
+ if (getActivity() == null) {
+ // We aren't attached, so don't accept preferences changes from the
+ // invisible UI.
+ Log.w("PageContentPreferencesFragment", "onPreferenceChange called from detached fragment!");
+ return false;
+ }
+
+ if (pref.getKey().equals(BrowserSettings.PREF_HOMEPAGE)) {
+ pref.setSummary((String) objValue);
+ return true;
+ }
+
+ return false;
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+
+ // Setup the proper state for the sync with chrome item
+ mChromeSync = findPreference(PREF_CHROME_SYNC);
+ refreshUi();
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+
+ mSharedPrefs.unregisterOnSharedPreferenceChangeListener(mListener);
+ }
+
+ OnSharedPreferenceChangeListener mListener
+ = new OnSharedPreferenceChangeListener() {
+ @Override
+ public void onSharedPreferenceChanged(
+ SharedPreferences sharedPreferences, String key) {
+ if (BrowserBookmarksPage.PREF_ACCOUNT_NAME.equals(key)
+ || BrowserBookmarksPage.PREF_ACCOUNT_TYPE.equals(key)) {
+ refreshUi();
+ BookmarkThumbnailWidgetProvider.refreshWidgets(getActivity(), true);
+ }
+ }
+
+ };
+
+ private AccountManagerCallback<Bundle> mCallback =
+ new AccountManagerCallback<Bundle>() {
+
+ @Override
+ public void run(AccountManagerFuture<Bundle> future) {
+ try {
+ Bundle bundle = future.getResult();
+ String name = bundle.getString(AccountManager.KEY_ACCOUNT_NAME);
+ String type = bundle.getString(AccountManager.KEY_ACCOUNT_TYPE);
+ Account account = new Account(name, type);
+ mAccounts = new Account[] { account };
+ ImportWizard wizard = ImportWizard.newInstance(mAccounts);
+ wizard.show(getFragmentManager(), null);
+ } catch (Exception ex) {
+ // Canceled or failed to login, doesn't matter to us
+ }
+ }
+ };
+
+ OnPreferenceClickListener mAddAccount = new OnPreferenceClickListener() {
+
+ @Override
+ public boolean onPreferenceClick(Preference preference) {
+ AccountManager am = AccountManager.get(getActivity());
+ am.addAccount("com.google", null, null, null, getActivity(),
+ mCallback, null);
+ return true;
+ }
+ };
+
+ private class GetAccountsTask extends AsyncTask<Void, Void, String> {
+ private Context mContext;
+
+ GetAccountsTask(Context ctx) {
+ mContext = ctx;
+ }
+
+ @Override
+ protected String doInBackground(Void... unused) {
+ AccountManager am = (AccountManager) mContext.getSystemService(Context.ACCOUNT_SERVICE);
+ Account[] accounts = am.getAccountsByType("com.google");
+ if (accounts == null || accounts.length == 0) {
+ // No Google accounts setup, don't offer Chrome sync
+ if (mChromeSync != null) {
+ mChromeSync.setOnPreferenceClickListener(mAddAccount);
+ }
+ } else {
+ // Google accounts are present.
+ mAccounts = accounts;
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mContext);
+ Bundle args = mChromeSync.getExtras();
+ args.putParcelableArray("accounts", accounts);
+ mEnabled = BrowserContract.Settings.isSyncEnabled(mContext);
+ mChromeSync.setOnPreferenceClickListener(GeneralPreferencesFragment.this);
+
+ if (!mEnabled) {
+ // Setup a link to the enable wizard
+ return mContext.getResources().getString(
+ R.string.pref_personal_sync_with_chrome_summary);
+ } else {
+ // Chrome sync is enabled, setup a link to account switcher
+ String accountName = prefs.getString(
+ BrowserBookmarksPage.PREF_ACCOUNT_NAME, null);
+ args.putString("curAccount", accountName);
+ return accountName;
+ }
+ }
+
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(String summary) {
+ if (summary != null) {
+ mChromeSync.setSummary(summary);
+ }
+ }
+ }
+
+ void refreshUi() {
+ new GetAccountsTask(getActivity()).execute();
+
+ PreferenceScreen autoFillSettings =
+ (PreferenceScreen)findPreference(BrowserSettings.PREF_AUTOFILL_PROFILE);
+ autoFillSettings.setDependency(BrowserSettings.PREF_AUTOFILL_ENABLED);
+ }
+
+ @Override
+ public boolean onPreferenceClick(Preference preference) {
+ if (mAccounts == null) {
+ Log.w(TAG, "NULL accounts!");
+ return true;
+ }
+ DialogFragment frag;
+ if (mEnabled) {
+ frag = new AccountChooserDialog();
+ frag.setArguments(preference.getExtras());
+ } else {
+ frag = ImportWizard.newInstance(mAccounts);
+ }
+ frag.show(getFragmentManager(), null);
+ return true;
+ }
+
+ public static class AccountChooserDialog extends DialogFragment
+ implements DialogInterface.OnClickListener {
+
+ AlertDialog mDialog;
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ Bundle args = getArguments();
+ Account[] accounts = (Account[]) args.getParcelableArray("accounts");
+ String curAccount = args.getString("curAccount");
+ int length = accounts.length;
+ int curAccountOffset = 0;
+ CharSequence[] accountNames = new CharSequence[length];
+ for (int i = 0; i < length; i++) {
+ String name = accounts[i].name;
+ if (name.equals(curAccount)) {
+ curAccountOffset = i;
+ }
+ accountNames[i] = name;
+ }
+
+ mDialog = new AlertDialog.Builder(getActivity())
+ .setIcon(android.R.drawable.ic_dialog_alert)
+ .setTitle(R.string.account_chooser_dialog_title)
+ .setSingleChoiceItems(accountNames, curAccountOffset, this)
+ .create();
+ return mDialog;
+ }
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ String accountName = mDialog.getListView().getAdapter().getItem(which).toString();
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getActivity());
+ prefs.edit().putString(BrowserBookmarksPage.PREF_ACCOUNT_NAME, accountName).apply();
+ dismiss();
+ }
+ }
+}
diff --git a/src/com/android/browser/preferences/ImportWizard.java b/src/com/android/browser/preferences/ImportWizard.java
new file mode 100644
index 000000000..7105f4d56
--- /dev/null
+++ b/src/com/android/browser/preferences/ImportWizard.java
@@ -0,0 +1,491 @@
+/*
+ * 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.preferences;
+
+import com.android.browser.BrowserBookmarksPage;
+import com.android.browser.R;
+import com.android.browser.view.EventRedirectingFrameLayout;
+
+import android.accounts.Account;
+import android.animation.LayoutTransition;
+import android.animation.ObjectAnimator;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.content.ContentProviderOperation;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnKeyListener;
+import android.content.DialogInterface.OnShowListener;
+import android.content.OperationApplicationException;
+import android.content.SharedPreferences;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.preference.PreferenceManager;
+import android.provider.BrowserContract;
+import android.provider.BrowserContract.Bookmarks;
+import android.provider.BrowserContract.ChromeSyncColumns;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.ListView;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+
+public class ImportWizard extends DialogFragment implements OnClickListener,
+ OnItemClickListener {
+
+ static final String TAG = "BookmarkImportWizard";
+
+ static final int PAGE_IMPORT_OR_DELETE = 0;
+ static final int PAGE_SELECT_ACCOUNT = 1;
+ static final int PAGE_CONFIRMATION = 2;
+
+ static final String STATE_CURRENT_PAGE = "wizard.current_page";
+ static final String STATE_IMPORT_OR_DELETE = "wizard.import_or_delete";
+ static final String STATE_SELECTED_ACCOUNT = "wizard.selected_account";
+
+ static final String ARG_ACCOUNTS = "accounts";
+
+ AlertDialog mDialog;
+ EventRedirectingFrameLayout mPages;
+ int mCurrentPage;
+ Button mPositiveButton, mNegativeButton;
+ ListView mImportOrDelete, mSelectAccount;
+ Account[] mAccounts;
+ TextView mSelectAccountDescription, mConfirmation;
+
+ static ImportWizard newInstance(Account[] accounts) {
+ ImportWizard wizard = new ImportWizard();
+ Bundle args = new Bundle();
+ args.putParcelableArray(ARG_ACCOUNTS, accounts);
+ wizard.setArguments(args);
+ return wizard;
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mAccounts = (Account[]) getArguments().getParcelableArray(ARG_ACCOUNTS);
+ }
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ mDialog = new AlertDialog.Builder(getActivity())
+ .setTitle(R.string.import_bookmarks_dialog_title)
+ .setView(createView(savedInstanceState))
+ .setPositiveButton("?", null) // This is just a placeholder
+ .setNegativeButton("?", null) // Ditto
+ .setOnKeyListener(new OnKeyListener() {
+ @Override
+ public boolean onKey(DialogInterface arg0, int arg1, KeyEvent key) {
+ if (key.getKeyCode() == KeyEvent.KEYCODE_BACK) {
+ if (key.getAction() == KeyEvent.ACTION_UP
+ && !key.isCanceled()) {
+ mNegativeButton.performClick();
+ }
+ return true;
+ }
+ return false;
+ }
+ })
+ .create();
+ mDialog.setOnShowListener(new OnShowListener() {
+ @Override
+ public void onShow(DialogInterface dialog) {
+ mPositiveButton = mDialog.getButton(AlertDialog.BUTTON_POSITIVE);
+ mNegativeButton = mDialog.getButton(AlertDialog.BUTTON_NEGATIVE);
+ mPositiveButton.setOnClickListener(ImportWizard.this);
+ mNegativeButton.setOnClickListener(ImportWizard.this);
+ setupAnimations();
+ updateNavigation();
+ }
+ });
+ return mDialog;
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putInt(STATE_CURRENT_PAGE, mCurrentPage);
+ outState.putInt(STATE_IMPORT_OR_DELETE, mImportOrDelete.getCheckedItemPosition());
+ outState.putInt(STATE_SELECTED_ACCOUNT, mSelectAccount.getCheckedItemPosition());
+ }
+
+ public View createView(Bundle savedInstanceState) {
+ LayoutInflater inflater = LayoutInflater.from(getActivity());
+ View root = inflater.inflate(R.layout.bookmark_sync_wizard, null);
+ mPages = (EventRedirectingFrameLayout) root.findViewById(R.id.pages);
+ if (mPages.getChildCount() < 1) {
+ throw new IllegalStateException("no pages in wizard!");
+ }
+ if (savedInstanceState != null) {
+ mCurrentPage = savedInstanceState.getInt(STATE_CURRENT_PAGE);
+ } else {
+ mCurrentPage = 0;
+ }
+ setupPage1(savedInstanceState);
+ setupPage2(savedInstanceState);
+ setupPage3(savedInstanceState);
+ for (int i = 0; i < mPages.getChildCount(); i++) {
+ View v = mPages.getChildAt(i);
+ if (i <= mCurrentPage) {
+ preparePage();
+ v.setVisibility(View.VISIBLE);
+ } else {
+ v.setVisibility(View.GONE);
+ }
+ }
+ mPages.setTargetChild(mCurrentPage);
+ return root;
+ }
+
+ void setupPage1(Bundle savedInstanceState) {
+ mImportOrDelete = (ListView) mPages.findViewById(R.id.add_remove_bookmarks);
+ // Add an empty header so we get a divider above the list
+ mImportOrDelete.addHeaderView(new View(getActivity()));
+ Resources res = getActivity().getResources();
+ String[] choices = new String[] {
+ res.getString(R.string.import_bookmarks_dialog_add),
+ res.getString(R.string.import_bookmarks_dialog_remove)
+ };
+ ArrayAdapter<String> adapter = new ArrayAdapter<String>(getActivity(),
+ R.layout.bookmark_sync_wizard_item, choices);
+ mImportOrDelete.setAdapter(adapter);
+ if (savedInstanceState != null) {
+ int position = savedInstanceState.getInt(STATE_IMPORT_OR_DELETE);
+ if (position == ListView.INVALID_POSITION) {
+ mImportOrDelete.clearChoices();
+ } else {
+ mImportOrDelete.setItemChecked(position, true);
+ }
+ }
+ mImportOrDelete.setOnItemClickListener(this);
+ }
+
+ void setupPage2(Bundle savedInstanceState) {
+ mSelectAccount = (ListView) mPages.findViewById(R.id.select_account);
+ mSelectAccountDescription =
+ (TextView) mPages.findViewById(R.id.select_account_description);
+ // Add an empty header so we get a divider above the list
+ mSelectAccount.addHeaderView(new View(getActivity()));
+ Resources res = getActivity().getResources();
+ String[] accountNames = new String[mAccounts.length];
+ for (int i = 0; i < mAccounts.length; i++) {
+ accountNames[i] = mAccounts[i].name;
+ }
+ ArrayAdapter<String> adapter = new ArrayAdapter<String>(getActivity(),
+ R.layout.bookmark_sync_wizard_item, accountNames);
+ mSelectAccount.setAdapter(adapter);
+ mSelectAccount.setItemChecked(mSelectAccount.getHeaderViewsCount(), true);
+ if (savedInstanceState != null) {
+ int position = savedInstanceState.getInt(STATE_SELECTED_ACCOUNT);
+ if (position != ListView.INVALID_POSITION) {
+ mSelectAccount.setItemChecked(position, true);
+ }
+ }
+ mSelectAccount.setOnItemClickListener(this);
+ }
+
+ void setupPage3(Bundle savedInstanceState) {
+ mConfirmation = (TextView) mPages.findViewById(R.id.confirm);
+ }
+
+ void preparePage() {
+ switch (mCurrentPage) {
+ case PAGE_SELECT_ACCOUNT:
+ if (shouldDeleteBookmarks()) {
+ mSelectAccountDescription.setText(
+ R.string.import_bookmarks_dialog_delete_select_account);
+ } else {
+ mSelectAccountDescription.setText(
+ R.string.import_bookmarks_dialog_select_add_account);
+ }
+ break;
+ case PAGE_CONFIRMATION:
+ String account = getSelectedAccount().name;
+ String confirmationMessage;
+ if (shouldDeleteBookmarks()) {
+ confirmationMessage = getActivity().getString(
+ R.string.import_bookmarks_dialog_confirm_delete, account);
+ } else {
+ confirmationMessage = getActivity().getString(
+ R.string.import_bookmarks_dialog_confirm_add, account);
+ }
+ mConfirmation.setText(confirmationMessage);
+ break;
+ }
+ }
+
+ int getAdjustedCheckedItemPosition(ListView list) {
+ int position = list.getCheckedItemPosition();
+ if (position != ListView.INVALID_POSITION) {
+ position -= list.getHeaderViewsCount();
+ }
+ return position;
+ }
+
+ Account getSelectedAccount() {
+ return mAccounts[getAdjustedCheckedItemPosition(mSelectAccount)];
+ }
+
+ boolean shouldDeleteBookmarks() {
+ return getAdjustedCheckedItemPosition(mImportOrDelete) == 1;
+ }
+
+ @Override
+ public void onItemClick(
+ AdapterView<?> parent, View view, int position, long id) {
+ validate();
+ }
+
+ void updateNavigation() {
+ if (mCurrentPage == 0) {
+ mNegativeButton.setText(R.string.import_bookmarks_wizard_cancel);
+ } else {
+ mNegativeButton.setText(R.string.import_bookmarks_wizard_previous);
+ }
+ if ((mCurrentPage + 1) == mPages.getChildCount()) {
+ mPositiveButton.setText(R.string.import_bookmarks_wizard_done);
+ } else {
+ mPositiveButton.setText(R.string.import_bookmarks_wizard_next);
+ }
+ validate();
+ }
+
+ void validate() {
+ switch (mCurrentPage) {
+ case PAGE_IMPORT_OR_DELETE:
+ mPositiveButton.setEnabled(
+ mImportOrDelete.getCheckedItemPosition() != ListView.INVALID_POSITION);
+ break;
+ case PAGE_SELECT_ACCOUNT:
+ mPositiveButton.setEnabled(
+ mSelectAccount.getCheckedItemPosition() != ListView.INVALID_POSITION);
+ break;
+ }
+ }
+
+ void setupAnimations() {
+ float animX = mPages.getMeasuredWidth();
+ final LayoutTransition transitioner = new LayoutTransition();
+ ObjectAnimator appearing = ObjectAnimator.ofFloat(this, "translationX",
+ animX, 0);
+ ObjectAnimator disappearing = ObjectAnimator.ofFloat(this, "translationX",
+ 0, animX);
+ transitioner.setAnimator(LayoutTransition.APPEARING, appearing);
+ transitioner.setAnimator(LayoutTransition.DISAPPEARING, disappearing);
+ mPages.setLayoutTransition(transitioner);
+ }
+
+ boolean next() {
+ if (mCurrentPage + 1 < mPages.getChildCount()) {
+ mCurrentPage++;
+ preparePage();
+ mPages.getChildAt(mCurrentPage).setVisibility(View.VISIBLE);
+ mPages.setTargetChild(mCurrentPage);
+ return true;
+ }
+ return false;
+ }
+
+ boolean prev() {
+ if (mCurrentPage > 0) {
+ mPages.getChildAt(mCurrentPage).setVisibility(View.GONE);
+ mCurrentPage--;
+ mPages.setTargetChild(mCurrentPage);
+ return true;
+ }
+ return false;
+ }
+
+ void done() {
+ ContentResolver resolver = getActivity().getContentResolver();
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getActivity());
+ String accountName = getSelectedAccount().name;
+ if (shouldDeleteBookmarks()) {
+ // The user chose to remove their old bookmarks, delete them now
+ resolver.delete(Bookmarks.CONTENT_URI,
+ Bookmarks.PARENT + "=1 AND " + Bookmarks.ACCOUNT_NAME + " IS NULL", null);
+ } else {
+ // The user chose to migrate their old bookmarks to the account they're syncing
+ migrateBookmarks(resolver, accountName);
+ }
+
+ // Record the fact that we turned on sync
+ BrowserContract.Settings.setSyncEnabled(getActivity(), true);
+ prefs.edit()
+ .putString(BrowserBookmarksPage.PREF_ACCOUNT_TYPE, "com.google")
+ .putString(BrowserBookmarksPage.PREF_ACCOUNT_NAME, accountName)
+ .apply();
+
+ // Enable bookmark sync on all accounts
+ Account[] accounts = (Account[]) getArguments().getParcelableArray("accounts");
+ for (Account account : accounts) {
+ if (ContentResolver.getIsSyncable(account, BrowserContract.AUTHORITY) == 0) {
+ // Account wasn't syncable, enable it
+ ContentResolver.setIsSyncable(account, BrowserContract.AUTHORITY, 1);
+ ContentResolver.setSyncAutomatically(account, BrowserContract.AUTHORITY, true);
+ }
+ }
+
+ dismiss();
+ }
+
+ @Override
+ public void onClick(View v) {
+ if (v == mNegativeButton) {
+ if (prev()) {
+ updateNavigation();
+ } else {
+ dismiss();
+ }
+ } else if (v == mPositiveButton) {
+ if (next()) {
+ updateNavigation();
+ } else {
+ done();
+ }
+ }
+ }
+
+ /**
+ * Migrates bookmarks to the given account
+ */
+ void migrateBookmarks(ContentResolver resolver, String accountName) {
+ Cursor cursor = null;
+ try {
+ // Re-parent the bookmarks in the default root folder
+ cursor = resolver.query(Bookmarks.CONTENT_URI, new String[] { Bookmarks._ID },
+ Bookmarks.ACCOUNT_NAME + " =? AND " +
+ ChromeSyncColumns.SERVER_UNIQUE + " =?",
+ new String[] { accountName,
+ ChromeSyncColumns.FOLDER_NAME_BOOKMARKS_BAR },
+ null);
+ ContentValues values = new ContentValues();
+ if (cursor == null || !cursor.moveToFirst()) {
+ // The root folders don't exist for the account, create them now
+ ArrayList<ContentProviderOperation> ops =
+ new ArrayList<ContentProviderOperation>();
+
+ // Chrome sync root folder
+ values.clear();
+ values.put(ChromeSyncColumns.SERVER_UNIQUE, ChromeSyncColumns.FOLDER_NAME_ROOT);
+ values.put(Bookmarks.TITLE, "Google Chrome");
+ values.put(Bookmarks.POSITION, 0);
+ values.put(Bookmarks.IS_FOLDER, true);
+ values.put(Bookmarks.DIRTY, true);
+ ops.add(ContentProviderOperation.newInsert(
+ Bookmarks.CONTENT_URI.buildUpon().appendQueryParameter(
+ BrowserContract.CALLER_IS_SYNCADAPTER, "true").build())
+ .withValues(values)
+ .build());
+
+ // Bookmarks folder
+ values.clear();
+ values.put(ChromeSyncColumns.SERVER_UNIQUE,
+ ChromeSyncColumns.FOLDER_NAME_BOOKMARKS);
+ values.put(Bookmarks.TITLE, "Bookmarks");
+ values.put(Bookmarks.POSITION, 0);
+ values.put(Bookmarks.IS_FOLDER, true);
+ values.put(Bookmarks.DIRTY, true);
+ ops.add(ContentProviderOperation.newInsert(Bookmarks.CONTENT_URI)
+ .withValues(values)
+ .withValueBackReference(Bookmarks.PARENT, 0)
+ .build());
+
+ // Bookmarks Bar folder
+ values.clear();
+ values.put(ChromeSyncColumns.SERVER_UNIQUE,
+ ChromeSyncColumns.FOLDER_NAME_BOOKMARKS_BAR);
+ values.put(Bookmarks.TITLE, "Bookmarks Bar");
+ values.put(Bookmarks.POSITION, 0);
+ values.put(Bookmarks.IS_FOLDER, true);
+ values.put(Bookmarks.DIRTY, true);
+ ops.add(ContentProviderOperation.newInsert(Bookmarks.CONTENT_URI)
+ .withValues(values)
+ .withValueBackReference(Bookmarks.PARENT, 1)
+ .build());
+
+ // Other Bookmarks folder
+ values.clear();
+ values.put(ChromeSyncColumns.SERVER_UNIQUE,
+ ChromeSyncColumns.FOLDER_NAME_OTHER_BOOKMARKS);
+ values.put(Bookmarks.TITLE, "Other Bookmarks");
+ values.put(Bookmarks.POSITION, 1000);
+ values.put(Bookmarks.IS_FOLDER, true);
+ values.put(Bookmarks.DIRTY, true);
+ ops.add(ContentProviderOperation.newInsert(Bookmarks.CONTENT_URI)
+ .withValues(values)
+ .withValueBackReference(Bookmarks.PARENT, 1)
+ .build());
+
+ // Re-parent the existing bookmarks to the newly create bookmarks bar folder
+ ops.add(ContentProviderOperation.newUpdate(Bookmarks.CONTENT_URI)
+ .withValueBackReference(Bookmarks.PARENT, 2)
+ .withSelection(Bookmarks.ACCOUNT_NAME + " IS NULL AND " +
+ Bookmarks.PARENT + "=?",
+ new String[] { Integer.toString(1) })
+ .build());
+
+ // Mark all non-root folder items as belonging to the new account
+ values.clear();
+ values.put(Bookmarks.ACCOUNT_TYPE, "com.google");
+ values.put(Bookmarks.ACCOUNT_NAME, accountName);
+ ops.add(ContentProviderOperation.newUpdate(Bookmarks.CONTENT_URI)
+ .withValues(values)
+ .withSelection(Bookmarks.ACCOUNT_NAME + " IS NULL AND " +
+ Bookmarks._ID + "<>1", null)
+ .build());
+
+ try {
+ resolver.applyBatch(BrowserContract.AUTHORITY, ops);
+ } catch (RemoteException e) {
+ Log.e(TAG, "failed to create root folder for account " + accountName, e);
+ return;
+ } catch (OperationApplicationException e) {
+ Log.e(TAG, "failed to create root folder for account " + accountName, e);
+ return;
+ }
+ } else {
+ values.put(Bookmarks.PARENT, cursor.getLong(0));
+ resolver.update(Bookmarks.CONTENT_URI, values, Bookmarks.PARENT + "=?",
+ new String[] { Integer.toString(1) });
+
+ // Mark all bookmarks at all levels as part of the new account
+ values.clear();
+ values.put(Bookmarks.ACCOUNT_TYPE, "com.google");
+ values.put(Bookmarks.ACCOUNT_NAME, accountName);
+ resolver.update(Bookmarks.CONTENT_URI, values,
+ Bookmarks.ACCOUNT_NAME + " IS NULL AND " + Bookmarks._ID + "<>1",
+ null);
+ }
+ } finally {
+ if (cursor != null) cursor.close();
+ }
+ }
+}
diff --git a/src/com/android/browser/preferences/LabPreferencesFragment.java b/src/com/android/browser/preferences/LabPreferencesFragment.java
new file mode 100644
index 000000000..a06dc3e0e
--- /dev/null
+++ b/src/com/android/browser/preferences/LabPreferencesFragment.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2010 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.preferences;
+
+import com.android.browser.BrowserActivity;
+import com.android.browser.BrowserSettings;
+import com.android.browser.R;
+import com.android.browser.search.SearchEngine;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.preference.Preference;
+import android.preference.Preference.OnPreferenceChangeListener;
+import android.preference.PreferenceFragment;
+
+public class LabPreferencesFragment extends PreferenceFragment
+ implements OnPreferenceChangeListener {
+ private BrowserSettings mBrowserSettings;
+ private Preference useInstantPref;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ mBrowserSettings = BrowserSettings.getInstance();
+
+ // Load the XML preferences file
+ addPreferencesFromResource(R.xml.lab_preferences);
+
+ Preference e = findPreference(BrowserSettings.PREF_QUICK_CONTROLS);
+ e.setOnPreferenceChangeListener(this);
+ useInstantPref = findPreference(BrowserSettings.PREF_USE_INSTANT);
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ useInstantPref.setEnabled(false);
+
+ // Enable the "use instant" preference only if the selected
+ // search engine is google.
+ if (mBrowserSettings.getSearchEngine() != null) {
+ final String currentName = mBrowserSettings.getSearchEngine().getName();
+ if (SearchEngine.GOOGLE.equals(currentName)) {
+ useInstantPref.setEnabled(true);
+ }
+ }
+ }
+
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ // Attempt to restart
+ startActivity(new Intent(BrowserActivity.ACTION_RESTART, null,
+ getActivity(), BrowserActivity.class));
+ return true;
+ }
+}
diff --git a/src/com/android/browser/preferences/PrivacySecurityPreferencesFragment.java b/src/com/android/browser/preferences/PrivacySecurityPreferencesFragment.java
new file mode 100644
index 000000000..22666088f
--- /dev/null
+++ b/src/com/android/browser/preferences/PrivacySecurityPreferencesFragment.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2010 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.preferences;
+
+import com.android.browser.BrowserSettings;
+import com.android.browser.R;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.preference.Preference;
+import android.preference.PreferenceFragment;
+
+public class PrivacySecurityPreferencesFragment extends PreferenceFragment
+ implements Preference.OnPreferenceChangeListener {
+
+ private BrowserSettings mSettings;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ mSettings = BrowserSettings.getInstance();
+
+ // Load the preferences from an XML resource
+ addPreferencesFromResource(R.xml.privacy_security_preferences);
+
+ Preference e = findPreference(BrowserSettings.PREF_CLEAR_HISTORY);
+ e.setOnPreferenceChangeListener(this);
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ }
+
+ @Override
+ public boolean onPreferenceChange(Preference pref, Object objValue) {
+ if (pref.getKey().equals(BrowserSettings.PREF_CLEAR_HISTORY)
+ && ((Boolean) objValue).booleanValue() == true) {
+ // Need to tell the browser to remove the parent/child relationship
+ // between tabs
+ getActivity().setResult(Activity.RESULT_OK, (new Intent()).putExtra(Intent.EXTRA_TEXT,
+ pref.getKey()));
+ return true;
+ }
+
+ return false;
+ }
+
+}
diff --git a/src/com/android/browser/preferences/WebsiteSettingsFragment.java b/src/com/android/browser/preferences/WebsiteSettingsFragment.java
new file mode 100644
index 000000000..6339d726d
--- /dev/null
+++ b/src/com/android/browser/preferences/WebsiteSettingsFragment.java
@@ -0,0 +1,672 @@
+/*
+ * Copyright (C) 2009 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.preferences;
+
+import com.android.browser.R;
+import com.android.browser.WebStorageSizeManager;
+
+import android.app.AlertDialog;
+import android.app.FragmentTransaction;
+import android.app.ListFragment;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.preference.PreferenceActivity;
+import android.preference.PreferenceFragment;
+import android.provider.BrowserContract.Bookmarks;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.webkit.GeolocationPermissions;
+import android.webkit.ValueCallback;
+import android.webkit.WebStorage;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.ArrayAdapter;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Manage the settings for an origin.
+ * We use it to keep track of the 'HTML5' settings, i.e. database (webstorage)
+ * and Geolocation.
+ */
+public class WebsiteSettingsFragment extends ListFragment implements OnClickListener {
+
+ private static final String EXTRA_SITE = "site";
+ private String LOGTAG = "WebsiteSettingsActivity";
+ private static String sMBStored = null;
+ private SiteAdapter mAdapter = null;
+ private Site mSite = null;
+
+ static class Site implements Serializable {
+ private String mOrigin;
+ private String mTitle;
+ private Bitmap mIcon;
+ private int mFeatures;
+
+ // These constants provide the set of features that a site may support
+ // They must be consecutive. To add a new feature, add a new FEATURE_XXX
+ // variable with value equal to the current value of FEATURE_COUNT, then
+ // increment FEATURE_COUNT.
+ private final static int FEATURE_WEB_STORAGE = 0;
+ private final static int FEATURE_GEOLOCATION = 1;
+ // The number of features available.
+ private final static int FEATURE_COUNT = 2;
+
+ public Site(String origin) {
+ mOrigin = origin;
+ mTitle = null;
+ mIcon = null;
+ mFeatures = 0;
+ }
+
+ public void addFeature(int feature) {
+ mFeatures |= (1 << feature);
+ }
+
+ public void removeFeature(int feature) {
+ mFeatures &= ~(1 << feature);
+ }
+
+ public boolean hasFeature(int feature) {
+ return (mFeatures & (1 << feature)) != 0;
+ }
+
+ /**
+ * Gets the number of features supported by this site.
+ */
+ public int getFeatureCount() {
+ int count = 0;
+ for (int i = 0; i < FEATURE_COUNT; ++i) {
+ count += hasFeature(i) ? 1 : 0;
+ }
+ return count;
+ }
+
+ /**
+ * Gets the ID of the nth (zero-based) feature supported by this site.
+ * The return value is a feature ID - one of the FEATURE_XXX values.
+ * This is required to determine which feature is displayed at a given
+ * position in the list of features for this site. This is used both
+ * when populating the view and when responding to clicks on the list.
+ */
+ public int getFeatureByIndex(int n) {
+ int j = -1;
+ for (int i = 0; i < FEATURE_COUNT; ++i) {
+ j += hasFeature(i) ? 1 : 0;
+ if (j == n) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ public String getOrigin() {
+ return mOrigin;
+ }
+
+ public void setTitle(String title) {
+ mTitle = title;
+ }
+
+ public void setIcon(Bitmap icon) {
+ mIcon = icon;
+ }
+
+ public Bitmap getIcon() {
+ return mIcon;
+ }
+
+ public String getPrettyOrigin() {
+ return mTitle == null ? null : hideHttp(mOrigin);
+ }
+
+ public String getPrettyTitle() {
+ return mTitle == null ? hideHttp(mOrigin) : mTitle;
+ }
+
+ private String hideHttp(String str) {
+ Uri uri = Uri.parse(str);
+ return "http".equals(uri.getScheme()) ? str.substring(7) : str;
+ }
+ }
+
+ class SiteAdapter extends ArrayAdapter<Site>
+ implements AdapterView.OnItemClickListener {
+ private int mResource;
+ private LayoutInflater mInflater;
+ private Bitmap mDefaultIcon;
+ private Bitmap mUsageEmptyIcon;
+ private Bitmap mUsageLowIcon;
+ private Bitmap mUsageHighIcon;
+ private Bitmap mLocationAllowedIcon;
+ private Bitmap mLocationDisallowedIcon;
+ private Site mCurrentSite;
+
+ public SiteAdapter(Context context, int rsc) {
+ this(context, rsc, null);
+ }
+
+ public SiteAdapter(Context context, int rsc, Site site) {
+ super(context, rsc);
+ mResource = rsc;
+ mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ mDefaultIcon = BitmapFactory.decodeResource(getResources(),
+ R.drawable.app_web_browser_sm);
+ mUsageEmptyIcon = BitmapFactory.decodeResource(getResources(),
+ R.drawable.ic_list_data_off);
+ mUsageLowIcon = BitmapFactory.decodeResource(getResources(),
+ R.drawable.ic_list_data_small);
+ mUsageHighIcon = BitmapFactory.decodeResource(getResources(),
+ R.drawable.ic_list_data_large);
+ mLocationAllowedIcon = BitmapFactory.decodeResource(getResources(),
+ R.drawable.ic_gps_on_holo_dark);
+ mLocationDisallowedIcon = BitmapFactory.decodeResource(getResources(),
+ R.drawable.ic_gps_denied_holo_dark);
+ mCurrentSite = site;
+ if (mCurrentSite == null) {
+ askForOrigins();
+ }
+ }
+
+ /**
+ * Adds the specified feature to the site corresponding to supplied
+ * origin in the map. Creates the site if it does not already exist.
+ */
+ private void addFeatureToSite(Map<String, Site> sites, String origin, int feature) {
+ Site site = null;
+ if (sites.containsKey(origin)) {
+ site = (Site) sites.get(origin);
+ } else {
+ site = new Site(origin);
+ sites.put(origin, site);
+ }
+ site.addFeature(feature);
+ }
+
+ public void askForOrigins() {
+ // Get the list of origins we want to display.
+ // All 'HTML 5 modules' (Database, Geolocation etc) form these
+ // origin strings using WebCore::SecurityOrigin::toString(), so it's
+ // safe to group origins here. Note that WebCore::SecurityOrigin
+ // uses 0 (which is not printed) for the port if the port is the
+ // default for the protocol. Eg http://www.google.com and
+ // http://www.google.com:80 both record a port of 0 and hence
+ // toString() == 'http://www.google.com' for both.
+
+ WebStorage.getInstance().getOrigins(new ValueCallback<Map>() {
+ public void onReceiveValue(Map origins) {
+ Map<String, Site> sites = new HashMap<String, Site>();
+ if (origins != null) {
+ Iterator<String> iter = origins.keySet().iterator();
+ while (iter.hasNext()) {
+ addFeatureToSite(sites, iter.next(), Site.FEATURE_WEB_STORAGE);
+ }
+ }
+ askForGeolocation(sites);
+ }
+ });
+ }
+
+ public void askForGeolocation(final Map<String, Site> sites) {
+ GeolocationPermissions.getInstance().getOrigins(new ValueCallback<Set<String> >() {
+ public void onReceiveValue(Set<String> origins) {
+ if (origins != null) {
+ Iterator<String> iter = origins.iterator();
+ while (iter.hasNext()) {
+ addFeatureToSite(sites, iter.next(), Site.FEATURE_GEOLOCATION);
+ }
+ }
+ populateIcons(sites);
+ populateOrigins(sites);
+ }
+ });
+ }
+
+ public void populateIcons(Map<String, Site> sites) {
+ // Create a map from host to origin. This is used to add metadata
+ // (title, icon) for this origin from the bookmarks DB. We must do
+ // the DB access on a background thread.
+ new UpdateFromBookmarksDbTask(this.getContext(), sites).execute();
+ }
+
+ private class UpdateFromBookmarksDbTask extends AsyncTask<Void, Void, Void> {
+
+ private Context mContext;
+ private boolean mDataSetChanged;
+ private Map<String, Site> mSites;
+
+ public UpdateFromBookmarksDbTask(Context ctx, Map<String, Site> sites) {
+ mContext = ctx;
+ mSites = sites;
+ }
+
+ protected Void doInBackground(Void... unused) {
+ HashMap<String, Set<Site>> hosts = new HashMap<String, Set<Site>>();
+ Set<Map.Entry<String, Site>> elements = mSites.entrySet();
+ Iterator<Map.Entry<String, Site>> originIter = elements.iterator();
+ while (originIter.hasNext()) {
+ Map.Entry<String, Site> entry = originIter.next();
+ Site site = entry.getValue();
+ String host = Uri.parse(entry.getKey()).getHost();
+ Set<Site> hostSites = null;
+ if (hosts.containsKey(host)) {
+ hostSites = (Set<Site>)hosts.get(host);
+ } else {
+ hostSites = new HashSet<Site>();
+ hosts.put(host, hostSites);
+ }
+ hostSites.add(site);
+ }
+
+ // Check the bookmark DB. If we have data for a host used by any of
+ // our origins, use it to set their title and favicon
+ Cursor c = mContext.getContentResolver().query(Bookmarks.CONTENT_URI,
+ new String[] { Bookmarks.URL, Bookmarks.TITLE, Bookmarks.FAVICON },
+ Bookmarks.IS_FOLDER + " == 0", null, null);
+
+ if (c != null) {
+ if (c.moveToFirst()) {
+ int urlIndex = c.getColumnIndex(Bookmarks.URL);
+ int titleIndex = c.getColumnIndex(Bookmarks.TITLE);
+ int faviconIndex = c.getColumnIndex(Bookmarks.FAVICON);
+ do {
+ String url = c.getString(urlIndex);
+ String host = Uri.parse(url).getHost();
+ if (hosts.containsKey(host)) {
+ String title = c.getString(titleIndex);
+ Bitmap bmp = null;
+ byte[] data = c.getBlob(faviconIndex);
+ if (data != null) {
+ bmp = BitmapFactory.decodeByteArray(data, 0, data.length);
+ }
+ Set matchingSites = (Set) hosts.get(host);
+ Iterator<Site> sitesIter = matchingSites.iterator();
+ while (sitesIter.hasNext()) {
+ Site site = sitesIter.next();
+ // We should only set the title if the bookmark is for the root
+ // (i.e. www.google.com), as website settings act on the origin
+ // as a whole rather than a single page under that origin. If the
+ // user has bookmarked a page under the root but *not* the root,
+ // then we risk displaying the title of that page which may or
+ // may not have any relevance to the origin.
+ if (url.equals(site.getOrigin()) ||
+ (new String(site.getOrigin()+"/")).equals(url)) {
+ mDataSetChanged = true;
+ site.setTitle(title);
+ }
+
+ if (bmp != null) {
+ mDataSetChanged = true;
+ site.setIcon(bmp);
+ }
+ }
+ }
+ } while (c.moveToNext());
+ }
+ c.close();
+ }
+ return null;
+ }
+
+ protected void onPostExecute(Void unused) {
+ if (mDataSetChanged) {
+ notifyDataSetChanged();
+ }
+ }
+ }
+
+
+ public void populateOrigins(Map<String, Site> sites) {
+ clear();
+
+ // We can now simply populate our array with Site instances
+ Set<Map.Entry<String, Site>> elements = sites.entrySet();
+ Iterator<Map.Entry<String, Site>> entryIterator = elements.iterator();
+ while (entryIterator.hasNext()) {
+ Map.Entry<String, Site> entry = entryIterator.next();
+ Site site = entry.getValue();
+ add(site);
+ }
+
+ notifyDataSetChanged();
+
+ if (getCount() == 0) {
+ finish(); // we close the screen
+ }
+ }
+
+ public int getCount() {
+ if (mCurrentSite == null) {
+ return super.getCount();
+ }
+ return mCurrentSite.getFeatureCount();
+ }
+
+ public String sizeValueToString(long bytes) {
+ // We display the size in MB, to 1dp, rounding up to the next 0.1MB.
+ // bytes should always be greater than zero.
+ if (bytes <= 0) {
+ Log.e(LOGTAG, "sizeValueToString called with non-positive value: " + bytes);
+ return "0";
+ }
+ float megabytes = (float) bytes / (1024.0F * 1024.0F);
+ int truncated = (int) Math.ceil(megabytes * 10.0F);
+ float result = (float) (truncated / 10.0F);
+ return String.valueOf(result);
+ }
+
+ /*
+ * If we receive the back event and are displaying
+ * site's settings, we want to go back to the main
+ * list view. If not, we just do nothing (see
+ * dispatchKeyEvent() below).
+ */
+ public boolean backKeyPressed() {
+ if (mCurrentSite != null) {
+ mCurrentSite = null;
+ askForOrigins();
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * @hide
+ * Utility function
+ * Set the icon according to the usage
+ */
+ public void setIconForUsage(ImageView usageIcon, long usageInBytes) {
+ float usageInMegabytes = (float) usageInBytes / (1024.0F * 1024.0F);
+ // We set the correct icon:
+ // 0 < empty < 0.1MB
+ // 0.1MB < low < 5MB
+ // 5MB < high
+ if (usageInMegabytes <= 0.1) {
+ usageIcon.setImageBitmap(mUsageEmptyIcon);
+ } else if (usageInMegabytes > 0.1 && usageInMegabytes <= 5) {
+ usageIcon.setImageBitmap(mUsageLowIcon);
+ } else if (usageInMegabytes > 5) {
+ usageIcon.setImageBitmap(mUsageHighIcon);
+ }
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ View view;
+ final TextView title;
+ final TextView subtitle;
+ final ImageView icon;
+ final ImageView usageIcon;
+ final ImageView locationIcon;
+ final ImageView featureIcon;
+
+ if (convertView == null) {
+ view = mInflater.inflate(mResource, parent, false);
+ } else {
+ view = convertView;
+ }
+
+ title = (TextView) view.findViewById(R.id.title);
+ subtitle = (TextView) view.findViewById(R.id.subtitle);
+ icon = (ImageView) view.findViewById(R.id.icon);
+ featureIcon = (ImageView) view.findViewById(R.id.feature_icon);
+ usageIcon = (ImageView) view.findViewById(R.id.usage_icon);
+ locationIcon = (ImageView) view.findViewById(R.id.location_icon);
+ usageIcon.setVisibility(View.GONE);
+ locationIcon.setVisibility(View.GONE);
+
+ if (mCurrentSite == null) {
+
+ Site site = getItem(position);
+ title.setText(site.getPrettyTitle());
+ String subtitleText = site.getPrettyOrigin();
+ if (subtitleText != null) {
+ title.setMaxLines(1);
+ title.setSingleLine(true);
+ subtitle.setVisibility(View.VISIBLE);
+ subtitle.setText(subtitleText);
+ } else {
+ subtitle.setVisibility(View.GONE);
+ title.setMaxLines(2);
+ title.setSingleLine(false);
+ }
+
+ icon.setVisibility(View.VISIBLE);
+ usageIcon.setVisibility(View.INVISIBLE);
+ locationIcon.setVisibility(View.INVISIBLE);
+ featureIcon.setVisibility(View.GONE);
+ Bitmap bmp = site.getIcon();
+ if (bmp == null) {
+ bmp = mDefaultIcon;
+ }
+ icon.setImageBitmap(bmp);
+ // We set the site as the view's tag,
+ // so that we can get it in onItemClick()
+ view.setTag(site);
+
+ String origin = site.getOrigin();
+ if (site.hasFeature(Site.FEATURE_WEB_STORAGE)) {
+ WebStorage.getInstance().getUsageForOrigin(origin, new ValueCallback<Long>() {
+ public void onReceiveValue(Long value) {
+ if (value != null) {
+ setIconForUsage(usageIcon, value.longValue());
+ usageIcon.setVisibility(View.VISIBLE);
+ }
+ }
+ });
+ }
+
+ if (site.hasFeature(Site.FEATURE_GEOLOCATION)) {
+ locationIcon.setVisibility(View.VISIBLE);
+ GeolocationPermissions.getInstance().getAllowed(origin, new ValueCallback<Boolean>() {
+ public void onReceiveValue(Boolean allowed) {
+ if (allowed != null) {
+ if (allowed.booleanValue()) {
+ locationIcon.setImageBitmap(mLocationAllowedIcon);
+ } else {
+ locationIcon.setImageBitmap(mLocationDisallowedIcon);
+ }
+ }
+ }
+ });
+ }
+ } else {
+ icon.setVisibility(View.GONE);
+ locationIcon.setVisibility(View.GONE);
+ usageIcon.setVisibility(View.GONE);
+ featureIcon.setVisibility(View.VISIBLE);
+ String origin = mCurrentSite.getOrigin();
+ switch (mCurrentSite.getFeatureByIndex(position)) {
+ case Site.FEATURE_WEB_STORAGE:
+ WebStorage.getInstance().getUsageForOrigin(origin, new ValueCallback<Long>() {
+ public void onReceiveValue(Long value) {
+ if (value != null) {
+ String usage = sizeValueToString(value.longValue()) + " " + sMBStored;
+ title.setText(R.string.webstorage_clear_data_title);
+ subtitle.setText(usage);
+ subtitle.setVisibility(View.VISIBLE);
+ setIconForUsage(featureIcon, value.longValue());
+ }
+ }
+ });
+ break;
+ case Site.FEATURE_GEOLOCATION:
+ title.setText(R.string.geolocation_settings_page_title);
+ GeolocationPermissions.getInstance().getAllowed(origin, new ValueCallback<Boolean>() {
+ public void onReceiveValue(Boolean allowed) {
+ if (allowed != null) {
+ if (allowed.booleanValue()) {
+ subtitle.setText(R.string.geolocation_settings_page_summary_allowed);
+ featureIcon.setImageBitmap(mLocationAllowedIcon);
+ } else {
+ subtitle.setText(R.string.geolocation_settings_page_summary_not_allowed);
+ featureIcon.setImageBitmap(mLocationDisallowedIcon);
+ }
+ subtitle.setVisibility(View.VISIBLE);
+ }
+ }
+ });
+ break;
+ }
+ }
+
+ return view;
+ }
+
+ public void onItemClick(AdapterView<?> parent,
+ View view,
+ int position,
+ long id) {
+ if (mCurrentSite != null) {
+ switch (mCurrentSite.getFeatureByIndex(position)) {
+ case Site.FEATURE_WEB_STORAGE:
+ new AlertDialog.Builder(getContext())
+ .setTitle(R.string.webstorage_clear_data_dialog_title)
+ .setMessage(R.string.webstorage_clear_data_dialog_message)
+ .setPositiveButton(R.string.webstorage_clear_data_dialog_ok_button,
+ new AlertDialog.OnClickListener() {
+ public void onClick(DialogInterface dlg, int which) {
+ WebStorage.getInstance().deleteOrigin(mCurrentSite.getOrigin());
+ // If this site has no more features, then go back to the
+ // origins list.
+ mCurrentSite.removeFeature(Site.FEATURE_WEB_STORAGE);
+ if (mCurrentSite.getFeatureCount() == 0) {
+ finish();
+ }
+ askForOrigins();
+ notifyDataSetChanged();
+ }})
+ .setNegativeButton(R.string.webstorage_clear_data_dialog_cancel_button, null)
+ .setIcon(android.R.drawable.ic_dialog_alert)
+ .show();
+ break;
+ case Site.FEATURE_GEOLOCATION:
+ new AlertDialog.Builder(getContext())
+ .setTitle(R.string.geolocation_settings_page_dialog_title)
+ .setMessage(R.string.geolocation_settings_page_dialog_message)
+ .setPositiveButton(R.string.geolocation_settings_page_dialog_ok_button,
+ new AlertDialog.OnClickListener() {
+ public void onClick(DialogInterface dlg, int which) {
+ GeolocationPermissions.getInstance().clear(mCurrentSite.getOrigin());
+ mCurrentSite.removeFeature(Site.FEATURE_GEOLOCATION);
+ if (mCurrentSite.getFeatureCount() == 0) {
+ finish();
+ }
+ askForOrigins();
+ notifyDataSetChanged();
+ }})
+ .setNegativeButton(R.string.geolocation_settings_page_dialog_cancel_button, null)
+ .setIcon(android.R.drawable.ic_dialog_alert)
+ .show();
+ break;
+ }
+ } else {
+ Site site = (Site) view.getTag();
+ PreferenceActivity activity = (PreferenceActivity) getActivity();
+ if (activity != null) {
+ Bundle args = new Bundle();
+ args.putSerializable(EXTRA_SITE, site);
+ activity.startPreferencePanel(WebsiteSettingsFragment.class.getName(), args, 0,
+ site.getPrettyTitle(), null, 0);
+ }
+ }
+ }
+
+ public Site currentSite() {
+ return mCurrentSite;
+ }
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ View view = inflater.inflate(R.layout.website_settings, container, false);
+ Bundle args = getArguments();
+ if (args != null) {
+ mSite = (Site) args.getSerializable(EXTRA_SITE);
+ }
+ if (mSite == null) {
+ View clear = view.findViewById(R.id.clear_all_button);
+ clear.setVisibility(View.VISIBLE);
+ clear.setOnClickListener(this);
+ }
+ return view;
+ }
+
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+ if (sMBStored == null) {
+ sMBStored = getString(R.string.webstorage_origin_summary_mb_stored);
+ }
+ mAdapter = new SiteAdapter(getActivity(), R.layout.website_settings_row);
+ if (mSite != null) {
+ mAdapter.mCurrentSite = mSite;
+ }
+ getListView().setAdapter(mAdapter);
+ getListView().setOnItemClickListener(mAdapter);
+ }
+
+ private void finish() {
+ PreferenceActivity activity = (PreferenceActivity) getActivity();
+ if (activity != null) {
+ activity.finishPreferencePanel(this, 0, null);
+ }
+ }
+
+ @Override
+ public void onClick(View v) {
+ switch (v.getId()) {
+ case R.id.clear_all_button:
+ // Show the prompt to clear all origins of their data and geolocation permissions.
+ new AlertDialog.Builder(getActivity())
+ .setTitle(R.string.website_settings_clear_all_dialog_title)
+ .setMessage(R.string.website_settings_clear_all_dialog_message)
+ .setPositiveButton(R.string.website_settings_clear_all_dialog_ok_button,
+ new AlertDialog.OnClickListener() {
+ public void onClick(DialogInterface dlg, int which) {
+ WebStorage.getInstance().deleteAllData();
+ GeolocationPermissions.getInstance().clearAll();
+ WebStorageSizeManager.resetLastOutOfSpaceNotificationTime();
+ mAdapter.askForOrigins();
+ finish();
+ }})
+ .setNegativeButton(R.string.website_settings_clear_all_dialog_cancel_button, null)
+ .setIcon(android.R.drawable.ic_dialog_alert)
+ .show();
+ break;
+ }
+ }
+}