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