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.google.android.collect.Lists;
21 import android.accounts.Account;
22 import android.accounts.AccountManager;
23 import android.accounts.AccountManagerCallback;
24 import android.accounts.AccountManagerFuture;
25 import android.accounts.AuthenticatorException;
26 import android.accounts.OperationCanceledException;
27 import android.app.Activity;
28 import android.app.AlertDialog;
29 import android.app.Dialog;
30 import android.content.ContentResolver;
31 import android.content.Context;
32 import android.content.DialogInterface;
33 import android.content.SyncAdapterType;
34 import android.content.SyncInfo;
35 import android.content.SyncStatusInfo;
36 import android.content.pm.ProviderInfo;
37 import android.os.Bundle;
38 import android.os.UserHandle;
39 import android.os.UserManager;
40 import android.preference.Preference;
41 import android.preference.PreferenceScreen;
42 import android.text.TextUtils;
43 import android.util.Log;
44 import android.view.LayoutInflater;
45 import android.view.Menu;
46 import android.view.MenuInflater;
47 import android.view.MenuItem;
48 import android.view.View;
49 import android.view.ViewGroup;
50 import android.widget.ImageView;
51 import android.widget.ListView;
52 import android.widget.TextView;
54 import com.android.settings.R;
55 import com.android.settings.Utils;
57 import java.io.IOException;
58 import java.util.ArrayList;
59 import java.util.Collections;
60 import java.util.Date;
61 import java.util.List;
63 public class AccountSyncSettings extends AccountPreferenceBase {
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 private ArrayList<SyncStateSwitchPreference> mSwitches =
78 new ArrayList<SyncStateSwitchPreference>();
79 private ArrayList<SyncAdapterType> mInvisibleAdapters = Lists.newArrayList();
82 public Dialog onCreateDialog(final int id) {
84 if (id == REALLY_REMOVE_DIALOG) {
85 dialog = new AlertDialog.Builder(getActivity())
86 .setTitle(R.string.really_remove_account_title)
87 .setMessage(R.string.really_remove_account_message)
88 .setNegativeButton(android.R.string.cancel, null)
89 .setPositiveButton(R.string.remove_account_label,
90 new DialogInterface.OnClickListener() {
92 public void onClick(DialogInterface dialog, int which) {
93 Activity activity = getActivity();
94 AccountManager.get(activity)
95 .removeAccountAsUser(mAccount, activity,
96 new AccountManagerCallback<Bundle>() {
98 public void run(AccountManagerFuture<Bundle> future) {
99 // If already out of this screen, don't proceed.
100 if (!AccountSyncSettings.this.isResumed()) {
103 boolean failed = true;
105 if (future.getResult()
106 .getBoolean(AccountManager.KEY_BOOLEAN_RESULT)) {
109 } catch (OperationCanceledException e) {
111 } catch (IOException e) {
113 } catch (AuthenticatorException e) {
116 if (failed && getActivity() != null &&
117 !getActivity().isFinishing()) {
118 showDialog(FAILED_REMOVAL_DIALOG);
123 }, null, mUserHandle);
127 } else if (id == FAILED_REMOVAL_DIALOG) {
128 dialog = new AlertDialog.Builder(getActivity())
129 .setTitle(R.string.really_remove_account_title)
130 .setPositiveButton(android.R.string.ok, null)
131 .setMessage(R.string.remove_account_failed)
133 } else if (id == CANT_DO_ONETIME_SYNC_DIALOG) {
134 dialog = new AlertDialog.Builder(getActivity())
135 .setTitle(R.string.cant_sync_dialog_title)
136 .setMessage(R.string.cant_sync_dialog_message)
137 .setPositiveButton(android.R.string.ok, null)
144 public void onCreate(Bundle icicle) {
145 super.onCreate(icicle);
147 setHasOptionsMenu(true);
151 public View onCreateView(LayoutInflater inflater, ViewGroup container,
152 Bundle savedInstanceState) {
153 final View view = inflater.inflate(R.layout.account_sync_screen, container, false);
155 final ListView list = (ListView) view.findViewById(android.R.id.list);
156 Utils.prepareCustomPreferencesList(container, view, list, false);
163 protected void initializeUi(final View rootView) {
164 addPreferencesFromResource(R.xml.account_sync_settings);
166 mErrorInfoView = (TextView) rootView.findViewById(R.id.sync_settings_error_info);
167 mErrorInfoView.setVisibility(View.GONE);
169 mUserId = (TextView) rootView.findViewById(R.id.user_id);
170 mProviderId = (TextView) rootView.findViewById(R.id.provider_id);
171 mProviderIcon = (ImageView) rootView.findViewById(R.id.provider_icon);
175 public void onActivityCreated(Bundle savedInstanceState) {
176 super.onActivityCreated(savedInstanceState);
178 Bundle arguments = getArguments();
179 if (arguments == null) {
180 Log.e(TAG, "No arguments provided when starting intent. ACCOUNT_KEY needed.");
184 mAccount = (Account) arguments.getParcelable(ACCOUNT_KEY);
185 if (!accountExists(mAccount)) {
186 Log.e(TAG, "Account provided does not exist: " + mAccount);
190 if (Log.isLoggable(TAG, Log.VERBOSE)) {
191 Log.v(TAG, "Got account: " + mAccount);
193 mUserId.setText(mAccount.name);
194 mProviderId.setText(mAccount.type);
198 public void onResume() {
199 mAuthenticatorHelper.listenToAccountUpdates();
200 updateAuthDescriptions();
201 onAccountsUpdate(UserHandle.getCallingUserHandle());
207 public void onPause() {
209 mAuthenticatorHelper.stopListeningToAccountUpdates();
212 private void addSyncStateSwitch(Account account, String authority) {
213 SyncStateSwitchPreference item =
214 new SyncStateSwitchPreference(getActivity(), account, authority);
215 item.setPersistent(false);
216 final ProviderInfo providerInfo = getPackageManager().resolveContentProviderAsUser(
217 authority, 0, mUserHandle.getIdentifier());
218 if (providerInfo == null) {
221 CharSequence providerLabel = providerInfo.loadLabel(getPackageManager());
222 if (TextUtils.isEmpty(providerLabel)) {
223 Log.e(TAG, "Provider needs a label for authority '" + authority + "'");
226 String title = getString(R.string.sync_item_title, providerLabel);
227 item.setTitle(title);
228 item.setKey(authority);
233 public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
235 MenuItem syncNow = menu.add(0, MENU_SYNC_NOW_ID, 0,
236 getString(R.string.sync_menu_sync_now))
237 .setIcon(R.drawable.ic_menu_refresh_holo_dark);
238 MenuItem syncCancel = menu.add(0, MENU_SYNC_CANCEL_ID, 0,
239 getString(R.string.sync_menu_sync_cancel))
240 .setIcon(com.android.internal.R.drawable.ic_menu_close_clear_cancel);
241 final UserManager um = (UserManager) getSystemService(Context.USER_SERVICE);
242 if (!um.hasUserRestriction(UserManager.DISALLOW_MODIFY_ACCOUNTS, mUserHandle)) {
243 MenuItem removeAccount = menu.add(0, MENU_REMOVE_ACCOUNT_ID, 0,
244 getString(R.string.remove_account_label))
245 .setIcon(R.drawable.ic_menu_delete);
246 removeAccount.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER |
247 MenuItem.SHOW_AS_ACTION_WITH_TEXT);
249 syncNow.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER |
250 MenuItem.SHOW_AS_ACTION_WITH_TEXT);
251 syncCancel.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER |
252 MenuItem.SHOW_AS_ACTION_WITH_TEXT);
254 super.onCreateOptionsMenu(menu, inflater);
258 public void onPrepareOptionsMenu(Menu menu) {
259 super.onPrepareOptionsMenu(menu);
260 // Note that this also counts accounts that are not currently displayed
261 boolean syncActive = !ContentResolver.getCurrentSyncsAsUser(
262 mUserHandle.getIdentifier()).isEmpty();
263 menu.findItem(MENU_SYNC_NOW_ID).setVisible(!syncActive);
264 menu.findItem(MENU_SYNC_CANCEL_ID).setVisible(syncActive);
268 public boolean onOptionsItemSelected(MenuItem item) {
269 switch (item.getItemId()) {
270 case MENU_SYNC_NOW_ID:
271 startSyncForEnabledProviders();
273 case MENU_SYNC_CANCEL_ID:
274 cancelSyncForEnabledProviders();
276 case MENU_REMOVE_ACCOUNT_ID:
277 showDialog(REALLY_REMOVE_DIALOG);
280 return super.onOptionsItemSelected(item);
284 public boolean onPreferenceTreeClick(PreferenceScreen preferences, Preference preference) {
285 if (preference instanceof SyncStateSwitchPreference) {
286 SyncStateSwitchPreference syncPref = (SyncStateSwitchPreference) preference;
287 String authority = syncPref.getAuthority();
288 Account account = syncPref.getAccount();
289 final int userId = mUserHandle.getIdentifier();
290 boolean syncAutomatically = ContentResolver.getSyncAutomaticallyAsUser(account,
292 if (syncPref.isOneTimeSyncMode()) {
293 requestOrCancelSync(account, authority, true);
295 boolean syncOn = syncPref.isChecked();
296 boolean oldSyncState = syncAutomatically;
297 if (syncOn != oldSyncState) {
298 // if we're enabling sync, this will request a sync as well
299 ContentResolver.setSyncAutomaticallyAsUser(account, authority, syncOn, userId);
300 // if the master sync switch is off, the request above will
301 // get dropped. when the user clicks on this toggle,
302 // we want to force the sync, however.
303 if (!ContentResolver.getMasterSyncAutomaticallyAsUser(userId) || !syncOn) {
304 requestOrCancelSync(account, authority, syncOn);
310 return super.onPreferenceTreeClick(preferences, preference);
314 private void startSyncForEnabledProviders() {
315 requestOrCancelSyncForEnabledProviders(true /* start them */);
316 final Activity activity = getActivity();
317 if (activity != null) {
318 activity.invalidateOptionsMenu();
322 private void cancelSyncForEnabledProviders() {
323 requestOrCancelSyncForEnabledProviders(false /* cancel them */);
324 final Activity activity = getActivity();
325 if (activity != null) {
326 activity.invalidateOptionsMenu();
330 private void requestOrCancelSyncForEnabledProviders(boolean startSync) {
331 // sync everything that the user has enabled
332 int count = getPreferenceScreen().getPreferenceCount();
333 for (int i = 0; i < count; i++) {
334 Preference pref = getPreferenceScreen().getPreference(i);
335 if (! (pref instanceof SyncStateSwitchPreference)) {
338 SyncStateSwitchPreference syncPref = (SyncStateSwitchPreference) pref;
339 if (!syncPref.isChecked()) {
342 requestOrCancelSync(syncPref.getAccount(), syncPref.getAuthority(), startSync);
344 // plus whatever the system needs to sync, e.g., invisible sync adapters
345 if (mAccount != null) {
346 for (SyncAdapterType syncAdapter : mInvisibleAdapters) {
347 requestOrCancelSync(mAccount, syncAdapter.authority, startSync);
352 private void requestOrCancelSync(Account account, String authority, boolean flag) {
354 Bundle extras = new Bundle();
355 extras.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);
356 ContentResolver.requestSyncAsUser(account, authority, mUserHandle.getIdentifier(),
359 ContentResolver.cancelSyncAsUser(account, authority, mUserHandle.getIdentifier());
363 private boolean isSyncing(List<SyncInfo> currentSyncs, Account account, String authority) {
364 for (SyncInfo syncInfo : currentSyncs) {
365 if (syncInfo.account.equals(account) && syncInfo.authority.equals(authority)) {
373 protected void onSyncStateUpdated() {
374 if (!isResumed()) return;
376 final Activity activity = getActivity();
377 if (activity != null) {
378 activity.invalidateOptionsMenu();
382 private void setFeedsState() {
383 // iterate over all the preferences, setting the state properly for each
384 Date date = new Date();
385 final int userId = mUserHandle.getIdentifier();
386 List<SyncInfo> currentSyncs = ContentResolver.getCurrentSyncsAsUser(userId);
387 boolean syncIsFailing = false;
389 // Refresh the sync status switches - some syncs may have become active.
390 updateAccountSwitches();
392 for (int i = 0, count = getPreferenceScreen().getPreferenceCount(); i < count; i++) {
393 Preference pref = getPreferenceScreen().getPreference(i);
394 if (! (pref instanceof SyncStateSwitchPreference)) {
397 SyncStateSwitchPreference syncPref = (SyncStateSwitchPreference) pref;
399 String authority = syncPref.getAuthority();
400 Account account = syncPref.getAccount();
402 SyncStatusInfo status = ContentResolver.getSyncStatusAsUser(account, authority, userId);
403 boolean syncEnabled = ContentResolver.getSyncAutomaticallyAsUser(account, authority,
405 boolean authorityIsPending = status == null ? false : status.pending;
406 boolean initialSync = status == null ? false : status.initialize;
408 boolean activelySyncing = isSyncing(currentSyncs, account, authority);
409 boolean lastSyncFailed = status != null
410 && status.lastFailureTime != 0
411 && status.getLastFailureMesgAsInt(0)
412 != ContentResolver.SYNC_ERROR_SYNC_ALREADY_IN_PROGRESS;
413 if (!syncEnabled) lastSyncFailed = false;
414 if (lastSyncFailed && !activelySyncing && !authorityIsPending) {
415 syncIsFailing = true;
417 if (Log.isLoggable(TAG, Log.VERBOSE)) {
418 Log.d(TAG, "Update sync status: " + account + " " + authority +
419 " active = " + activelySyncing + " pend =" + authorityIsPending);
422 final long successEndTime = (status == null) ? 0 : status.lastSuccessTime;
424 syncPref.setSummary(R.string.sync_disabled);
425 } else if (activelySyncing) {
426 syncPref.setSummary(R.string.sync_in_progress);
427 } else if (successEndTime != 0) {
428 date.setTime(successEndTime);
429 final String timeString = formatSyncDate(date);
430 syncPref.setSummary(getResources().getString(R.string.last_synced, timeString));
432 syncPref.setSummary("");
434 int syncState = ContentResolver.getIsSyncableAsUser(account, authority, userId);
436 syncPref.setActive(activelySyncing && (syncState >= 0) &&
438 syncPref.setPending(authorityIsPending && (syncState >= 0) &&
441 syncPref.setFailed(lastSyncFailed);
442 final boolean oneTimeSyncMode = !ContentResolver.getMasterSyncAutomaticallyAsUser(
444 syncPref.setOneTimeSyncMode(oneTimeSyncMode);
445 syncPref.setChecked(oneTimeSyncMode || syncEnabled);
447 mErrorInfoView.setVisibility(syncIsFailing ? View.VISIBLE : View.GONE);
451 public void onAccountsUpdate(final UserHandle userHandle) {
452 super.onAccountsUpdate(userHandle);
453 if (!accountExists(mAccount)) {
454 // The account was deleted
458 updateAccountSwitches();
459 onSyncStateUpdated();
462 private boolean accountExists(Account account) {
463 if (account == null) return false;
465 Account[] accounts = AccountManager.get(getActivity()).getAccountsByTypeAsUser(
466 account.type, mUserHandle);
467 final int count = accounts.length;
468 for (int i = 0; i < count; i++) {
469 if (accounts[i].equals(account)) {
476 private void updateAccountSwitches() {
477 mInvisibleAdapters.clear();
479 SyncAdapterType[] syncAdapters = ContentResolver.getSyncAdapterTypesAsUser(
480 mUserHandle.getIdentifier());
481 ArrayList<String> authorities = new ArrayList<String>();
482 for (int i = 0, n = syncAdapters.length; i < n; i++) {
483 final SyncAdapterType sa = syncAdapters[i];
484 // Only keep track of sync adapters for this account
485 if (!sa.accountType.equals(mAccount.type)) continue;
486 if (sa.isUserVisible()) {
487 if (Log.isLoggable(TAG, Log.VERBOSE)) {
488 Log.d(TAG, "updateAccountSwitches: added authority " + sa.authority
489 + " to accountType " + sa.accountType);
491 authorities.add(sa.authority);
493 // keep track of invisible sync adapters, so sync now forces
494 // them to sync as well.
495 mInvisibleAdapters.add(sa);
499 for (int i = 0, n = mSwitches.size(); i < n; i++) {
500 getPreferenceScreen().removePreference(mSwitches.get(i));
504 if (Log.isLoggable(TAG, Log.VERBOSE)) {
505 Log.d(TAG, "looking for sync adapters that match account " + mAccount);
507 for (int j = 0, m = authorities.size(); j < m; j++) {
508 final String authority = authorities.get(j);
509 // We could check services here....
510 int syncState = ContentResolver.getIsSyncableAsUser(mAccount, authority,
511 mUserHandle.getIdentifier());
512 if (Log.isLoggable(TAG, Log.VERBOSE)) {
513 Log.d(TAG, " found authority " + authority + " " + syncState);
516 addSyncStateSwitch(mAccount, authority);
520 Collections.sort(mSwitches);
521 for (int i = 0, n = mSwitches.size(); i < n; i++) {
522 getPreferenceScreen().addPreference(mSwitches.get(i));
527 * Updates the titlebar with an icon for the provider type.
530 protected void onAuthDescriptionsUpdated() {
531 super.onAuthDescriptionsUpdated();
532 getPreferenceScreen().removeAll();
533 if (mAccount != null) {
534 mProviderIcon.setImageDrawable(getDrawableForType(mAccount.type));
535 mProviderId.setText(getLabelForType(mAccount.type));
537 addPreferencesFromResource(R.xml.account_sync_settings);
541 protected int getHelpResource() {
542 return R.string.help_url_accounts;