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