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