OSDN Git Service

2de8e4abe1ef39bdb149a2558ed78ad4b2e54610
[android-x86/packages-apps-Settings.git] / src / com / android / settings / accounts / AccountSyncSettings.java
1 /*
2  * Copyright (C) 2008 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
17 package com.android.settings.accounts;
18
19 import com.android.internal.logging.MetricsLogger;
20 import com.google.android.collect.Lists;
21
22 import android.accounts.Account;
23 import android.accounts.AccountManager;
24 import android.accounts.AccountManagerCallback;
25 import android.accounts.AccountManagerFuture;
26 import android.accounts.AuthenticatorException;
27 import android.accounts.OperationCanceledException;
28 import android.app.Activity;
29 import android.app.AlertDialog;
30 import android.app.Dialog;
31 import android.content.ContentResolver;
32 import android.content.Context;
33 import android.content.DialogInterface;
34 import android.content.SyncAdapterType;
35 import android.content.SyncInfo;
36 import android.content.SyncStatusInfo;
37 import android.content.pm.ProviderInfo;
38 import android.os.Bundle;
39 import android.os.UserHandle;
40 import android.os.UserManager;
41 import android.preference.Preference;
42 import android.preference.PreferenceScreen;
43 import android.text.TextUtils;
44 import android.util.Log;
45 import android.view.LayoutInflater;
46 import android.view.Menu;
47 import android.view.MenuInflater;
48 import android.view.MenuItem;
49 import android.view.View;
50 import android.view.ViewGroup;
51 import android.widget.ImageView;
52 import android.widget.ListView;
53 import android.widget.TextView;
54
55 import com.android.settings.R;
56 import com.android.settings.Utils;
57
58 import java.io.IOException;
59 import java.util.ArrayList;
60 import java.util.Collections;
61 import java.util.Date;
62 import java.util.List;
63
64 public class AccountSyncSettings extends AccountPreferenceBase {
65
66     public static final String ACCOUNT_KEY = "account";
67     private static final int MENU_SYNC_NOW_ID       = Menu.FIRST;
68     private static final int MENU_SYNC_CANCEL_ID    = Menu.FIRST + 1;
69     private static final int MENU_REMOVE_ACCOUNT_ID = Menu.FIRST + 2;
70     private static final int REALLY_REMOVE_DIALOG = 100;
71     private static final int FAILED_REMOVAL_DIALOG = 101;
72     private static final int CANT_DO_ONETIME_SYNC_DIALOG = 102;
73     private TextView mUserId;
74     private TextView mProviderId;
75     private ImageView mProviderIcon;
76     private TextView mErrorInfoView;
77     private Account mAccount;
78     private ArrayList<SyncStateSwitchPreference> mSwitches =
79                 new ArrayList<SyncStateSwitchPreference>();
80     private ArrayList<SyncAdapterType> mInvisibleAdapters = Lists.newArrayList();
81
82     @Override
83     public Dialog onCreateDialog(final int id) {
84         Dialog dialog = null;
85         if (id == REALLY_REMOVE_DIALOG) {
86             dialog = new AlertDialog.Builder(getActivity())
87                 .setTitle(R.string.really_remove_account_title)
88                 .setMessage(R.string.really_remove_account_message)
89                 .setNegativeButton(android.R.string.cancel, null)
90                 .setPositiveButton(R.string.remove_account_label,
91                         new DialogInterface.OnClickListener() {
92                     @Override
93                     public void onClick(DialogInterface dialog, int which) {
94                         Activity activity = getActivity();
95                         AccountManager.get(activity)
96                                 .removeAccountAsUser(mAccount, activity,
97                                 new AccountManagerCallback<Bundle>() {
98                             @Override
99                             public void run(AccountManagerFuture<Bundle> future) {
100                                 // If already out of this screen, don't proceed.
101                                 if (!AccountSyncSettings.this.isResumed()) {
102                                     return;
103                                 }
104                                 boolean failed = true;
105                                 try {
106                                     if (future.getResult()
107                                             .getBoolean(AccountManager.KEY_BOOLEAN_RESULT)) {
108                                         failed = false;
109                                     }
110                                 } catch (OperationCanceledException e) {
111                                     // handled below
112                                 } catch (IOException e) {
113                                     // handled below
114                                 } catch (AuthenticatorException e) {
115                                     // handled below
116                                 }
117                                 if (failed && getActivity() != null &&
118                                         !getActivity().isFinishing()) {
119                                     showDialog(FAILED_REMOVAL_DIALOG);
120                                 } else {
121                                     finish();
122                                 }
123                             }
124                         }, null, mUserHandle);
125                     }
126                 })
127                 .create();
128         } else if (id == FAILED_REMOVAL_DIALOG) {
129             dialog = new AlertDialog.Builder(getActivity())
130                 .setTitle(R.string.really_remove_account_title)
131                 .setPositiveButton(android.R.string.ok, null)
132                 .setMessage(R.string.remove_account_failed)
133                 .create();
134         } else if (id == CANT_DO_ONETIME_SYNC_DIALOG) {
135             dialog = new AlertDialog.Builder(getActivity())
136                 .setTitle(R.string.cant_sync_dialog_title)
137                 .setMessage(R.string.cant_sync_dialog_message)
138                 .setPositiveButton(android.R.string.ok, null)
139                 .create();
140         }
141         return dialog;
142     }
143
144     @Override
145     protected int getMetricsCategory() {
146         return MetricsLogger.ACCOUNTS_ACCOUNT_SYNC;
147     }
148
149     @Override
150     public void onCreate(Bundle icicle) {
151         super.onCreate(icicle);
152
153         setHasOptionsMenu(true);
154     }
155
156     @Override
157     public View onCreateView(LayoutInflater inflater, ViewGroup container,
158             Bundle savedInstanceState) {
159         final View view = inflater.inflate(R.layout.account_sync_screen, container, false);
160
161         final ListView list = (ListView) view.findViewById(android.R.id.list);
162         Utils.prepareCustomPreferencesList(container, view, list, false);
163
164         initializeUi(view);
165
166         return view;
167     }
168
169     protected void initializeUi(final View rootView) {
170         addPreferencesFromResource(R.xml.account_sync_settings);
171
172         mErrorInfoView = (TextView) rootView.findViewById(R.id.sync_settings_error_info);
173         mErrorInfoView.setVisibility(View.GONE);
174
175         mUserId = (TextView) rootView.findViewById(R.id.user_id);
176         mProviderId = (TextView) rootView.findViewById(R.id.provider_id);
177         mProviderIcon = (ImageView) rootView.findViewById(R.id.provider_icon);
178     }
179
180     @Override
181     public void onActivityCreated(Bundle savedInstanceState) {
182         super.onActivityCreated(savedInstanceState);
183
184         Bundle arguments = getArguments();
185         if (arguments == null) {
186             Log.e(TAG, "No arguments provided when starting intent. ACCOUNT_KEY needed.");
187             finish();
188             return;
189         }
190         mAccount = (Account) arguments.getParcelable(ACCOUNT_KEY);
191         if (!accountExists(mAccount)) {
192             Log.e(TAG, "Account provided does not exist: " + mAccount);
193             finish();
194             return;
195         }
196         if (Log.isLoggable(TAG, Log.VERBOSE)) {
197           Log.v(TAG, "Got account: " + mAccount);
198         }
199         mUserId.setText(mAccount.name);
200         mProviderId.setText(mAccount.type);
201     }
202
203     @Override
204     public void onResume() {
205         mAuthenticatorHelper.listenToAccountUpdates();
206         updateAuthDescriptions();
207         onAccountsUpdate(UserHandle.getCallingUserHandle());
208
209         super.onResume();
210     }
211
212     @Override
213     public void onPause() {
214         super.onPause();
215         mAuthenticatorHelper.stopListeningToAccountUpdates();
216     }
217
218     private void addSyncStateSwitch(Account account, String authority) {
219         SyncStateSwitchPreference item =
220                 new SyncStateSwitchPreference(getActivity(), account, authority);
221         item.setPersistent(false);
222         final ProviderInfo providerInfo = getPackageManager().resolveContentProviderAsUser(
223                 authority, 0, mUserHandle.getIdentifier());
224         if (providerInfo == null) {
225             return;
226         }
227         CharSequence providerLabel = providerInfo.loadLabel(getPackageManager());
228         if (TextUtils.isEmpty(providerLabel)) {
229             Log.e(TAG, "Provider needs a label for authority '" + authority + "'");
230             return;
231         }
232         String title = getString(R.string.sync_item_title, providerLabel);
233         item.setTitle(title);
234         item.setKey(authority);
235         mSwitches.add(item);
236     }
237
238     @Override
239     public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
240
241         MenuItem syncNow = menu.add(0, MENU_SYNC_NOW_ID, 0,
242                 getString(R.string.sync_menu_sync_now))
243                 .setIcon(R.drawable.ic_menu_refresh_holo_dark);
244         MenuItem syncCancel = menu.add(0, MENU_SYNC_CANCEL_ID, 0,
245                 getString(R.string.sync_menu_sync_cancel))
246                 .setIcon(com.android.internal.R.drawable.ic_menu_close_clear_cancel);
247         final UserManager um = (UserManager) getSystemService(Context.USER_SERVICE);
248         if (!um.hasUserRestriction(UserManager.DISALLOW_MODIFY_ACCOUNTS, mUserHandle)) {
249             MenuItem removeAccount = menu.add(0, MENU_REMOVE_ACCOUNT_ID, 0,
250                     getString(R.string.remove_account_label))
251                     .setIcon(R.drawable.ic_menu_delete);
252             removeAccount.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER |
253                     MenuItem.SHOW_AS_ACTION_WITH_TEXT);
254         }
255         syncNow.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER |
256                 MenuItem.SHOW_AS_ACTION_WITH_TEXT);
257         syncCancel.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER |
258                 MenuItem.SHOW_AS_ACTION_WITH_TEXT);
259
260         super.onCreateOptionsMenu(menu, inflater);
261     }
262
263     @Override
264     public void onPrepareOptionsMenu(Menu menu) {
265         super.onPrepareOptionsMenu(menu);
266         // Note that this also counts accounts that are not currently displayed
267         boolean syncActive = !ContentResolver.getCurrentSyncsAsUser(
268                 mUserHandle.getIdentifier()).isEmpty();
269         menu.findItem(MENU_SYNC_NOW_ID).setVisible(!syncActive);
270         menu.findItem(MENU_SYNC_CANCEL_ID).setVisible(syncActive);
271     }
272
273     @Override
274     public boolean onOptionsItemSelected(MenuItem item) {
275         switch (item.getItemId()) {
276             case MENU_SYNC_NOW_ID:
277                 startSyncForEnabledProviders();
278                 return true;
279             case MENU_SYNC_CANCEL_ID:
280                 cancelSyncForEnabledProviders();
281                 return true;
282             case MENU_REMOVE_ACCOUNT_ID:
283                 showDialog(REALLY_REMOVE_DIALOG);
284                 return true;
285         }
286         return super.onOptionsItemSelected(item);
287     }
288
289     @Override
290     public boolean onPreferenceTreeClick(PreferenceScreen preferences, Preference preference) {
291         if (preference instanceof SyncStateSwitchPreference) {
292             SyncStateSwitchPreference syncPref = (SyncStateSwitchPreference) preference;
293             String authority = syncPref.getAuthority();
294             Account account = syncPref.getAccount();
295             final int userId = mUserHandle.getIdentifier();
296             boolean syncAutomatically = ContentResolver.getSyncAutomaticallyAsUser(account,
297                     authority, userId);
298             if (syncPref.isOneTimeSyncMode()) {
299                 requestOrCancelSync(account, authority, true);
300             } else {
301                 boolean syncOn = syncPref.isChecked();
302                 boolean oldSyncState = syncAutomatically;
303                 if (syncOn != oldSyncState) {
304                     // if we're enabling sync, this will request a sync as well
305                     ContentResolver.setSyncAutomaticallyAsUser(account, authority, syncOn, userId);
306                     // if the master sync switch is off, the request above will
307                     // get dropped.  when the user clicks on this toggle,
308                     // we want to force the sync, however.
309                     if (!ContentResolver.getMasterSyncAutomaticallyAsUser(userId) || !syncOn) {
310                         requestOrCancelSync(account, authority, syncOn);
311                     }
312                 }
313             }
314             return true;
315         } else {
316             return super.onPreferenceTreeClick(preferences, preference);
317         }
318     }
319
320     private void startSyncForEnabledProviders() {
321         requestOrCancelSyncForEnabledProviders(true /* start them */);
322         final Activity activity = getActivity();
323         if (activity != null) {
324             activity.invalidateOptionsMenu();
325         }
326     }
327
328     private void cancelSyncForEnabledProviders() {
329         requestOrCancelSyncForEnabledProviders(false /* cancel them */);
330         final Activity activity = getActivity();
331         if (activity != null) {
332             activity.invalidateOptionsMenu();
333         }
334     }
335
336     private void requestOrCancelSyncForEnabledProviders(boolean startSync) {
337         // sync everything that the user has enabled
338         int count = getPreferenceScreen().getPreferenceCount();
339         for (int i = 0; i < count; i++) {
340             Preference pref = getPreferenceScreen().getPreference(i);
341             if (! (pref instanceof SyncStateSwitchPreference)) {
342                 continue;
343             }
344             SyncStateSwitchPreference syncPref = (SyncStateSwitchPreference) pref;
345             if (!syncPref.isChecked()) {
346                 continue;
347             }
348             requestOrCancelSync(syncPref.getAccount(), syncPref.getAuthority(), startSync);
349         }
350         // plus whatever the system needs to sync, e.g., invisible sync adapters
351         if (mAccount != null) {
352             for (SyncAdapterType syncAdapter : mInvisibleAdapters) {
353                   requestOrCancelSync(mAccount, syncAdapter.authority, startSync);
354             }
355         }
356     }
357
358     private void requestOrCancelSync(Account account, String authority, boolean flag) {
359         if (flag) {
360             Bundle extras = new Bundle();
361             extras.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);
362             ContentResolver.requestSyncAsUser(account, authority, mUserHandle.getIdentifier(),
363                     extras);
364         } else {
365             ContentResolver.cancelSyncAsUser(account, authority, mUserHandle.getIdentifier());
366         }
367     }
368
369     private boolean isSyncing(List<SyncInfo> currentSyncs, Account account, String authority) {
370         for (SyncInfo syncInfo : currentSyncs) {
371             if (syncInfo.account.equals(account) && syncInfo.authority.equals(authority)) {
372                 return true;
373             }
374         }
375         return false;
376     }
377
378     @Override
379     protected void onSyncStateUpdated() {
380         if (!isResumed()) return;
381         setFeedsState();
382         final Activity activity = getActivity();
383         if (activity != null) {
384             activity.invalidateOptionsMenu();
385         }
386     }
387
388     private void setFeedsState() {
389         // iterate over all the preferences, setting the state properly for each
390         Date date = new Date();
391         final int userId = mUserHandle.getIdentifier();
392         List<SyncInfo> currentSyncs = ContentResolver.getCurrentSyncsAsUser(userId);
393         boolean syncIsFailing = false;
394
395         // Refresh the sync status switches - some syncs may have become active.
396         updateAccountSwitches();
397
398         for (int i = 0, count = getPreferenceScreen().getPreferenceCount(); i < count; i++) {
399             Preference pref = getPreferenceScreen().getPreference(i);
400             if (! (pref instanceof SyncStateSwitchPreference)) {
401                 continue;
402             }
403             SyncStateSwitchPreference syncPref = (SyncStateSwitchPreference) pref;
404
405             String authority = syncPref.getAuthority();
406             Account account = syncPref.getAccount();
407
408             SyncStatusInfo status = ContentResolver.getSyncStatusAsUser(account, authority, userId);
409             boolean syncEnabled = ContentResolver.getSyncAutomaticallyAsUser(account, authority,
410                     userId);
411             boolean authorityIsPending = status == null ? false : status.pending;
412             boolean initialSync = status == null ? false : status.initialize;
413
414             boolean activelySyncing = isSyncing(currentSyncs, account, authority);
415             boolean lastSyncFailed = status != null
416                     && status.lastFailureTime != 0
417                     && status.getLastFailureMesgAsInt(0)
418                        != ContentResolver.SYNC_ERROR_SYNC_ALREADY_IN_PROGRESS;
419             if (!syncEnabled) lastSyncFailed = false;
420             if (lastSyncFailed && !activelySyncing && !authorityIsPending) {
421                 syncIsFailing = true;
422             }
423             if (Log.isLoggable(TAG, Log.VERBOSE)) {
424                 Log.d(TAG, "Update sync status: " + account + " " + authority +
425                         " active = " + activelySyncing + " pend =" +  authorityIsPending);
426             }
427
428             final long successEndTime = (status == null) ? 0 : status.lastSuccessTime;
429             if (!syncEnabled) {
430                 syncPref.setSummary(R.string.sync_disabled);
431             } else if (activelySyncing) {
432                 syncPref.setSummary(R.string.sync_in_progress);
433             } else if (successEndTime != 0) {
434                 date.setTime(successEndTime);
435                 final String timeString = formatSyncDate(date);
436                 syncPref.setSummary(getResources().getString(R.string.last_synced, timeString));
437             } else {
438                 syncPref.setSummary("");
439             }
440             int syncState = ContentResolver.getIsSyncableAsUser(account, authority, userId);
441
442             syncPref.setActive(activelySyncing && (syncState >= 0) &&
443                     !initialSync);
444             syncPref.setPending(authorityIsPending && (syncState >= 0) &&
445                     !initialSync);
446
447             syncPref.setFailed(lastSyncFailed);
448             final boolean oneTimeSyncMode = !ContentResolver.getMasterSyncAutomaticallyAsUser(
449                 userId);
450             syncPref.setOneTimeSyncMode(oneTimeSyncMode);
451             syncPref.setChecked(oneTimeSyncMode || syncEnabled);
452         }
453         mErrorInfoView.setVisibility(syncIsFailing ? View.VISIBLE : View.GONE);
454     }
455
456     @Override
457     public void onAccountsUpdate(final UserHandle userHandle) {
458         super.onAccountsUpdate(userHandle);
459         if (!accountExists(mAccount)) {
460             // The account was deleted
461             finish();
462             return;
463         }
464         updateAccountSwitches();
465         onSyncStateUpdated();
466     }
467
468     private boolean accountExists(Account account) {
469         if (account == null) return false;
470
471         Account[] accounts = AccountManager.get(getActivity()).getAccountsByTypeAsUser(
472                 account.type, mUserHandle);
473         final int count = accounts.length;
474         for (int i = 0; i < count; i++) {
475             if (accounts[i].equals(account)) {
476                 return true;
477             }
478         }
479         return false;
480     }
481
482     private void updateAccountSwitches() {
483         mInvisibleAdapters.clear();
484
485         SyncAdapterType[] syncAdapters = ContentResolver.getSyncAdapterTypesAsUser(
486                 mUserHandle.getIdentifier());
487         ArrayList<String> authorities = new ArrayList<String>();
488         for (int i = 0, n = syncAdapters.length; i < n; i++) {
489             final SyncAdapterType sa = syncAdapters[i];
490             // Only keep track of sync adapters for this account
491             if (!sa.accountType.equals(mAccount.type)) continue;
492             if (sa.isUserVisible()) {
493                 if (Log.isLoggable(TAG, Log.VERBOSE)) {
494                     Log.d(TAG, "updateAccountSwitches: added authority " + sa.authority
495                             + " to accountType " + sa.accountType);
496                 }
497                 authorities.add(sa.authority);
498             } else {
499                 // keep track of invisible sync adapters, so sync now forces
500                 // them to sync as well.
501                 mInvisibleAdapters.add(sa);
502             }
503         }
504
505         for (int i = 0, n = mSwitches.size(); i < n; i++) {
506             getPreferenceScreen().removePreference(mSwitches.get(i));
507         }
508         mSwitches.clear();
509
510         if (Log.isLoggable(TAG, Log.VERBOSE)) {
511             Log.d(TAG, "looking for sync adapters that match account " + mAccount);
512         }
513         for (int j = 0, m = authorities.size(); j < m; j++) {
514             final String authority = authorities.get(j);
515             // We could check services here....
516             int syncState = ContentResolver.getIsSyncableAsUser(mAccount, authority,
517                     mUserHandle.getIdentifier());
518             if (Log.isLoggable(TAG, Log.VERBOSE)) {
519                 Log.d(TAG, "  found authority " + authority + " " + syncState);
520             }
521             if (syncState > 0) {
522                 addSyncStateSwitch(mAccount, authority);
523             }
524         }
525
526         Collections.sort(mSwitches);
527         for (int i = 0, n = mSwitches.size(); i < n; i++) {
528             getPreferenceScreen().addPreference(mSwitches.get(i));
529         }
530     }
531
532     /**
533      * Updates the titlebar with an icon for the provider type.
534      */
535     @Override
536     protected void onAuthDescriptionsUpdated() {
537         super.onAuthDescriptionsUpdated();
538         getPreferenceScreen().removeAll();
539         if (mAccount != null) {
540             mProviderIcon.setImageDrawable(getDrawableForType(mAccount.type));
541             mProviderId.setText(getLabelForType(mAccount.type));
542         }
543         addPreferencesFromResource(R.xml.account_sync_settings);
544     }
545
546     @Override
547     protected int getHelpResource() {
548         return R.string.help_url_accounts;
549     }
550 }