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