The invisible sync adapters array is modified to store the sync adapters instead of their authorities, so that "Sync now" function will only synchronize those with the same account type as the current account. Change-Id: If9b0b32db9828d8671f57a0d7a2f64712a7ebabf
523 lines
22 KiB
Java
523 lines
22 KiB
Java
/*
|
|
* Copyright (C) 2008 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
package com.android.settings.accounts;
|
|
|
|
import android.accounts.Account;
|
|
import android.accounts.AccountManager;
|
|
import android.accounts.AccountManagerCallback;
|
|
import android.accounts.AccountManagerFuture;
|
|
import android.accounts.AuthenticatorException;
|
|
import android.accounts.OperationCanceledException;
|
|
import android.app.Activity;
|
|
import android.app.AlertDialog;
|
|
import android.app.Dialog;
|
|
import android.content.ContentResolver;
|
|
import android.content.Context;
|
|
import android.content.DialogInterface;
|
|
import android.content.Intent;
|
|
import android.content.SyncAdapterType;
|
|
import android.content.SyncInfo;
|
|
import android.content.SyncStatusInfo;
|
|
import android.content.pm.ProviderInfo;
|
|
import android.net.ConnectivityManager;
|
|
import android.os.Bundle;
|
|
import android.preference.Preference;
|
|
import android.preference.PreferenceScreen;
|
|
import android.text.TextUtils;
|
|
import android.text.format.DateFormat;
|
|
import android.util.Log;
|
|
import android.view.LayoutInflater;
|
|
import android.view.Menu;
|
|
import android.view.MenuInflater;
|
|
import android.view.MenuItem;
|
|
import android.view.View;
|
|
import android.view.ViewGroup;
|
|
import android.widget.ImageView;
|
|
import android.widget.TextView;
|
|
|
|
import com.android.settings.R;
|
|
import com.google.android.collect.Lists;
|
|
import com.google.android.collect.Maps;
|
|
|
|
import java.io.IOException;
|
|
import java.util.ArrayList;
|
|
import java.util.Collections;
|
|
import java.util.Date;
|
|
import java.util.HashMap;
|
|
import java.util.List;
|
|
|
|
public class AccountSyncSettings extends AccountPreferenceBase {
|
|
|
|
public static final String ACCOUNT_KEY = "account";
|
|
protected static final int MENU_REMOVE_ACCOUNT_ID = Menu.FIRST;
|
|
private static final int MENU_SYNC_NOW_ID = Menu.FIRST + 1;
|
|
private static final int MENU_SYNC_CANCEL_ID = Menu.FIRST + 2;
|
|
private static final int REALLY_REMOVE_DIALOG = 100;
|
|
private static final int FAILED_REMOVAL_DIALOG = 101;
|
|
private static final int CANT_DO_ONETIME_SYNC_DIALOG = 102;
|
|
private TextView mUserId;
|
|
private TextView mProviderId;
|
|
private ImageView mProviderIcon;
|
|
private TextView mErrorInfoView;
|
|
private java.text.DateFormat mDateFormat;
|
|
private java.text.DateFormat mTimeFormat;
|
|
private Account mAccount;
|
|
// List of all accounts, updated when accounts are added/removed
|
|
// We need to re-scan the accounts on sync events, in case sync state changes.
|
|
private Account[] mAccounts;
|
|
private ArrayList<SyncStateCheckBoxPreference> mCheckBoxes =
|
|
new ArrayList<SyncStateCheckBoxPreference>();
|
|
private ArrayList<SyncAdapterType> mInvisibleAdapters = Lists.newArrayList();
|
|
|
|
@Override
|
|
public Dialog onCreateDialog(final int id) {
|
|
Dialog dialog = null;
|
|
if (id == REALLY_REMOVE_DIALOG) {
|
|
dialog = new AlertDialog.Builder(getActivity())
|
|
.setTitle(R.string.really_remove_account_title)
|
|
.setMessage(R.string.really_remove_account_message)
|
|
.setNegativeButton(android.R.string.cancel, null)
|
|
.setPositiveButton(R.string.remove_account_label,
|
|
new DialogInterface.OnClickListener() {
|
|
public void onClick(DialogInterface dialog, int which) {
|
|
AccountManager.get(AccountSyncSettings.this.getActivity())
|
|
.removeAccount(mAccount,
|
|
new AccountManagerCallback<Boolean>() {
|
|
public void run(AccountManagerFuture<Boolean> future) {
|
|
boolean failed = true;
|
|
try {
|
|
if (future.getResult() == true) {
|
|
failed = false;
|
|
}
|
|
} catch (OperationCanceledException e) {
|
|
// handled below
|
|
} catch (IOException e) {
|
|
// handled below
|
|
} catch (AuthenticatorException e) {
|
|
// handled below
|
|
}
|
|
if (failed) {
|
|
showDialog(FAILED_REMOVAL_DIALOG);
|
|
} else {
|
|
finish();
|
|
}
|
|
}
|
|
}, null);
|
|
}
|
|
})
|
|
.create();
|
|
} else if (id == FAILED_REMOVAL_DIALOG) {
|
|
dialog = new AlertDialog.Builder(getActivity())
|
|
.setTitle(R.string.really_remove_account_title)
|
|
.setPositiveButton(android.R.string.ok, null)
|
|
.setMessage(R.string.remove_account_failed)
|
|
.create();
|
|
} else if (id == CANT_DO_ONETIME_SYNC_DIALOG) {
|
|
dialog = new AlertDialog.Builder(getActivity())
|
|
.setTitle(R.string.cant_sync_dialog_title)
|
|
.setMessage(R.string.cant_sync_dialog_message)
|
|
.setPositiveButton(android.R.string.ok, null)
|
|
.create();
|
|
}
|
|
return dialog;
|
|
}
|
|
|
|
@Override
|
|
public void onCreate(Bundle icicle) {
|
|
super.onCreate(icicle);
|
|
|
|
setHasOptionsMenu(true);
|
|
}
|
|
|
|
@Override
|
|
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
|
Bundle savedInstanceState) {
|
|
final View view = inflater.inflate(R.layout.account_sync_screen, container, false);
|
|
|
|
initializeUi(view);
|
|
|
|
return view;
|
|
}
|
|
|
|
protected void initializeUi(final View rootView) {
|
|
addPreferencesFromResource(R.xml.account_sync_settings);
|
|
|
|
mErrorInfoView = (TextView) rootView.findViewById(R.id.sync_settings_error_info);
|
|
mErrorInfoView.setVisibility(View.GONE);
|
|
|
|
mUserId = (TextView) rootView.findViewById(R.id.user_id);
|
|
mProviderId = (TextView) rootView.findViewById(R.id.provider_id);
|
|
mProviderIcon = (ImageView) rootView.findViewById(R.id.provider_icon);
|
|
}
|
|
|
|
@Override
|
|
public void onActivityCreated(Bundle savedInstanceState) {
|
|
super.onActivityCreated(savedInstanceState);
|
|
|
|
final Activity activity = getActivity();
|
|
|
|
mDateFormat = DateFormat.getDateFormat(activity);
|
|
mTimeFormat = DateFormat.getTimeFormat(activity);
|
|
|
|
Bundle arguments = getArguments();
|
|
if (arguments == null) {
|
|
Log.e(TAG, "No arguments provided when starting intent. ACCOUNT_KEY needed.");
|
|
return;
|
|
}
|
|
|
|
mAccount = (Account) arguments.getParcelable(ACCOUNT_KEY);
|
|
if (mAccount != null) {
|
|
if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "Got account: " + mAccount);
|
|
mUserId.setText(mAccount.name);
|
|
mProviderId.setText(mAccount.type);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onResume() {
|
|
final Activity activity = getActivity();
|
|
AccountManager.get(activity).addOnAccountsUpdatedListener(this, null, false);
|
|
updateAuthDescriptions();
|
|
onAccountsUpdated(AccountManager.get(activity).getAccounts());
|
|
|
|
super.onResume();
|
|
}
|
|
|
|
@Override
|
|
public void onPause() {
|
|
super.onPause();
|
|
AccountManager.get(getActivity()).removeOnAccountsUpdatedListener(this);
|
|
}
|
|
|
|
private void addSyncStateCheckBox(Account account, String authority) {
|
|
SyncStateCheckBoxPreference item =
|
|
new SyncStateCheckBoxPreference(getActivity(), account, authority);
|
|
item.setPersistent(false);
|
|
final ProviderInfo providerInfo = getPackageManager().resolveContentProvider(authority, 0);
|
|
CharSequence providerLabel = providerInfo != null
|
|
? providerInfo.loadLabel(getPackageManager()) : null;
|
|
if (TextUtils.isEmpty(providerLabel)) {
|
|
Log.e(TAG, "Provider needs a label for authority '" + authority + "'");
|
|
providerLabel = authority;
|
|
}
|
|
String title = getString(R.string.sync_item_title, providerLabel);
|
|
item.setTitle(title);
|
|
item.setKey(authority);
|
|
mCheckBoxes.add(item);
|
|
}
|
|
|
|
@Override
|
|
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
|
super.onCreateOptionsMenu(menu, inflater);
|
|
|
|
MenuItem removeAccount = menu.add(0, MENU_REMOVE_ACCOUNT_ID, 0,
|
|
getString(R.string.remove_account_label))
|
|
.setIcon(R.drawable.ic_menu_delete_holo_dark);
|
|
MenuItem syncNow = menu.add(0, MENU_SYNC_NOW_ID, 0,
|
|
getString(R.string.sync_menu_sync_now))
|
|
.setIcon(R.drawable.ic_menu_refresh_holo_dark);
|
|
MenuItem syncCancel = menu.add(0, MENU_SYNC_CANCEL_ID, 0,
|
|
getString(R.string.sync_menu_sync_cancel))
|
|
.setIcon(com.android.internal.R.drawable.ic_menu_close_clear_cancel);
|
|
|
|
removeAccount.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER |
|
|
MenuItem.SHOW_AS_ACTION_WITH_TEXT);
|
|
syncNow.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER |
|
|
MenuItem.SHOW_AS_ACTION_WITH_TEXT);
|
|
syncCancel.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER |
|
|
MenuItem.SHOW_AS_ACTION_WITH_TEXT);
|
|
}
|
|
|
|
@Override
|
|
public void onPrepareOptionsMenu(Menu menu) {
|
|
super.onPrepareOptionsMenu(menu);
|
|
boolean syncActive = ContentResolver.getCurrentSync() != null;
|
|
menu.findItem(MENU_SYNC_NOW_ID).setVisible(!syncActive);
|
|
menu.findItem(MENU_SYNC_CANCEL_ID).setVisible(syncActive);
|
|
}
|
|
|
|
@Override
|
|
public boolean onOptionsItemSelected(MenuItem item) {
|
|
switch (item.getItemId()) {
|
|
case MENU_SYNC_NOW_ID:
|
|
startSyncForEnabledProviders();
|
|
return true;
|
|
case MENU_SYNC_CANCEL_ID:
|
|
cancelSyncForEnabledProviders();
|
|
return true;
|
|
case MENU_REMOVE_ACCOUNT_ID:
|
|
showDialog(REALLY_REMOVE_DIALOG);
|
|
return true;
|
|
}
|
|
return super.onOptionsItemSelected(item);
|
|
}
|
|
|
|
@Override
|
|
public boolean onPreferenceTreeClick(PreferenceScreen preferences, Preference preference) {
|
|
if (preference instanceof SyncStateCheckBoxPreference) {
|
|
SyncStateCheckBoxPreference syncPref = (SyncStateCheckBoxPreference) preference;
|
|
String authority = syncPref.getAuthority();
|
|
Account account = syncPref.getAccount();
|
|
boolean syncAutomatically = ContentResolver.getSyncAutomatically(account, authority);
|
|
if (syncPref.isOneTimeSyncMode()) {
|
|
requestOrCancelSync(account, authority, true);
|
|
} else {
|
|
boolean syncOn = syncPref.isChecked();
|
|
boolean oldSyncState = syncAutomatically;
|
|
if (syncOn != oldSyncState) {
|
|
// if we're enabling sync, this will request a sync as well
|
|
ContentResolver.setSyncAutomatically(account, authority, syncOn);
|
|
// if the master sync switch is off, the request above will
|
|
// get dropped. when the user clicks on this toggle,
|
|
// we want to force the sync, however.
|
|
if (!ContentResolver.getMasterSyncAutomatically() || !syncOn) {
|
|
requestOrCancelSync(account, authority, syncOn);
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
} else {
|
|
return super.onPreferenceTreeClick(preferences, preference);
|
|
}
|
|
}
|
|
|
|
private void startSyncForEnabledProviders() {
|
|
requestOrCancelSyncForEnabledProviders(true /* start them */);
|
|
getActivity().invalidateOptionsMenu();
|
|
}
|
|
|
|
private void cancelSyncForEnabledProviders() {
|
|
requestOrCancelSyncForEnabledProviders(false /* cancel them */);
|
|
getActivity().invalidateOptionsMenu();
|
|
}
|
|
|
|
private void requestOrCancelSyncForEnabledProviders(boolean startSync) {
|
|
// sync everything that the user has enabled
|
|
int count = getPreferenceScreen().getPreferenceCount();
|
|
for (int i = 0; i < count; i++) {
|
|
Preference pref = getPreferenceScreen().getPreference(i);
|
|
if (! (pref instanceof SyncStateCheckBoxPreference)) {
|
|
continue;
|
|
}
|
|
SyncStateCheckBoxPreference syncPref = (SyncStateCheckBoxPreference) pref;
|
|
if (!syncPref.isChecked()) {
|
|
continue;
|
|
}
|
|
requestOrCancelSync(syncPref.getAccount(), syncPref.getAuthority(), startSync);
|
|
}
|
|
// plus whatever the system needs to sync, e.g., invisible sync adapters
|
|
if (mAccount != null) {
|
|
for (SyncAdapterType syncAdapter : mInvisibleAdapters) {
|
|
// invisible sync adapters' account type should be same as current account type
|
|
if (syncAdapter.accountType.equals(mAccount.type)) {
|
|
requestOrCancelSync(mAccount, syncAdapter.authority, startSync);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void requestOrCancelSync(Account account, String authority, boolean flag) {
|
|
if (flag) {
|
|
Bundle extras = new Bundle();
|
|
extras.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);
|
|
ContentResolver.requestSync(account, authority, extras);
|
|
} else {
|
|
ContentResolver.cancelSync(account, authority);
|
|
}
|
|
}
|
|
|
|
private boolean isSyncing(List<SyncInfo> currentSyncs, Account account, String authority) {
|
|
for (SyncInfo syncInfo : currentSyncs) {
|
|
if (syncInfo.account.equals(account) && syncInfo.authority.equals(authority)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
@Override
|
|
protected void onSyncStateUpdated() {
|
|
if (!isResumed()) return;
|
|
setFeedsState();
|
|
}
|
|
|
|
private void setFeedsState() {
|
|
// iterate over all the preferences, setting the state properly for each
|
|
Date date = new Date();
|
|
List<SyncInfo> currentSyncs = ContentResolver.getCurrentSyncs();
|
|
boolean syncIsFailing = false;
|
|
|
|
// Refresh the sync status checkboxes - some syncs may have become active.
|
|
updateAccountCheckboxes(mAccounts);
|
|
|
|
for (int i = 0, count = getPreferenceScreen().getPreferenceCount(); i < count; i++) {
|
|
Preference pref = getPreferenceScreen().getPreference(i);
|
|
if (! (pref instanceof SyncStateCheckBoxPreference)) {
|
|
continue;
|
|
}
|
|
SyncStateCheckBoxPreference syncPref = (SyncStateCheckBoxPreference) pref;
|
|
|
|
String authority = syncPref.getAuthority();
|
|
Account account = syncPref.getAccount();
|
|
|
|
SyncStatusInfo status = ContentResolver.getSyncStatus(account, authority);
|
|
boolean syncEnabled = ContentResolver.getSyncAutomatically(account, authority);
|
|
boolean authorityIsPending = status == null ? false : status.pending;
|
|
boolean initialSync = status == null ? false : status.initialize;
|
|
|
|
boolean activelySyncing = isSyncing(currentSyncs, account, authority);
|
|
boolean lastSyncFailed = status != null
|
|
&& status.lastFailureTime != 0
|
|
&& status.getLastFailureMesgAsInt(0)
|
|
!= ContentResolver.SYNC_ERROR_SYNC_ALREADY_IN_PROGRESS;
|
|
if (!syncEnabled) lastSyncFailed = false;
|
|
if (lastSyncFailed && !activelySyncing && !authorityIsPending) {
|
|
syncIsFailing = true;
|
|
}
|
|
if (Log.isLoggable(TAG, Log.VERBOSE)) {
|
|
Log.d(TAG, "Update sync status: " + account + " " + authority +
|
|
" active = " + activelySyncing + " pend =" + authorityIsPending);
|
|
}
|
|
|
|
final long successEndTime = (status == null) ? 0 : status.lastSuccessTime;
|
|
if (successEndTime != 0) {
|
|
date.setTime(successEndTime);
|
|
final String timeString = mDateFormat.format(date) + " "
|
|
+ mTimeFormat.format(date);
|
|
syncPref.setSummary(timeString);
|
|
} else {
|
|
syncPref.setSummary("");
|
|
}
|
|
int syncState = ContentResolver.getIsSyncable(account, authority);
|
|
|
|
syncPref.setActive(activelySyncing && (syncState >= 0) &&
|
|
!initialSync);
|
|
syncPref.setPending(authorityIsPending && (syncState >= 0) &&
|
|
!initialSync);
|
|
|
|
syncPref.setFailed(lastSyncFailed);
|
|
ConnectivityManager connManager =
|
|
(ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
|
|
final boolean masterSyncAutomatically = ContentResolver.getMasterSyncAutomatically();
|
|
final boolean backgroundDataEnabled = connManager.getBackgroundDataSetting();
|
|
final boolean oneTimeSyncMode = !masterSyncAutomatically || !backgroundDataEnabled;
|
|
syncPref.setOneTimeSyncMode(oneTimeSyncMode);
|
|
syncPref.setChecked(oneTimeSyncMode || syncEnabled);
|
|
}
|
|
mErrorInfoView.setVisibility(syncIsFailing ? View.VISIBLE : View.GONE);
|
|
getActivity().invalidateOptionsMenu();
|
|
}
|
|
|
|
@Override
|
|
public void onAccountsUpdated(Account[] accounts) {
|
|
super.onAccountsUpdated(accounts);
|
|
mAccounts = accounts;
|
|
updateAccountCheckboxes(accounts);
|
|
onSyncStateUpdated();
|
|
}
|
|
|
|
private void updateAccountCheckboxes(Account[] accounts) {
|
|
mInvisibleAdapters.clear();
|
|
|
|
SyncAdapterType[] syncAdapters = ContentResolver.getSyncAdapterTypes();
|
|
HashMap<String, ArrayList<String>> accountTypeToAuthorities =
|
|
Maps.newHashMap();
|
|
for (int i = 0, n = syncAdapters.length; i < n; i++) {
|
|
final SyncAdapterType sa = syncAdapters[i];
|
|
if (sa.isUserVisible()) {
|
|
ArrayList<String> authorities = accountTypeToAuthorities.get(sa.accountType);
|
|
if (authorities == null) {
|
|
authorities = new ArrayList<String>();
|
|
accountTypeToAuthorities.put(sa.accountType, authorities);
|
|
}
|
|
if (Log.isLoggable(TAG, Log.VERBOSE)) {
|
|
Log.d(TAG, "onAccountUpdated: added authority " + sa.authority
|
|
+ " to accountType " + sa.accountType);
|
|
}
|
|
authorities.add(sa.authority);
|
|
} else {
|
|
// keep track of invisible sync adapters, so sync now forces
|
|
// them to sync as well.
|
|
mInvisibleAdapters.add(sa);
|
|
}
|
|
}
|
|
|
|
for (int i = 0, n = mCheckBoxes.size(); i < n; i++) {
|
|
getPreferenceScreen().removePreference(mCheckBoxes.get(i));
|
|
}
|
|
mCheckBoxes.clear();
|
|
|
|
for (int i = 0, n = accounts.length; i < n; i++) {
|
|
final Account account = accounts[i];
|
|
if (Log.isLoggable(TAG, Log.VERBOSE)) {
|
|
Log.d(TAG, "looking for sync adapters that match account " + account);
|
|
}
|
|
final ArrayList<String> authorities = accountTypeToAuthorities.get(account.type);
|
|
if (authorities != null && (mAccount == null || mAccount.equals(account))) {
|
|
for (int j = 0, m = authorities.size(); j < m; j++) {
|
|
final String authority = authorities.get(j);
|
|
// We could check services here....
|
|
int syncState = ContentResolver.getIsSyncable(account, authority);
|
|
if (Log.isLoggable(TAG, Log.VERBOSE)) {
|
|
Log.d(TAG, " found authority " + authority + " " + syncState);
|
|
}
|
|
if (syncState > 0) {
|
|
addSyncStateCheckBox(account, authority);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Collections.sort(mCheckBoxes);
|
|
for (int i = 0, n = mCheckBoxes.size(); i < n; i++) {
|
|
getPreferenceScreen().addPreference(mCheckBoxes.get(i));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Updates the titlebar with an icon for the provider type.
|
|
*/
|
|
@Override
|
|
protected void onAuthDescriptionsUpdated() {
|
|
super.onAuthDescriptionsUpdated();
|
|
getPreferenceScreen().removeAll();
|
|
if (mAccount != null) {
|
|
mProviderIcon.setImageDrawable(getDrawableForType(mAccount.type));
|
|
mProviderId.setText(getLabelForType(mAccount.type));
|
|
PreferenceScreen prefs = addPreferencesForType(mAccount.type);
|
|
if (prefs != null) {
|
|
updatePreferenceIntents(prefs);
|
|
}
|
|
}
|
|
addPreferencesFromResource(R.xml.account_sync_settings);
|
|
}
|
|
|
|
private void updatePreferenceIntents(PreferenceScreen prefs) {
|
|
for (int i = 0; i < prefs.getPreferenceCount(); i++) {
|
|
Intent intent = prefs.getPreference(i).getIntent();
|
|
if (intent != null) {
|
|
intent.putExtra(ACCOUNT_KEY, mAccount);
|
|
// This is somewhat of a hack. Since the preference screen we're accessing comes
|
|
// from another package, we need to modify the intent to launch it with
|
|
// FLAG_ACTIVITY_NEW_TASK.
|
|
// TODO: Do something smarter if we ever have PreferenceScreens of our own.
|
|
intent.setFlags(intent.getFlags() | Intent.FLAG_ACTIVITY_NEW_TASK);
|
|
}
|
|
}
|
|
}
|
|
}
|