Home | History | Annotate | Download | only in accounts
      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     protected static final int MENU_REMOVE_ACCOUNT_ID = Menu.FIRST;
     67     private static final int MENU_SYNC_NOW_ID = Menu.FIRST + 1;
     68     private static final int MENU_SYNC_CANCEL_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 java.text.DateFormat mDateFormat;
     77     private java.text.DateFormat mTimeFormat;
     78     private Account mAccount;
     79     // List of all accounts, updated when accounts are added/removed
     80     // We need to re-scan the accounts on sync events, in case sync state changes.
     81     private Account[] mAccounts;
     82     private ArrayList<SyncStateCheckBoxPreference> mCheckBoxes =
     83                 new ArrayList<SyncStateCheckBoxPreference>();
     84     private ArrayList<String> mInvisibleAdapters = Lists.newArrayList();
     85 
     86     @Override
     87     public Dialog onCreateDialog(final int id) {
     88         Dialog dialog = null;
     89         if (id == REALLY_REMOVE_DIALOG) {
     90             dialog = new AlertDialog.Builder(getActivity())
     91                 .setTitle(R.string.really_remove_account_title)
     92                 .setMessage(R.string.really_remove_account_message)
     93                 .setNegativeButton(android.R.string.cancel, null)
     94                 .setPositiveButton(R.string.remove_account_label,
     95                         new DialogInterface.OnClickListener() {
     96                     public void onClick(DialogInterface dialog, int which) {
     97                         AccountManager.get(AccountSyncSettings.this.getActivity())
     98                                 .removeAccount(mAccount,
     99                                 new AccountManagerCallback<Boolean>() {
    100                             public void run(AccountManagerFuture<Boolean> future) {
    101                                 boolean failed = true;
    102                                 try {
    103                                     if (future.getResult() == true) {
    104                                         failed = false;
    105                                     }
    106                                 } catch (OperationCanceledException e) {
    107                                     // handled below
    108                                 } catch (IOException e) {
    109                                     // handled below
    110                                 } catch (AuthenticatorException e) {
    111                                     // handled below
    112                                 }
    113                                 if (failed) {
    114                                     showDialog(FAILED_REMOVAL_DIALOG);
    115                                 } else {
    116                                     finish();
    117                                 }
    118                             }
    119                         }, null);
    120                     }
    121                 })
    122                 .create();
    123         } else if (id == FAILED_REMOVAL_DIALOG) {
    124             dialog = new AlertDialog.Builder(getActivity())
    125                 .setTitle(R.string.really_remove_account_title)
    126                 .setPositiveButton(android.R.string.ok, null)
    127                 .setMessage(R.string.remove_account_failed)
    128                 .create();
    129         } else if (id == CANT_DO_ONETIME_SYNC_DIALOG) {
    130             dialog = new AlertDialog.Builder(getActivity())
    131                 .setTitle(R.string.cant_sync_dialog_title)
    132                 .setMessage(R.string.cant_sync_dialog_message)
    133                 .setPositiveButton(android.R.string.ok, null)
    134                 .create();
    135         }
    136         return dialog;
    137     }
    138 
    139     @Override
    140     public void onCreate(Bundle icicle) {
    141         super.onCreate(icicle);
    142 
    143         setHasOptionsMenu(true);
    144     }
    145 
    146     @Override
    147     public View onCreateView(LayoutInflater inflater, ViewGroup container,
    148             Bundle savedInstanceState) {
    149         final View view = inflater.inflate(R.layout.account_sync_screen, container, false);
    150 
    151         initializeUi(view);
    152 
    153         return view;
    154     }
    155 
    156     protected void initializeUi(final View rootView) {
    157         addPreferencesFromResource(R.xml.account_sync_settings);
    158 
    159         mErrorInfoView = (TextView) rootView.findViewById(R.id.sync_settings_error_info);
    160         mErrorInfoView.setVisibility(View.GONE);
    161 
    162         mUserId = (TextView) rootView.findViewById(R.id.user_id);
    163         mProviderId = (TextView) rootView.findViewById(R.id.provider_id);
    164         mProviderIcon = (ImageView) rootView.findViewById(R.id.provider_icon);
    165     }
    166 
    167     @Override
    168     public void onActivityCreated(Bundle savedInstanceState) {
    169         super.onActivityCreated(savedInstanceState);
    170 
    171         final Activity activity = getActivity();
    172 
    173         mDateFormat = DateFormat.getDateFormat(activity);
    174         mTimeFormat = DateFormat.getTimeFormat(activity);
    175 
    176         Bundle arguments = getArguments();
    177         if (arguments == null) {
    178             Log.e(TAG, "No arguments provided when starting intent. ACCOUNT_KEY needed.");
    179             return;
    180         }
    181 
    182         mAccount = (Account) arguments.getParcelable(ACCOUNT_KEY);
    183         if (mAccount != null) {
    184             if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "Got account: " + mAccount);
    185             mUserId.setText(mAccount.name);
    186             mProviderId.setText(mAccount.type);
    187         }
    188     }
    189 
    190     @Override
    191     public void onResume() {
    192         final Activity activity = getActivity();
    193         AccountManager.get(activity).addOnAccountsUpdatedListener(this, null, false);
    194         updateAuthDescriptions();
    195         onAccountsUpdated(AccountManager.get(activity).getAccounts());
    196 
    197         super.onResume();
    198     }
    199 
    200     @Override
    201     public void onPause() {
    202         super.onPause();
    203         AccountManager.get(getActivity()).removeOnAccountsUpdatedListener(this);
    204     }
    205 
    206     private void addSyncStateCheckBox(Account account, String authority) {
    207         SyncStateCheckBoxPreference item =
    208                 new SyncStateCheckBoxPreference(getActivity(), account, authority);
    209         item.setPersistent(false);
    210         final ProviderInfo providerInfo = getPackageManager().resolveContentProvider(authority, 0);
    211         CharSequence providerLabel = providerInfo != null
    212                 ? providerInfo.loadLabel(getPackageManager()) : null;
    213         if (TextUtils.isEmpty(providerLabel)) {
    214             Log.e(TAG, "Provider needs a label for authority '" + authority + "'");
    215             providerLabel = authority;
    216         }
    217         String title = getString(R.string.sync_item_title, providerLabel);
    218         item.setTitle(title);
    219         item.setKey(authority);
    220         mCheckBoxes.add(item);
    221     }
    222 
    223     @Override
    224     public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
    225         super.onCreateOptionsMenu(menu, inflater);
    226 
    227         MenuItem removeAccount = menu.add(0, MENU_REMOVE_ACCOUNT_ID, 0,
    228                                           getString(R.string.remove_account_label))
    229                 .setIcon(R.drawable.ic_menu_delete_holo_dark);
    230         MenuItem syncNow = menu.add(0, MENU_SYNC_NOW_ID, 0,
    231                                           getString(R.string.sync_menu_sync_now))
    232                 .setIcon(R.drawable.ic_menu_refresh_holo_dark);
    233         MenuItem syncCancel = menu.add(0, MENU_SYNC_CANCEL_ID, 0,
    234                                        getString(R.string.sync_menu_sync_cancel))
    235                 .setIcon(com.android.internal.R.drawable.ic_menu_close_clear_cancel);
    236 
    237         removeAccount.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER |
    238                 MenuItem.SHOW_AS_ACTION_WITH_TEXT);
    239         syncNow.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER |
    240                 MenuItem.SHOW_AS_ACTION_WITH_TEXT);
    241         syncCancel.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER |
    242                 MenuItem.SHOW_AS_ACTION_WITH_TEXT);
    243     }
    244 
    245     @Override
    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
    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
    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 
    298     private void startSyncForEnabledProviders() {
    299         requestOrCancelSyncForEnabledProviders(true /* start them */);
    300         getActivity().invalidateOptionsMenu();
    301     }
    302 
    303     private void cancelSyncForEnabledProviders() {
    304         requestOrCancelSyncForEnabledProviders(false /* cancel them */);
    305         getActivity().invalidateOptionsMenu();
    306     }
    307 
    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 
    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 
    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
    350     protected void onSyncStateUpdated() {
    351         if (!isResumed()) return;
    352         setFeedsState();
    353     }
    354 
    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 (successEndTime != 0) {
    395                 date.setTime(successEndTime);
    396                 final String timeString = mDateFormat.format(date) + " "
    397                         + mTimeFormat.format(date);
    398                 syncPref.setSummary(timeString);
    399             } else {
    400                 syncPref.setSummary("");
    401             }
    402             int syncState = ContentResolver.getIsSyncable(account, authority);
    403 
    404             syncPref.setActive(activelySyncing && (syncState >= 0) &&
    405                     !initialSync);
    406             syncPref.setPending(authorityIsPending && (syncState >= 0) &&
    407                     !initialSync);
    408 
    409             syncPref.setFailed(lastSyncFailed);
    410             ConnectivityManager connManager =
    411                 (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
    412             final boolean masterSyncAutomatically = ContentResolver.getMasterSyncAutomatically();
    413             final boolean backgroundDataEnabled = connManager.getBackgroundDataSetting();
    414             final boolean oneTimeSyncMode = !masterSyncAutomatically || !backgroundDataEnabled;
    415             syncPref.setOneTimeSyncMode(oneTimeSyncMode);
    416             syncPref.setChecked(oneTimeSyncMode || syncEnabled);
    417         }
    418         mErrorInfoView.setVisibility(syncIsFailing ? View.VISIBLE : View.GONE);
    419         getActivity().invalidateOptionsMenu();
    420     }
    421 
    422     @Override
    423     public void onAccountsUpdated(Account[] accounts) {
    424         super.onAccountsUpdated(accounts);
    425         mAccounts = accounts;
    426         updateAccountCheckboxes(accounts);
    427         onSyncStateUpdated();
    428     }
    429 
    430     private void updateAccountCheckboxes(Account[] accounts) {
    431         mInvisibleAdapters.clear();
    432 
    433         SyncAdapterType[] syncAdapters = ContentResolver.getSyncAdapterTypes();
    434         HashMap<String, ArrayList<String>> accountTypeToAuthorities =
    435             Maps.newHashMap();
    436         for (int i = 0, n = syncAdapters.length; i < n; i++) {
    437             final SyncAdapterType sa = syncAdapters[i];
    438             if (sa.isUserVisible()) {
    439                 ArrayList<String> authorities = accountTypeToAuthorities.get(sa.accountType);
    440                 if (authorities == null) {
    441                     authorities = new ArrayList<String>();
    442                     accountTypeToAuthorities.put(sa.accountType, authorities);
    443                 }
    444                 if (Log.isLoggable(TAG, Log.VERBOSE)) {
    445                     Log.d(TAG, "onAccountUpdated: added authority " + sa.authority
    446                             + " to accountType " + sa.accountType);
    447                 }
    448                 authorities.add(sa.authority);
    449             } else {
    450                 // keep track of invisible sync adapters, so sync now forces
    451                 // them to sync as well.
    452                 mInvisibleAdapters.add(sa.authority);
    453             }
    454         }
    455 
    456         for (int i = 0, n = mCheckBoxes.size(); i < n; i++) {
    457             getPreferenceScreen().removePreference(mCheckBoxes.get(i));
    458         }
    459         mCheckBoxes.clear();
    460 
    461         for (int i = 0, n = accounts.length; i < n; i++) {
    462             final Account account = accounts[i];
    463             if (Log.isLoggable(TAG, Log.VERBOSE)) {
    464                 Log.d(TAG, "looking for sync adapters that match account " + account);
    465             }
    466             final ArrayList<String> authorities = accountTypeToAuthorities.get(account.type);
    467             if (authorities != null && (mAccount == null || mAccount.equals(account))) {
    468                 for (int j = 0, m = authorities.size(); j < m; j++) {
    469                     final String authority = authorities.get(j);
    470                     // We could check services here....
    471                     int syncState = ContentResolver.getIsSyncable(account, authority);
    472                     if (Log.isLoggable(TAG, Log.VERBOSE)) {
    473                         Log.d(TAG, "  found authority " + authority + " " + syncState);
    474                     }
    475                     if (syncState > 0) {
    476                         addSyncStateCheckBox(account, authority);
    477                     }
    478                 }
    479             }
    480         }
    481 
    482         Collections.sort(mCheckBoxes);
    483         for (int i = 0, n = mCheckBoxes.size(); i < n; i++) {
    484             getPreferenceScreen().addPreference(mCheckBoxes.get(i));
    485         }
    486     }
    487 
    488     /**
    489      * Updates the titlebar with an icon for the provider type.
    490      */
    491     @Override
    492     protected void onAuthDescriptionsUpdated() {
    493         super.onAuthDescriptionsUpdated();
    494         getPreferenceScreen().removeAll();
    495         if (mAccount != null) {
    496             mProviderIcon.setImageDrawable(getDrawableForType(mAccount.type));
    497             mProviderId.setText(getLabelForType(mAccount.type));
    498             PreferenceScreen prefs = addPreferencesForType(mAccount.type);
    499             if (prefs != null) {
    500                 updatePreferenceIntents(prefs);
    501             }
    502         }
    503         addPreferencesFromResource(R.xml.account_sync_settings);
    504     }
    505 
    506     private void updatePreferenceIntents(PreferenceScreen prefs) {
    507         for (int i = 0; i < prefs.getPreferenceCount(); i++) {
    508             Intent intent = prefs.getPreference(i).getIntent();
    509             if (intent != null) {
    510                 intent.putExtra(ACCOUNT_KEY, mAccount);
    511                 // This is somewhat of a hack. Since the preference screen we're accessing comes
    512                 // from another package, we need to modify the intent to launch it with
    513                 // FLAG_ACTIVITY_NEW_TASK.
    514                 // TODO: Do something smarter if we ever have PreferenceScreens of our own.
    515                 intent.setFlags(intent.getFlags() | Intent.FLAG_ACTIVITY_NEW_TASK);
    516             }
    517         }
    518     }
    519 }
    520