GeneralPreferencesFragment.java revision 99c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2
1/* 2 * Copyright (C) 2010 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License 15 */ 16 17package com.android.browser.preferences; 18 19import com.android.browser.BrowserBookmarksPage; 20import com.android.browser.BrowserHomepagePreference; 21import com.android.browser.BrowserPreferencesPage; 22import com.android.browser.BrowserSettings; 23import com.android.browser.R; 24import com.android.browser.widget.BookmarkThumbnailWidgetProvider; 25 26import android.accounts.Account; 27import android.accounts.AccountManager; 28import android.app.AlertDialog; 29import android.app.Dialog; 30import android.app.DialogFragment; 31import android.app.Fragment; 32import android.content.ContentProviderOperation; 33import android.content.ContentResolver; 34import android.content.ContentValues; 35import android.content.Context; 36import android.content.DialogInterface; 37import android.content.OperationApplicationException; 38import android.content.SharedPreferences; 39import android.content.SharedPreferences.OnSharedPreferenceChangeListener; 40import android.database.Cursor; 41import android.os.AsyncTask; 42import android.os.Bundle; 43import android.os.RemoteException; 44import android.preference.Preference; 45import android.preference.Preference.OnPreferenceClickListener; 46import android.preference.PreferenceFragment; 47import android.preference.PreferenceManager; 48import android.preference.PreferenceScreen; 49import android.provider.BrowserContract; 50import android.provider.BrowserContract.Bookmarks; 51import android.provider.BrowserContract.ChromeSyncColumns; 52import android.util.Log; 53import android.view.LayoutInflater; 54import android.view.View; 55import android.view.View.OnClickListener; 56import android.widget.Button; 57import android.widget.LinearLayout; 58 59import java.util.ArrayList; 60 61public class GeneralPreferencesFragment extends PreferenceFragment 62 implements OnPreferenceClickListener, Preference.OnPreferenceChangeListener { 63 static final String TAG = "PersonalPreferencesFragment"; 64 65 static final String PREF_CHROME_SYNC = "sync_with_chrome"; 66 67 Preference mChromeSync; 68 boolean mEnabled; 69 SharedPreferences mSharedPrefs; 70 71 @Override 72 public void onCreate(Bundle savedInstanceState) { 73 super.onCreate(savedInstanceState); 74 75 // Load the XML preferences file 76 addPreferencesFromResource(R.xml.general_preferences); 77 78 Preference e = findPreference(BrowserSettings.PREF_HOMEPAGE); 79 e.setOnPreferenceChangeListener(this); 80 e.setSummary(getPreferenceScreen().getSharedPreferences() 81 .getString(BrowserSettings.PREF_HOMEPAGE, null)); 82 ((BrowserHomepagePreference) e).setCurrentPage( 83 getActivity().getIntent().getStringExtra(BrowserPreferencesPage.CURRENT_PAGE)); 84 } 85 86 @Override 87 public boolean onPreferenceChange(Preference pref, Object objValue) { 88 if (getActivity() == null) { 89 // We aren't attached, so don't accept preferences changes from the 90 // invisible UI. 91 Log.w("PageContentPreferencesFragment", "onPreferenceChange called from detached fragment!"); 92 return false; 93 } 94 95 if (pref.getKey().equals(BrowserSettings.PREF_HOMEPAGE)) { 96 pref.setSummary((String) objValue); 97 return true; 98 } 99 100 return false; 101 } 102 103 @Override 104 public void onResume() { 105 super.onResume(); 106 107 // Setup the proper state for the sync with chrome item 108 mChromeSync = findPreference(PREF_CHROME_SYNC); 109 refreshUi(); 110 mSharedPrefs = PreferenceManager.getDefaultSharedPreferences(getActivity()); 111 mSharedPrefs.registerOnSharedPreferenceChangeListener(mListener); 112 } 113 114 @Override 115 public void onPause() { 116 super.onPause(); 117 118 mSharedPrefs.unregisterOnSharedPreferenceChangeListener(mListener); 119 } 120 121 OnSharedPreferenceChangeListener mListener 122 = new OnSharedPreferenceChangeListener() { 123 @Override 124 public void onSharedPreferenceChanged( 125 SharedPreferences sharedPreferences, String key) { 126 if (BrowserBookmarksPage.PREF_ACCOUNT_NAME.equals(key) 127 || BrowserBookmarksPage.PREF_ACCOUNT_TYPE.equals(key)) { 128 refreshUi(); 129 BookmarkThumbnailWidgetProvider.refreshWidgets(getActivity(), true); 130 } 131 } 132 133 }; 134 135 private class GetAccountsTask extends AsyncTask<Void, Void, String> { 136 private Context mContext; 137 138 GetAccountsTask(Context ctx) { 139 mContext = ctx; 140 } 141 142 @Override 143 protected String doInBackground(Void... unused) { 144 AccountManager am = (AccountManager) mContext.getSystemService(Context.ACCOUNT_SERVICE); 145 Account[] accounts = am.getAccountsByType("com.google"); 146 if (accounts == null || accounts.length == 0) { 147 // No Google accounts setup, don't offer Chrome sync 148 if (mChromeSync != null) { 149 getPreferenceScreen().removePreference(mChromeSync); 150 } 151 } else { 152 // Google accounts are present. 153 SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mContext); 154 Bundle args = mChromeSync.getExtras(); 155 args.putParcelableArray("accounts", accounts); 156 mEnabled = BrowserContract.Settings.isSyncEnabled(mContext); 157 mChromeSync.setOnPreferenceClickListener(GeneralPreferencesFragment.this); 158 159 if (!mEnabled) { 160 // Setup a link to the enable wizard 161 return mContext.getResources().getString( 162 R.string.pref_personal_sync_with_chrome_summary); 163 } else { 164 // Chrome sync is enabled, setup a link to account switcher 165 String accountName = prefs.getString( 166 BrowserBookmarksPage.PREF_ACCOUNT_NAME, null); 167 args.putString("curAccount", accountName); 168 return accountName; 169 } 170 } 171 172 return null; 173 } 174 175 @Override 176 protected void onPostExecute(String summary) { 177 if (summary != null) { 178 mChromeSync.setSummary(summary); 179 } 180 } 181 } 182 183 void refreshUi() { 184 new GetAccountsTask(getActivity()).execute(); 185 186 PreferenceScreen autoFillSettings = 187 (PreferenceScreen)findPreference(BrowserSettings.PREF_AUTOFILL_PROFILE); 188 autoFillSettings.setDependency(BrowserSettings.PREF_AUTOFILL_ENABLED); 189 } 190 191 @Override 192 public boolean onPreferenceClick(Preference preference) { 193 Fragment frag; 194 if (mEnabled) { 195 frag = new AccountChooserDialog(); 196 } else { 197 frag = new ImportWizardDialog(); 198 } 199 frag.setArguments(preference.getExtras()); 200 getFragmentManager().beginTransaction() 201 .add(frag, null) 202 .commit(); 203 return true; 204 } 205 206 public static class AccountChooserDialog extends DialogFragment 207 implements DialogInterface.OnClickListener { 208 209 AlertDialog mDialog; 210 211 @Override 212 public Dialog onCreateDialog(Bundle savedInstanceState) { 213 Bundle args = getArguments(); 214 Account[] accounts = (Account[]) args.getParcelableArray("accounts"); 215 String curAccount = args.getString("curAccount"); 216 int length = accounts.length; 217 int curAccountOffset = 0; 218 CharSequence[] accountNames = new CharSequence[length]; 219 for (int i = 0; i < length; i++) { 220 String name = accounts[i].name; 221 if (name.equals(curAccount)) { 222 curAccountOffset = i; 223 } 224 accountNames[i] = name; 225 } 226 227 mDialog = new AlertDialog.Builder(getActivity()) 228 .setIcon(android.R.drawable.ic_dialog_alert) 229 .setTitle(R.string.account_chooser_dialog_title) 230 .setSingleChoiceItems(accountNames, curAccountOffset, this) 231 .create(); 232 return mDialog; 233 } 234 235 @Override 236 public void onClick(DialogInterface dialog, int which) { 237 String accountName = mDialog.getListView().getAdapter().getItem(which).toString(); 238 SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getActivity()); 239 prefs.edit().putString(BrowserBookmarksPage.PREF_ACCOUNT_NAME, accountName).apply(); 240 dismiss(); 241 } 242 } 243 244 public static class ImportWizardDialog extends DialogFragment implements OnClickListener { 245 View mRemoveButton; 246 View mCancelButton; 247 String mDefaultAccount; 248 249 @Override 250 public Dialog onCreateDialog(Bundle savedInstanceState) { 251 Context context = getActivity(); 252 Dialog dialog = new Dialog(context); 253 dialog.setTitle(R.string.import_bookmarks_dialog_title); 254 dialog.setContentView(R.layout.import_bookmarks_dialog); 255 mRemoveButton = dialog.findViewById(R.id.remove); 256 mRemoveButton.setOnClickListener(this); 257 mCancelButton = dialog.findViewById(R.id.cancel); 258 mCancelButton.setOnClickListener(this); 259 260 LayoutInflater inflater = dialog.getLayoutInflater(); 261 LinearLayout accountList = (LinearLayout) dialog.findViewById(R.id.accountList); 262 Account[] accounts = (Account[]) getArguments().getParcelableArray("accounts"); 263 mDefaultAccount = accounts[0].name; 264 int length = accounts.length; 265 for (int i = 0; i < length; i++) { 266 Button button = (Button) inflater.inflate(R.layout.import_bookmarks_dialog_button, 267 null); 268 button.setText(context.getString(R.string.import_bookmarks_dialog_import, 269 accounts[i].name)); 270 button.setTag(accounts[i].name); 271 button.setOnClickListener(this); 272 accountList.addView(button); 273 } 274 275 return dialog; 276 } 277 278 @Override 279 public void onClick(View view) { 280 if (view == mCancelButton) { 281 dismiss(); 282 return; 283 } 284 285 ContentResolver resolver = getActivity().getContentResolver(); 286 SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getActivity()); 287 String accountName; 288 if (view == mRemoveButton) { 289 // The user chose to remove their old bookmarks, delete them now 290 resolver.delete(Bookmarks.CONTENT_URI, 291 Bookmarks.PARENT + "=1 AND " + Bookmarks.ACCOUNT_NAME + " IS NULL", null); 292 accountName = mDefaultAccount; 293 } else { 294 // The user chose to migrate their old bookmarks to the account they're syncing 295 accountName = view.getTag().toString(); 296 migrateBookmarks(resolver, accountName); 297 } 298 299 // Record the fact that we turned on sync 300 BrowserContract.Settings.setSyncEnabled(getActivity(), true); 301 prefs.edit() 302 .putString(BrowserBookmarksPage.PREF_ACCOUNT_TYPE, "com.google") 303 .putString(BrowserBookmarksPage.PREF_ACCOUNT_NAME, accountName) 304 .apply(); 305 306 // Enable bookmark sync on all accounts 307 Account[] accounts = (Account[]) getArguments().getParcelableArray("accounts"); 308 for (Account account : accounts) { 309 if (ContentResolver.getIsSyncable(account, BrowserContract.AUTHORITY) == 0) { 310 // Account wasn't syncable, enable it 311 ContentResolver.setIsSyncable(account, BrowserContract.AUTHORITY, 1); 312 ContentResolver.setSyncAutomatically(account, BrowserContract.AUTHORITY, true); 313 } 314 } 315 316 dismiss(); 317 } 318 319 /** 320 * Migrates bookmarks to the given account 321 */ 322 void migrateBookmarks(ContentResolver resolver, String accountName) { 323 Cursor cursor = null; 324 try { 325 // Re-parent the bookmarks in the default root folder 326 cursor = resolver.query(Bookmarks.CONTENT_URI, new String[] { Bookmarks._ID }, 327 Bookmarks.ACCOUNT_NAME + " =? AND " + 328 ChromeSyncColumns.SERVER_UNIQUE + " =?", 329 new String[] { accountName, 330 ChromeSyncColumns.FOLDER_NAME_BOOKMARKS_BAR }, 331 null); 332 ContentValues values = new ContentValues(); 333 if (cursor == null || !cursor.moveToFirst()) { 334 // The root folders don't exist for the account, create them now 335 ArrayList<ContentProviderOperation> ops = 336 new ArrayList<ContentProviderOperation>(); 337 338 // Chrome sync root folder 339 values.clear(); 340 values.put(ChromeSyncColumns.SERVER_UNIQUE, ChromeSyncColumns.FOLDER_NAME_ROOT); 341 values.put(Bookmarks.TITLE, "Google Chrome"); 342 values.put(Bookmarks.POSITION, 0); 343 values.put(Bookmarks.IS_FOLDER, true); 344 values.put(Bookmarks.DIRTY, true); 345 ops.add(ContentProviderOperation.newInsert( 346 Bookmarks.CONTENT_URI.buildUpon().appendQueryParameter( 347 BrowserContract.CALLER_IS_SYNCADAPTER, "true").build()) 348 .withValues(values) 349 .build()); 350 351 // Bookmarks folder 352 values.clear(); 353 values.put(ChromeSyncColumns.SERVER_UNIQUE, 354 ChromeSyncColumns.FOLDER_NAME_BOOKMARKS); 355 values.put(Bookmarks.TITLE, "Bookmarks"); 356 values.put(Bookmarks.POSITION, 0); 357 values.put(Bookmarks.IS_FOLDER, true); 358 values.put(Bookmarks.DIRTY, true); 359 ops.add(ContentProviderOperation.newInsert(Bookmarks.CONTENT_URI) 360 .withValues(values) 361 .withValueBackReference(Bookmarks.PARENT, 0) 362 .build()); 363 364 // Bookmarks Bar folder 365 values.clear(); 366 values.put(ChromeSyncColumns.SERVER_UNIQUE, 367 ChromeSyncColumns.FOLDER_NAME_BOOKMARKS_BAR); 368 values.put(Bookmarks.TITLE, "Bookmarks Bar"); 369 values.put(Bookmarks.POSITION, 0); 370 values.put(Bookmarks.IS_FOLDER, true); 371 values.put(Bookmarks.DIRTY, true); 372 ops.add(ContentProviderOperation.newInsert(Bookmarks.CONTENT_URI) 373 .withValues(values) 374 .withValueBackReference(Bookmarks.PARENT, 1) 375 .build()); 376 377 // Other Bookmarks folder 378 values.clear(); 379 values.put(ChromeSyncColumns.SERVER_UNIQUE, 380 ChromeSyncColumns.FOLDER_NAME_OTHER_BOOKMARKS); 381 values.put(Bookmarks.TITLE, "Other Bookmarks"); 382 values.put(Bookmarks.POSITION, 1000); 383 values.put(Bookmarks.IS_FOLDER, true); 384 values.put(Bookmarks.DIRTY, true); 385 ops.add(ContentProviderOperation.newInsert(Bookmarks.CONTENT_URI) 386 .withValues(values) 387 .withValueBackReference(Bookmarks.PARENT, 1) 388 .build()); 389 390 // Re-parent the existing bookmarks to the newly create bookmarks bar folder 391 ops.add(ContentProviderOperation.newUpdate(Bookmarks.CONTENT_URI) 392 .withValueBackReference(Bookmarks.PARENT, 2) 393 .withSelection(Bookmarks.PARENT + "=?", 394 new String[] { Integer.toString(1) }) 395 .build()); 396 397 // Mark all non-root folder items as belonging to the new account 398 values.clear(); 399 values.put(Bookmarks.ACCOUNT_TYPE, "com.google"); 400 values.put(Bookmarks.ACCOUNT_NAME, accountName); 401 ops.add(ContentProviderOperation.newUpdate(Bookmarks.CONTENT_URI) 402 .withValues(values) 403 .withSelection(Bookmarks.ACCOUNT_NAME + " IS NULL AND " + 404 Bookmarks._ID + "<>1", null) 405 .build()); 406 407 try { 408 resolver.applyBatch(BrowserContract.AUTHORITY, ops); 409 } catch (RemoteException e) { 410 Log.e(TAG, "failed to create root folder for account " + accountName, e); 411 return; 412 } catch (OperationApplicationException e) { 413 Log.e(TAG, "failed to create root folder for account " + accountName, e); 414 return; 415 } 416 } else { 417 values.put(Bookmarks.PARENT, cursor.getLong(0)); 418 resolver.update(Bookmarks.CONTENT_URI, values, Bookmarks.PARENT + "=?", 419 new String[] { Integer.toString(1) }); 420 421 // Mark all bookmarks at all levels as part of the new account 422 values.clear(); 423 values.put(Bookmarks.ACCOUNT_TYPE, "com.google"); 424 values.put(Bookmarks.ACCOUNT_NAME, accountName); 425 resolver.update(Bookmarks.CONTENT_URI, values, 426 Bookmarks.ACCOUNT_NAME + " IS NULL AND " + Bookmarks._ID + "<>1", 427 null); 428 } 429 } finally { 430 if (cursor != null) cursor.close(); 431 } 432 } 433 } 434} 435