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