2 * Copyright (C) 2008 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.settings.accounts;
19 import com.android.internal.logging.MetricsLogger;
20 import com.google.android.collect.Lists;
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;
55 import com.android.settings.R;
56 import com.android.settings.Utils;
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;
64 public class AccountSyncSettings extends AccountPreferenceBase {
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();
83 public Dialog onCreateDialog(final int id) {
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() {
93 public void onClick(DialogInterface dialog, int which) {
94 Activity activity = getActivity();
95 AccountManager.get(activity)
96 .removeAccountAsUser(mAccount, activity,
97 new AccountManagerCallback<Bundle>() {
99 public void run(AccountManagerFuture<Bundle> future) {
100 // If already out of this screen, don't proceed.
101 if (!AccountSyncSettings.this.isResumed()) {
104 boolean failed = true;
106 if (future.getResult()
107 .getBoolean(AccountManager.KEY_BOOLEAN_RESULT)) {
110 } catch (OperationCanceledException e) {
112 } catch (IOException e) {
114 } catch (AuthenticatorException e) {
117 if (failed && getActivity() != null &&
118 !getActivity().isFinishing()) {
119 showDialog(FAILED_REMOVAL_DIALOG);
124 }, null, mUserHandle);
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)
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)
145 protected int getMetricsCategory() {
146 return MetricsLogger.ACCOUNTS_ACCOUNT_SYNC;
150 public void onCreate(Bundle icicle) {
151 super.onCreate(icicle);
153 setHasOptionsMenu(true);
157 public View onCreateView(LayoutInflater inflater, ViewGroup container,
158 Bundle savedInstanceState) {
159 final View view = inflater.inflate(R.layout.account_sync_screen, container, false);
161 final ListView list = (ListView) view.findViewById(android.R.id.list);
162 Utils.prepareCustomPreferencesList(container, view, list, false);
169 protected void initializeUi(final View rootView) {
170 addPreferencesFromResource(R.xml.account_sync_settings);
172 mErrorInfoView = (TextView) rootView.findViewById(R.id.sync_settings_error_info);
173 mErrorInfoView.setVisibility(View.GONE);
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);
181 public void onActivityCreated(Bundle savedInstanceState) {
182 super.onActivityCreated(savedInstanceState);
184 Bundle arguments = getArguments();
185 if (arguments == null) {
186 Log.e(TAG, "No arguments provided when starting intent. ACCOUNT_KEY needed.");
190 mAccount = (Account) arguments.getParcelable(ACCOUNT_KEY);
191 if (!accountExists(mAccount)) {
192 Log.e(TAG, "Account provided does not exist: " + mAccount);
196 if (Log.isLoggable(TAG, Log.VERBOSE)) {
197 Log.v(TAG, "Got account: " + mAccount);
199 mUserId.setText(mAccount.name);
200 mProviderId.setText(mAccount.type);
204 public void onResume() {
205 mAuthenticatorHelper.listenToAccountUpdates();
206 updateAuthDescriptions();
207 onAccountsUpdate(UserHandle.getCallingUserHandle());
213 public void onPause() {
215 mAuthenticatorHelper.stopListeningToAccountUpdates();
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) {
227 CharSequence providerLabel = providerInfo.loadLabel(getPackageManager());
228 if (TextUtils.isEmpty(providerLabel)) {
229 Log.e(TAG, "Provider needs a label for authority '" + authority + "'");
232 String title = getString(R.string.sync_item_title, providerLabel);
233 item.setTitle(title);
234 item.setKey(authority);
239 public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
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);
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);
260 super.onCreateOptionsMenu(menu, inflater);
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);
274 public boolean onOptionsItemSelected(MenuItem item) {
275 switch (item.getItemId()) {
276 case MENU_SYNC_NOW_ID:
277 startSyncForEnabledProviders();
279 case MENU_SYNC_CANCEL_ID:
280 cancelSyncForEnabledProviders();
282 case MENU_REMOVE_ACCOUNT_ID:
283 showDialog(REALLY_REMOVE_DIALOG);
286 return super.onOptionsItemSelected(item);
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,
298 if (syncPref.isOneTimeSyncMode()) {
299 requestOrCancelSync(account, authority, true);
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);
316 return super.onPreferenceTreeClick(preferences, preference);
320 private void startSyncForEnabledProviders() {
321 requestOrCancelSyncForEnabledProviders(true /* start them */);
322 final Activity activity = getActivity();
323 if (activity != null) {
324 activity.invalidateOptionsMenu();
328 private void cancelSyncForEnabledProviders() {
329 requestOrCancelSyncForEnabledProviders(false /* cancel them */);
330 final Activity activity = getActivity();
331 if (activity != null) {
332 activity.invalidateOptionsMenu();
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)) {
344 SyncStateSwitchPreference syncPref = (SyncStateSwitchPreference) pref;
345 if (!syncPref.isChecked()) {
348 requestOrCancelSync(syncPref.getAccount(), syncPref.getAuthority(), startSync);
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);
358 private void requestOrCancelSync(Account account, String authority, boolean flag) {
360 Bundle extras = new Bundle();
361 extras.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);
362 ContentResolver.requestSyncAsUser(account, authority, mUserHandle.getIdentifier(),
365 ContentResolver.cancelSyncAsUser(account, authority, mUserHandle.getIdentifier());
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)) {
379 protected void onSyncStateUpdated() {
380 if (!isResumed()) return;
382 final Activity activity = getActivity();
383 if (activity != null) {
384 activity.invalidateOptionsMenu();
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;
395 // Refresh the sync status switches - some syncs may have become active.
396 updateAccountSwitches();
398 for (int i = 0, count = getPreferenceScreen().getPreferenceCount(); i < count; i++) {
399 Preference pref = getPreferenceScreen().getPreference(i);
400 if (! (pref instanceof SyncStateSwitchPreference)) {
403 SyncStateSwitchPreference syncPref = (SyncStateSwitchPreference) pref;
405 String authority = syncPref.getAuthority();
406 Account account = syncPref.getAccount();
408 SyncStatusInfo status = ContentResolver.getSyncStatusAsUser(account, authority, userId);
409 boolean syncEnabled = ContentResolver.getSyncAutomaticallyAsUser(account, authority,
411 boolean authorityIsPending = status == null ? false : status.pending;
412 boolean initialSync = status == null ? false : status.initialize;
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;
423 if (Log.isLoggable(TAG, Log.VERBOSE)) {
424 Log.d(TAG, "Update sync status: " + account + " " + authority +
425 " active = " + activelySyncing + " pend =" + authorityIsPending);
428 final long successEndTime = (status == null) ? 0 : status.lastSuccessTime;
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));
438 syncPref.setSummary("");
440 int syncState = ContentResolver.getIsSyncableAsUser(account, authority, userId);
442 syncPref.setActive(activelySyncing && (syncState >= 0) &&
444 syncPref.setPending(authorityIsPending && (syncState >= 0) &&
447 syncPref.setFailed(lastSyncFailed);
448 final boolean oneTimeSyncMode = !ContentResolver.getMasterSyncAutomaticallyAsUser(
450 syncPref.setOneTimeSyncMode(oneTimeSyncMode);
451 syncPref.setChecked(oneTimeSyncMode || syncEnabled);
453 mErrorInfoView.setVisibility(syncIsFailing ? View.VISIBLE : View.GONE);
457 public void onAccountsUpdate(final UserHandle userHandle) {
458 super.onAccountsUpdate(userHandle);
459 if (!accountExists(mAccount)) {
460 // The account was deleted
464 updateAccountSwitches();
465 onSyncStateUpdated();
468 private boolean accountExists(Account account) {
469 if (account == null) return false;
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)) {
482 private void updateAccountSwitches() {
483 mInvisibleAdapters.clear();
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);
497 authorities.add(sa.authority);
499 // keep track of invisible sync adapters, so sync now forces
500 // them to sync as well.
501 mInvisibleAdapters.add(sa);
505 for (int i = 0, n = mSwitches.size(); i < n; i++) {
506 getPreferenceScreen().removePreference(mSwitches.get(i));
510 if (Log.isLoggable(TAG, Log.VERBOSE)) {
511 Log.d(TAG, "looking for sync adapters that match account " + mAccount);
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);
522 addSyncStateSwitch(mAccount, authority);
526 Collections.sort(mSwitches);
527 for (int i = 0, n = mSwitches.size(); i < n; i++) {
528 getPreferenceScreen().addPreference(mSwitches.get(i));
533 * Updates the titlebar with an icon for the provider type.
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));
543 addPreferencesFromResource(R.xml.account_sync_settings);
547 protected int getHelpResource() {
548 return R.string.help_url_accounts;