From 43c697854c7e373fbc1dae8b7a5259a32de346b4 Mon Sep 17 00:00:00 2001 From: Amith Yamasani Date: Wed, 1 Dec 2010 09:04:36 -0800 Subject: Move Account & sync settings into Settings app. Fragmentized some of the activities and moved buttons into the menu area. Bug: 3148844 --- src/com/android/settings/AccountPreference.java | 4 - src/com/android/settings/DialogCreatable.java | 29 ++ .../android/settings/ManageAccountsSettings.java | 397 ---------------- src/com/android/settings/Settings.java | 2 + .../settings/SettingsPreferenceFragment.java | 13 +- .../settings/accounts/AccountPreferenceBase.java | 198 ++++++++ .../settings/accounts/AccountSyncSettings.java | 505 +++++++++++++++++++++ .../accounts/AccountSyncSettingsInAddAccount.java | 42 ++ .../settings/accounts/AddAccountSettings.java | 114 +++++ .../settings/accounts/ChooseAccountActivity.java | 233 ++++++++++ .../settings/accounts/ManageAccountsSettings.java | 347 ++++++++++++++ .../settings/accounts/ProviderPreference.java | 46 ++ .../accounts/SyncActivityTooManyDeletes.java | 134 ++++++ .../accounts/SyncStateCheckBoxPreference.java | 165 +++++++ 14 files changed, 1817 insertions(+), 412 deletions(-) create mode 100644 src/com/android/settings/DialogCreatable.java delete mode 100644 src/com/android/settings/ManageAccountsSettings.java create mode 100644 src/com/android/settings/accounts/AccountPreferenceBase.java create mode 100644 src/com/android/settings/accounts/AccountSyncSettings.java create mode 100644 src/com/android/settings/accounts/AccountSyncSettingsInAddAccount.java create mode 100644 src/com/android/settings/accounts/AddAccountSettings.java create mode 100644 src/com/android/settings/accounts/ChooseAccountActivity.java create mode 100644 src/com/android/settings/accounts/ManageAccountsSettings.java create mode 100644 src/com/android/settings/accounts/ProviderPreference.java create mode 100644 src/com/android/settings/accounts/SyncActivityTooManyDeletes.java create mode 100644 src/com/android/settings/accounts/SyncStateCheckBoxPreference.java (limited to 'src/com/android/settings') diff --git a/src/com/android/settings/AccountPreference.java b/src/com/android/settings/AccountPreference.java index dc5633457..437839996 100644 --- a/src/com/android/settings/AccountPreference.java +++ b/src/com/android/settings/AccountPreference.java @@ -52,10 +52,6 @@ public class AccountPreference extends Preference { setWidgetLayoutResource(R.layout.account_preference); setTitle(mAccount.name); setSummary(""); - // Add account info to the intent for AccountSyncSettings - Intent intent = new Intent("android.settings.ACCOUNT_SYNC_SETTINGS"); - intent.putExtra("account", mAccount); - setIntent(intent); setPersistent(false); setSyncStatus(SYNC_DISABLED); } diff --git a/src/com/android/settings/DialogCreatable.java b/src/com/android/settings/DialogCreatable.java new file mode 100644 index 000000000..1d10be7f8 --- /dev/null +++ b/src/com/android/settings/DialogCreatable.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2010 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; + +import android.app.Dialog; + +/** + * Letting the class, assumed to be Fragment, create a Dialog on it. Should be useful + * you want to utilize some capability in {@link SettingsPreferenceFragment} but don't want + * the class inherit the class itself (See {@link ProxySelector} for example). + */ +public interface DialogCreatable { + + public Dialog onCreateDialog(int dialogId); +} diff --git a/src/com/android/settings/ManageAccountsSettings.java b/src/com/android/settings/ManageAccountsSettings.java deleted file mode 100644 index 93053f166..000000000 --- a/src/com/android/settings/ManageAccountsSettings.java +++ /dev/null @@ -1,397 +0,0 @@ -/* - * 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; - -import com.android.settings.SettingsPreferenceFragment.SettingsDialogFragment; -import com.google.android.collect.Maps; - -import android.accounts.Account; -import android.accounts.AccountManager; -import android.accounts.AuthenticatorDescription; -import android.accounts.OnAccountsUpdateListener; -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.PackageManager; -import android.graphics.drawable.Drawable; -import android.net.ConnectivityManager; -import android.os.Bundle; -import android.preference.CheckBoxPreference; -import android.preference.Preference; -import android.preference.PreferenceCategory; -import android.preference.PreferenceFragment; -import android.preference.PreferenceScreen; -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.Button; -import android.widget.TextView; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; - -public class ManageAccountsSettings extends PreferenceFragment - implements OnAccountsUpdateListener, - DialogCreatable { - private static final String TAG = ManageAccountsSettings.class.getSimpleName(); - - private static final String AUTHORITIES_FILTER_KEY = "authorities"; - private static final boolean LDEBUG = Log.isLoggable(TAG, Log.DEBUG); - - private static final String AUTO_SYNC_CHECKBOX_KEY = "syncAutomaticallyCheckBox"; - private static final String MANAGE_ACCOUNTS_CATEGORY_KEY = "manageAccountsCategory"; - private static final String BACKGROUND_DATA_CHECKBOX_KEY = "backgroundDataCheckBox"; - private static final int DIALOG_DISABLE_BACKGROUND_DATA = 1; - - private static final int MENU_ADD_ACCOUNT = Menu.FIRST; - - private CheckBoxPreference mBackgroundDataCheckBox; - private PreferenceCategory mManageAccountsCategory; - private String[] mAuthorities; - private TextView mErrorInfoView; - private Button mAddAccountButton; - private CheckBoxPreference mAutoSyncCheckbox; - - private SettingsDialogFragment mDialogFragment; - - private AuthenticatorDescription[] mAuthDescs; - private Map mTypeToAuthDescription - = new HashMap(); - private HashMap> mAccountTypeToAuthorities = null; - - @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.manage_accounts_screen, container, false); - return view; - } - - @Override - public void onResume() { - super.onResume(); - onSyncStateUpdated(); - } - - @Override - public void onActivityCreated(Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - - final Activity activity = getActivity(); - addPreferencesFromResource(R.xml.manage_accounts_settings); - final View view = getView(); - - mErrorInfoView = (TextView)view.findViewById(R.id.sync_settings_error_info); - mErrorInfoView.setVisibility(View.GONE); - mErrorInfoView.setCompoundDrawablesWithIntrinsicBounds( - activity.getResources().getDrawable(R.drawable.ic_list_syncerror), - null, null, null); - - mBackgroundDataCheckBox = (CheckBoxPreference) findPreference(BACKGROUND_DATA_CHECKBOX_KEY); - mAutoSyncCheckbox = (CheckBoxPreference) findPreference(AUTO_SYNC_CHECKBOX_KEY); - - mManageAccountsCategory = (PreferenceCategory)findPreference(MANAGE_ACCOUNTS_CATEGORY_KEY); - mAuthorities = activity.getIntent().getStringArrayExtra(AUTHORITIES_FILTER_KEY); - - AccountManager.get(activity).addOnAccountsUpdatedListener(this, null, true); - updateAuthDescriptions(activity); - } - - @Override - public void onDestroy() { - AccountManager.get(getActivity()).removeOnAccountsUpdatedListener(this); - super.onDestroy(); - } - - @Override - public boolean onPreferenceTreeClick(PreferenceScreen preferences, Preference preference) { - if (preference == mBackgroundDataCheckBox) { - final ConnectivityManager connManager = (ConnectivityManager) - getActivity().getSystemService(Context.CONNECTIVITY_SERVICE); - final boolean oldBackgroundDataSetting = connManager.getBackgroundDataSetting(); - final boolean backgroundDataSetting = mBackgroundDataCheckBox.isChecked(); - if (oldBackgroundDataSetting != backgroundDataSetting) { - if (backgroundDataSetting) { - setBackgroundDataInt(true); - onSyncStateUpdated(); - } else { - // This will get unchecked only if the user hits "Ok" - mBackgroundDataCheckBox.setChecked(true); - showDialog(DIALOG_DISABLE_BACKGROUND_DATA); - } - } - } else if (preference == mAutoSyncCheckbox) { - ContentResolver.setMasterSyncAutomatically(mAutoSyncCheckbox.isChecked()); - onSyncStateUpdated(); - } else { - return false; - } - return true; - } - - @Override - public Dialog onCreateDialog(int id) { - switch (id) { - case DIALOG_DISABLE_BACKGROUND_DATA: - final CheckBoxPreference pref = - (CheckBoxPreference) findPreference(BACKGROUND_DATA_CHECKBOX_KEY); - return new AlertDialog.Builder(getActivity()) - .setTitle(R.string.background_data_dialog_title) - .setIcon(android.R.drawable.ic_dialog_alert) - .setMessage(R.string.background_data_dialog_message) - .setPositiveButton(android.R.string.ok, - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - setBackgroundDataInt(false); - pref.setChecked(false); - onSyncStateUpdated(); - } - }) - .setNegativeButton(android.R.string.cancel, null) - .create(); - } - - return null; - } - - void showDialog(int dialogId) { - if (mDialogFragment != null) { - Log.e(TAG, "Old dialog fragment not null!"); - } - mDialogFragment = new SettingsDialogFragment(this, dialogId); - mDialogFragment.show(getActivity().getFragmentManager(), Integer.toString(dialogId)); - } - - @Override - public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { - MenuItem actionItem = - menu.add(0, MENU_ADD_ACCOUNT, 0, R.string.add_account_label) - .setIcon(R.drawable.ic_menu_add); - actionItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM - | MenuItem.SHOW_AS_ACTION_WITH_TEXT); - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - onAddAccountClicked(); - return true; - } - - private void setBackgroundDataInt(boolean enabled) { - final ConnectivityManager connManager = (ConnectivityManager) - getActivity().getSystemService(Context.CONNECTIVITY_SERVICE); - connManager.setBackgroundDataSetting(enabled); - } - - private void onSyncStateUpdated() { - // Set background connection state - final ConnectivityManager connManager = (ConnectivityManager) - getActivity().getSystemService(Context.CONNECTIVITY_SERVICE); - final boolean backgroundDataSetting = connManager.getBackgroundDataSetting(); - mBackgroundDataCheckBox.setChecked(backgroundDataSetting); - boolean masterSyncAutomatically = ContentResolver.getMasterSyncAutomatically(); - mAutoSyncCheckbox.setChecked(masterSyncAutomatically); - - // iterate over all the preferences, setting the state properly for each - SyncInfo currentSync = ContentResolver.getCurrentSync(); - - boolean anySyncFailed = false; // true if sync on any account failed - - // only track userfacing sync adapters when deciding if account is synced or not - final SyncAdapterType[] syncAdapters = ContentResolver.getSyncAdapterTypes(); - HashSet userFacing = new HashSet(); - for (int k = 0, n = syncAdapters.length; k < n; k++) { - final SyncAdapterType sa = syncAdapters[k]; - if (sa.isUserVisible()) { - userFacing.add(sa.authority); - } - } - for (int i = 0, count = mManageAccountsCategory.getPreferenceCount(); i < count; i++) { - Preference pref = mManageAccountsCategory.getPreference(i); - if (! (pref instanceof AccountPreference)) { - continue; - } - - AccountPreference accountPref = (AccountPreference) pref; - Account account = accountPref.getAccount(); - int syncCount = 0; - boolean syncIsFailing = false; - final ArrayList authorities = accountPref.getAuthorities(); - if (authorities != null) { - for (String authority : authorities) { - SyncStatusInfo status = ContentResolver.getSyncStatus(account, authority); - boolean syncEnabled = ContentResolver.getSyncAutomatically(account, authority) - && masterSyncAutomatically - && backgroundDataSetting - && (ContentResolver.getIsSyncable(account, authority) > 0); - boolean authorityIsPending = ContentResolver.isSyncPending(account, authority); - boolean activelySyncing = currentSync != null - && currentSync.authority.equals(authority) - && new Account(currentSync.account.name, currentSync.account.type) - .equals(account); - boolean lastSyncFailed = status != null - && syncEnabled - && status.lastFailureTime != 0 - && status.getLastFailureMesgAsInt(0) - != ContentResolver.SYNC_ERROR_SYNC_ALREADY_IN_PROGRESS; - if (lastSyncFailed && !activelySyncing && !authorityIsPending) { - syncIsFailing = true; - anySyncFailed = true; - } - syncCount += syncEnabled && userFacing.contains(authority) ? 1 : 0; - } - } else { - if (Log.isLoggable(TAG, Log.VERBOSE)) { - Log.v(TAG, "no syncadapters found for " + account); - } - } - int syncStatus = AccountPreference.SYNC_DISABLED; - if (syncIsFailing) { - syncStatus = AccountPreference.SYNC_ERROR; - } else if (syncCount == 0) { - syncStatus = AccountPreference.SYNC_DISABLED; - } else if (syncCount > 0) { - syncStatus = AccountPreference.SYNC_ENABLED; - } - accountPref.setSyncStatus(syncStatus); - } - - mErrorInfoView.setVisibility(anySyncFailed ? View.VISIBLE : View.GONE); - } - - @Override - public void onAccountsUpdated(Account[] accounts) { - mManageAccountsCategory.removeAll(); - for (int i = 0, n = accounts.length; i < n; i++) { - final Account account = accounts[i]; - final ArrayList auths = getAuthoritiesForAccountType(account.type); - - boolean showAccount = true; - if (mAuthorities != null && auths != null) { - showAccount = false; - for (String requestedAuthority : mAuthorities) { - if (auths.contains(requestedAuthority)) { - showAccount = true; - break; - } - } - } - - if (showAccount) { - final Drawable icon = getDrawableForType(account.type); - final AccountPreference preference = - new AccountPreference(getActivity(), account, icon, auths); - mManageAccountsCategory.addPreference(preference); - } - } - onSyncStateUpdated(); - } - - private void onAuthDescriptionsUpdated() { - // Update account icons for all account preference items - for (int i = 0; i < mManageAccountsCategory.getPreferenceCount(); i++) { - AccountPreference pref = (AccountPreference) mManageAccountsCategory.getPreference(i); - pref.setProviderIcon(getDrawableForType(pref.getAccount().type)); - pref.setSummary(getLabelForType(pref.getAccount().type)); - } - } - - public void onAddAccountClicked() { - Intent intent = new Intent("android.settings.ADD_ACCOUNT_SETTINGS"); - intent.putExtra(AUTHORITIES_FILTER_KEY, mAuthorities); - startActivity(intent); - } - - /* The logic below is copied from AcountPrefernceBase */ - - private Drawable getDrawableForType(final String accountType) { - Drawable icon = null; - if (mTypeToAuthDescription.containsKey(accountType)) { - try { - AuthenticatorDescription desc = mTypeToAuthDescription.get(accountType); - Context authContext = getActivity().createPackageContext(desc.packageName, 0); - icon = authContext.getResources().getDrawable(desc.iconId); - } catch (PackageManager.NameNotFoundException e) { - // TODO: place holder icon for missing account icons? - Log.w(TAG, "No icon for account type " + accountType); - } - } - return icon; - } - - private CharSequence getLabelForType(final String accountType) { - CharSequence label = null; - if (mTypeToAuthDescription.containsKey(accountType)) { - try { - AuthenticatorDescription desc = mTypeToAuthDescription.get(accountType); - Context authContext = getActivity().createPackageContext(desc.packageName, 0); - label = authContext.getResources().getText(desc.labelId); - } catch (PackageManager.NameNotFoundException e) { - Log.w(TAG, "No label for account type " + ", type " + accountType); - } - } - return label; - } - - private ArrayList getAuthoritiesForAccountType(String type) { - if (mAccountTypeToAuthorities == null) { - mAccountTypeToAuthorities = Maps.newHashMap(); - SyncAdapterType[] syncAdapters = ContentResolver.getSyncAdapterTypes(); - for (int i = 0, n = syncAdapters.length; i < n; i++) { - final SyncAdapterType sa = syncAdapters[i]; - ArrayList authorities = mAccountTypeToAuthorities.get(sa.accountType); - if (authorities == null) { - authorities = new ArrayList(); - mAccountTypeToAuthorities.put(sa.accountType, authorities); - } - if (LDEBUG) { - Log.d(TAG, "added authority " + sa.authority + " to accountType " - + sa.accountType); - } - authorities.add(sa.authority); - } - } - return mAccountTypeToAuthorities.get(type); - } - - private void updateAuthDescriptions(Context context) { - mAuthDescs = AccountManager.get(context).getAuthenticatorTypes(); - for (int i = 0; i < mAuthDescs.length; i++) { - mTypeToAuthDescription.put(mAuthDescs[i].type, mAuthDescs[i]); - } - onAuthDescriptionsUpdated(); - } -} diff --git a/src/com/android/settings/Settings.java b/src/com/android/settings/Settings.java index c94a0e168..a47fa5627 100644 --- a/src/com/android/settings/Settings.java +++ b/src/com/android/settings/Settings.java @@ -208,4 +208,6 @@ public class Settings extends PreferenceActivity { public static class VoiceInputOutputSettingsActivity extends Settings { } public static class ManageAccountsSettingsActivity extends Settings { } public static class PowerUsageSummaryActivity extends Settings { } + public static class AccountSyncSettingsActivity extends Settings { } + public static class AccountSyncSettingsInAddAccountActivity extends Settings { } } diff --git a/src/com/android/settings/SettingsPreferenceFragment.java b/src/com/android/settings/SettingsPreferenceFragment.java index 3c771f592..a2f701d05 100644 --- a/src/com/android/settings/SettingsPreferenceFragment.java +++ b/src/com/android/settings/SettingsPreferenceFragment.java @@ -33,15 +33,6 @@ import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; -/** - * Letting the class, assumed to be Fragment, create a Dialog on it. Should be useful - * you want to utilize some capability in {@link SettingsPreferenceFragment} but don't want - * the class inherit the class itself (See {@link ProxySelector} for example). - */ -interface DialogCreatable { - public Dialog onCreateDialog(int dialogId); -} - /** * Base class for Settings fragments, with some helper functions and dialog management. */ @@ -122,12 +113,12 @@ public class SettingsPreferenceFragment extends PreferenceFragment mDialogFragment = null; } - static class SettingsDialogFragment extends DialogFragment { + public static class SettingsDialogFragment extends DialogFragment { private int mDialogId; private DialogCreatable mFragment; - SettingsDialogFragment(DialogCreatable fragment, int dialogId) { + public SettingsDialogFragment(DialogCreatable fragment, int dialogId) { mDialogId = dialogId; mFragment = fragment; } diff --git a/src/com/android/settings/accounts/AccountPreferenceBase.java b/src/com/android/settings/accounts/AccountPreferenceBase.java new file mode 100644 index 000000000..a84bece04 --- /dev/null +++ b/src/com/android/settings/accounts/AccountPreferenceBase.java @@ -0,0 +1,198 @@ +/* + * 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 java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +import com.android.settings.SettingsPreferenceFragment; +import com.google.android.collect.Maps; + +import android.accounts.Account; +import android.accounts.AccountManager; +import android.accounts.AuthenticatorDescription; +import android.accounts.OnAccountsUpdateListener; +import android.content.ContentResolver; +import android.content.Context; +import android.content.SyncAdapterType; +import android.content.SyncStatusObserver; +import android.content.pm.PackageManager; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.os.Handler; +import android.preference.PreferenceActivity; +import android.preference.PreferenceScreen; +import android.util.Log; + +class AccountPreferenceBase extends SettingsPreferenceFragment + implements OnAccountsUpdateListener { + + protected static final String TAG = "AccountSettings"; + public static final String AUTHORITIES_FILTER_KEY = "authorities"; + public static final String ACCOUNT_TYPES_FILTER_KEY = "account_types"; + private Map mTypeToAuthDescription + = new HashMap(); + protected AuthenticatorDescription[] mAuthDescs; + private final Handler mHandler = new Handler(); + private Object mStatusChangeListenerHandle; + private HashMap> mAccountTypeToAuthorities = null; + + /** + * Overload to handle account updates. + */ + public void onAccountsUpdated(Account[] accounts) { + + } + + /** + * Overload to handle authenticator description updates + */ + protected void onAuthDescriptionsUpdated() { + + } + + /** + * Overload to handle sync state updates. + */ + protected void onSyncStateUpdated() { + + } + + @Override + public void onResume() { + super.onResume(); + mStatusChangeListenerHandle = ContentResolver.addStatusChangeListener( + ContentResolver.SYNC_OBSERVER_TYPE_ACTIVE + | ContentResolver.SYNC_OBSERVER_TYPE_STATUS + | ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS, + mSyncStatusObserver); + onSyncStateUpdated(); + } + + @Override + public void onPause() { + super.onPause(); + ContentResolver.removeStatusChangeListener(mStatusChangeListenerHandle); + } + + + private SyncStatusObserver mSyncStatusObserver = new SyncStatusObserver() { + public void onStatusChanged(int which) { + mHandler.post(new Runnable() { + public void run() { + onSyncStateUpdated(); + } + }); + } + }; + + public ArrayList getAuthoritiesForAccountType(String type) { + if (mAccountTypeToAuthorities == null) { + mAccountTypeToAuthorities = Maps.newHashMap(); + SyncAdapterType[] syncAdapters = ContentResolver.getSyncAdapterTypes(); + for (int i = 0, n = syncAdapters.length; i < n; i++) { + final SyncAdapterType sa = syncAdapters[i]; + ArrayList authorities = mAccountTypeToAuthorities.get(sa.accountType); + if (authorities == null) { + authorities = new ArrayList(); + mAccountTypeToAuthorities.put(sa.accountType, authorities); + } + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.d(TAG, "added authority " + sa.authority + " to accountType " + + sa.accountType); + } + authorities.add(sa.authority); + } + } + return mAccountTypeToAuthorities.get(type); + } + + /** + * Gets an icon associated with a particular account type. If none found, return null. + * @param accountType the type of account + * @return a drawable for the icon or null if one cannot be found. + */ + protected Drawable getDrawableForType(final String accountType) { + Drawable icon = null; + if (mTypeToAuthDescription.containsKey(accountType)) { + try { + AuthenticatorDescription desc = mTypeToAuthDescription.get(accountType); + Context authContext = getActivity().createPackageContext(desc.packageName, 0); + icon = authContext.getResources().getDrawable(desc.iconId); + } catch (PackageManager.NameNotFoundException e) { + // TODO: place holder icon for missing account icons? + Log.w(TAG, "No icon for account type " + accountType); + } + } + return icon; + } + + /** + * Gets the label associated with a particular account type. If none found, return null. + * @param accountType the type of account + * @return a CharSequence for the label or null if one cannot be found. + */ + protected CharSequence getLabelForType(final String accountType) { + CharSequence label = null; + if (mTypeToAuthDescription.containsKey(accountType)) { + try { + AuthenticatorDescription desc = mTypeToAuthDescription.get(accountType); + Context authContext = getActivity().createPackageContext(desc.packageName, 0); + label = authContext.getResources().getText(desc.labelId); + } catch (PackageManager.NameNotFoundException e) { + Log.w(TAG, "No label for account type " + ", type " + accountType); + } + } + return label; + } + + /** + * Gets the preferences.xml file associated with a particular account type. + * @param accountType the type of account + * @return a PreferenceScreen inflated from accountPreferenceId. + */ + protected PreferenceScreen addPreferencesForType(final String accountType) { + PreferenceScreen prefs = null; + if (mTypeToAuthDescription.containsKey(accountType)) { + AuthenticatorDescription desc = null; + try { + desc = mTypeToAuthDescription.get(accountType); + if (desc != null && desc.accountPreferencesId != 0) { + Context authContext = getActivity().createPackageContext(desc.packageName, 0); + prefs = getPreferenceManager().inflateFromResource(authContext, + desc.accountPreferencesId, getPreferenceScreen()); + } + } catch (PackageManager.NameNotFoundException e) { + Log.w(TAG, "Couldn't load preferences.xml file from " + desc.packageName); + } + } + return prefs; + } + + /** + * Updates provider icons. Subclasses should call this in onCreate() + * and update any UI that depends on AuthenticatorDescriptions in onAuthDescriptionsUpdated(). + */ + protected void updateAuthDescriptions() { + mAuthDescs = AccountManager.get(getActivity()).getAuthenticatorTypes(); + for (int i = 0; i < mAuthDescs.length; i++) { + mTypeToAuthDescription.put(mAuthDescs[i].type, mAuthDescs[i]); + } + onAuthDescriptionsUpdated(); + } +} diff --git a/src/com/android/settings/accounts/AccountSyncSettings.java b/src/com/android/settings/accounts/AccountSyncSettings.java new file mode 100644 index 000000000..141f2445f --- /dev/null +++ b/src/com/android/settings/accounts/AccountSyncSettings.java @@ -0,0 +1,505 @@ +/* + * 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 com.android.settings.R; +import com.google.android.collect.Lists; +import com.google.android.collect.Maps; + +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.view.View.OnClickListener; +import android.widget.Button; +import android.widget.ImageView; +import android.widget.TextView; + +import java.io.IOException; +import java.util.ArrayList; +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; + protected View mRemoveAccountArea; + 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 Button mRemoveAccountButton; + private ArrayList mCheckBoxes = + new ArrayList(); + private ArrayList 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() { + public void run(AccountManagerFuture 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); + mErrorInfoView.setCompoundDrawablesWithIntrinsicBounds( + getResources().getDrawable(R.drawable.ic_list_syncerror), null, null, null); + + 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); + + mAccount = (Account) getArguments().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); + getPreferenceScreen().addPreference(item); + 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(com.android.internal.R.drawable.ic_menu_delete); + MenuItem syncNow = menu.add(0, MENU_SYNC_NOW_ID, 0, + getString(R.string.sync_menu_sync_now)) + .setIcon(com.android.internal.R.drawable.ic_menu_refresh); + 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_ALWAYS + | MenuItem.SHOW_AS_ACTION_WITH_TEXT); + syncNow.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); + syncCancel.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); + } + + @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 (String authority : mInvisibleAdapters) { + requestOrCancelSync(mAccount, 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 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() { + // iterate over all the preferences, setting the state properly for each + Date date = new Date(); + List 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> accountTypeToAuthorities = + Maps.newHashMap(); + for (int i = 0, n = syncAdapters.length; i < n; i++) { + final SyncAdapterType sa = syncAdapters[i]; + if (sa.isUserVisible()) { + ArrayList authorities = accountTypeToAuthorities.get(sa.accountType); + if (authorities == null) { + authorities = new ArrayList(); + 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.authority); + } + } + + 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 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); + } + } + } + } + } + + /** + * Updates the titlebar with an icon for the provider type. + */ + @Override + protected void onAuthDescriptionsUpdated() { + super.onAuthDescriptionsUpdated(); + getPreferenceScreen().removeAll(); + 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); + } + } + } +} diff --git a/src/com/android/settings/accounts/AccountSyncSettingsInAddAccount.java b/src/com/android/settings/accounts/AccountSyncSettingsInAddAccount.java new file mode 100644 index 000000000..8fa576ab1 --- /dev/null +++ b/src/com/android/settings/accounts/AccountSyncSettingsInAddAccount.java @@ -0,0 +1,42 @@ + +package com.android.settings.accounts; + +import com.android.settings.R; + +import android.app.Activity; +import android.content.ContentResolver; +import android.os.Bundle; +import android.view.Menu; +import android.view.View; +import android.view.View.OnClickListener; + +/** + * This is AccountSyncSettings with 'remove account' button always gone and + * a wizard-like button bar to complete the activity. + */ +public class AccountSyncSettingsInAddAccount extends AccountSyncSettings + implements OnClickListener { + private View mFinishArea; + private View mFinishButton; + + @Override + protected void initializeUi(final View rootView) { + super.initializeUi(rootView); + + mFinishArea = (View) rootView.findViewById(R.id.finish_button_area); + mFinishArea.setVisibility(View.VISIBLE); + mFinishButton = (View) rootView.findViewById(R.id.finish_button); + mFinishButton.setOnClickListener(this); + } + + @Override + public void onPrepareOptionsMenu(Menu menu) { + super.onPrepareOptionsMenu(menu); + // Remove the "remove account" menu item + menu.findItem(MENU_REMOVE_ACCOUNT_ID).setVisible(false); + } + + public void onClick(View v) { + finish(); + } +} diff --git a/src/com/android/settings/accounts/AddAccountSettings.java b/src/com/android/settings/accounts/AddAccountSettings.java new file mode 100644 index 000000000..4c5c0b8c8 --- /dev/null +++ b/src/com/android/settings/accounts/AddAccountSettings.java @@ -0,0 +1,114 @@ +/* + * 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.AccountManager; +import android.accounts.AccountManagerCallback; +import android.accounts.AccountManagerFuture; +import android.accounts.AuthenticatorException; +import android.accounts.OperationCanceledException; +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; +import android.util.Log; + +import java.io.IOException; + +/** + * Entry point Actiivty for account setup. Works as follows + * + * 1) When the other Activities launch this Activity, it launches {@link ChooseAccountActivity} + * without showing anything. + * 2) After receiving an account type from ChooseAccountActivity, this Activity launches the + * account setup specified by AccountManager. + * 3) After the account setup, this Activity finishes without showing anything. + * + * Note: + * Previously this Activity did what {@link ChooseAccountActivity} does right now, but we + * currently delegate the work to the other Activity. When we let this Activity do that work, users + * would see the list of account types when leaving this Activity, since the UI is already ready + * when returning from each account setup, which doesn't look good. + */ +public class AddAccountSettings extends Activity { + private static final String TAG = "AccountSettings"; + + /* package */ static final String EXTRA_SELECTED_ACCOUNT = "selected_account"; + + private static final int CHOOSE_ACCOUNT_REQUEST = 1; + + private AccountManagerCallback mCallback = new AccountManagerCallback() { + public void run(AccountManagerFuture future) { + try { + Bundle bundle = future.getResult(); + bundle.keySet(); + setResult(RESULT_OK); + if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "account added: " + bundle); + } catch (OperationCanceledException e) { + if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "addAccount was canceled"); + } catch (IOException e) { + if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "addAccount failed: " + e); + } catch (AuthenticatorException e) { + if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "addAccount failed: " + e); + } finally { + finish(); + } + } + }; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + final String[] authorities = + getIntent().getStringArrayExtra(AccountPreferenceBase.AUTHORITIES_FILTER_KEY); + final String[] accountTypes = + getIntent().getStringArrayExtra(AccountPreferenceBase.ACCOUNT_TYPES_FILTER_KEY); + final Intent intent = new Intent(this, ChooseAccountActivity.class); + if (authorities != null) { + intent.putExtra(AccountPreferenceBase.AUTHORITIES_FILTER_KEY, authorities); + } + if (accountTypes != null) { + intent.putExtra(AccountPreferenceBase.ACCOUNT_TYPES_FILTER_KEY, accountTypes); + } + startActivityForResult(intent, CHOOSE_ACCOUNT_REQUEST); + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + switch (requestCode) { + case CHOOSE_ACCOUNT_REQUEST: + if (resultCode == RESULT_CANCELED) { + setResult(resultCode); + finish(); + return; + } + // Go to account setup screen. finish() is called inside mCallback. + addAccount(data.getStringExtra(EXTRA_SELECTED_ACCOUNT)); + break; + } + } + + private void addAccount(String accountType) { + AccountManager.get(this).addAccount( + accountType, + null, /* authTokenType */ + null, /* requiredFeatures */ + null, /* addAccountOptions */ + this, + mCallback, + null /* handler */); + } +} diff --git a/src/com/android/settings/accounts/ChooseAccountActivity.java b/src/com/android/settings/accounts/ChooseAccountActivity.java new file mode 100644 index 000000000..9d4965f71 --- /dev/null +++ b/src/com/android/settings/accounts/ChooseAccountActivity.java @@ -0,0 +1,233 @@ +/* + * Copyright (C) 2010 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 com.android.settings.R; +import com.google.android.collect.Maps; + +import android.accounts.AccountManager; +import android.accounts.AuthenticatorDescription; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.SyncAdapterType; +import android.content.pm.PackageManager; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.preference.Preference; +import android.preference.PreferenceActivity; +import android.preference.PreferenceGroup; +import android.preference.PreferenceScreen; +import android.util.Log; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; + +/** + * Activity asking a user to select an account to be set up. + */ +public class ChooseAccountActivity extends PreferenceActivity { + + private static final String TAG = "ChooseAccountActivity"; + private String[] mAuthorities; + private PreferenceGroup mAddAccountGroup; + private final ArrayList mProviderList = new ArrayList(); + public HashSet mAccountTypesFilter; + private AuthenticatorDescription[] mAuthDescs; + private HashMap> mAccountTypeToAuthorities = null; + private Map mTypeToAuthDescription + = new HashMap(); + + private static class ProviderEntry { + private final CharSequence name; + private final String type; + ProviderEntry(CharSequence providerName, String accountType) { + name = providerName; + type = accountType; + } + } + + @Override + protected void onCreate(Bundle icicle) { + super.onCreate(icicle); + + setContentView(R.layout.add_account_screen); + addPreferencesFromResource(R.xml.add_account_settings); + mAuthorities = getIntent().getStringArrayExtra( + AccountPreferenceBase.AUTHORITIES_FILTER_KEY); + String[] accountTypesFilter = getIntent().getStringArrayExtra( + AccountPreferenceBase.ACCOUNT_TYPES_FILTER_KEY); + if (accountTypesFilter != null) { + mAccountTypesFilter = new HashSet(); + for (String accountType : accountTypesFilter) { + mAccountTypesFilter.add(accountType); + } + } + mAddAccountGroup = getPreferenceScreen(); + updateAuthDescriptions(); + } + + /** + * Updates provider icons. Subclasses should call this in onCreate() + * and update any UI that depends on AuthenticatorDescriptions in onAuthDescriptionsUpdated(). + */ + private void updateAuthDescriptions() { + mAuthDescs = AccountManager.get(this).getAuthenticatorTypes(); + for (int i = 0; i < mAuthDescs.length; i++) { + mTypeToAuthDescription.put(mAuthDescs[i].type, mAuthDescs[i]); + } + onAuthDescriptionsUpdated(); + } + + private void onAuthDescriptionsUpdated() { + // Create list of providers to show on preference screen + for (int i = 0; i < mAuthDescs.length; i++) { + String accountType = mAuthDescs[i].type; + CharSequence providerName = getLabelForType(accountType); + + // Skip preferences for authorities not specified. If no authorities specified, + // then include them all. + ArrayList accountAuths = getAuthoritiesForAccountType(accountType); + boolean addAccountPref = true; + if (mAuthorities != null && mAuthorities.length > 0 && accountAuths != null) { + addAccountPref = false; + for (int k = 0; k < mAuthorities.length; k++) { + if (accountAuths.contains(mAuthorities[k])) { + addAccountPref = true; + break; + } + } + } + if (addAccountPref && mAccountTypesFilter != null + && !mAccountTypesFilter.contains(accountType)) { + addAccountPref = false; + } + if (addAccountPref) { + mProviderList.add(new ProviderEntry(providerName, accountType)); + } else { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "Skipped pref " + providerName + ": has no authority we need"); + } + } + } + + if (mProviderList.size() == 1) { + // If there's only one provider that matches, just run it. + finishWithAccountType(mProviderList.get(0).type); + } else if (mProviderList.size() > 0) { + mAddAccountGroup.removeAll(); + for (ProviderEntry pref : mProviderList) { + Drawable drawable = getDrawableForType(pref.type); + ProviderPreference p = + new ProviderPreference(this, pref.type, drawable, pref.name); + mAddAccountGroup.addPreference(p); + } + } else { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + final StringBuilder auths = new StringBuilder(); + for (String a : mAuthorities) { + auths.append(a); + auths.append(' '); + } + Log.v(TAG, "No providers found for authorities: " + auths); + } + setResult(RESULT_CANCELED); + finish(); + } + } + + public ArrayList getAuthoritiesForAccountType(String type) { + if (mAccountTypeToAuthorities == null) { + mAccountTypeToAuthorities = Maps.newHashMap(); + SyncAdapterType[] syncAdapters = ContentResolver.getSyncAdapterTypes(); + for (int i = 0, n = syncAdapters.length; i < n; i++) { + final SyncAdapterType sa = syncAdapters[i]; + ArrayList authorities = mAccountTypeToAuthorities.get(sa.accountType); + if (authorities == null) { + authorities = new ArrayList(); + mAccountTypeToAuthorities.put(sa.accountType, authorities); + } + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.d(TAG, "added authority " + sa.authority + " to accountType " + + sa.accountType); + } + authorities.add(sa.authority); + } + } + return mAccountTypeToAuthorities.get(type); + } + + /** + * Gets an icon associated with a particular account type. If none found, return null. + * @param accountType the type of account + * @return a drawable for the icon or null if one cannot be found. + */ + protected Drawable getDrawableForType(final String accountType) { + Drawable icon = null; + if (mTypeToAuthDescription.containsKey(accountType)) { + try { + AuthenticatorDescription desc = mTypeToAuthDescription.get(accountType); + Context authContext = createPackageContext(desc.packageName, 0); + icon = authContext.getResources().getDrawable(desc.iconId); + } catch (PackageManager.NameNotFoundException e) { + // TODO: place holder icon for missing account icons? + Log.w(TAG, "No icon for account type " + accountType); + } + } + return icon; + } + + /** + * Gets the label associated with a particular account type. If none found, return null. + * @param accountType the type of account + * @return a CharSequence for the label or null if one cannot be found. + */ + protected CharSequence getLabelForType(final String accountType) { + CharSequence label = null; + if (mTypeToAuthDescription.containsKey(accountType)) { + try { + AuthenticatorDescription desc = mTypeToAuthDescription.get(accountType); + Context authContext = createPackageContext(desc.packageName, 0); + label = authContext.getResources().getText(desc.labelId); + } catch (PackageManager.NameNotFoundException e) { + Log.w(TAG, "No label for account type " + ", type " + accountType); + } + } + return label; + } + + @Override + public boolean onPreferenceTreeClick(PreferenceScreen preferences, Preference preference) { + if (preference instanceof ProviderPreference) { + ProviderPreference pref = (ProviderPreference) preference; + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "Attempting to add account of type " + pref.getAccountType()); + } + finishWithAccountType(pref.getAccountType()); + } + return true; + } + + private void finishWithAccountType(String accountType) { + Intent intent = new Intent(); + intent.putExtra(AddAccountSettings.EXTRA_SELECTED_ACCOUNT, accountType); + setResult(RESULT_OK, intent); + finish(); + } +} diff --git a/src/com/android/settings/accounts/ManageAccountsSettings.java b/src/com/android/settings/accounts/ManageAccountsSettings.java new file mode 100644 index 000000000..8f61516c2 --- /dev/null +++ b/src/com/android/settings/accounts/ManageAccountsSettings.java @@ -0,0 +1,347 @@ +/* + * 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 com.android.settings.AccountPreference; +import com.android.settings.DialogCreatable; +import com.android.settings.R; +import com.android.settings.vpn.VpnTypeSelection; +import com.google.android.collect.Maps; + +import android.accounts.Account; +import android.accounts.AccountManager; +import android.accounts.AuthenticatorDescription; +import android.accounts.OnAccountsUpdateListener; +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.PackageManager; +import android.graphics.drawable.Drawable; +import android.net.ConnectivityManager; +import android.os.Bundle; +import android.preference.CheckBoxPreference; +import android.preference.Preference; +import android.preference.PreferenceActivity; +import android.preference.PreferenceCategory; +import android.preference.PreferenceScreen; +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.Button; +import android.widget.TextView; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; + +public class ManageAccountsSettings extends AccountPreferenceBase + implements OnAccountsUpdateListener, DialogCreatable { + + private static final String TAG = ManageAccountsSettings.class.getSimpleName(); + + private static final String AUTHORITIES_FILTER_KEY = "authorities"; + private static final boolean LDEBUG = Log.isLoggable(TAG, Log.DEBUG); + + private static final String AUTO_SYNC_CHECKBOX_KEY = "syncAutomaticallyCheckBox"; + private static final String MANAGE_ACCOUNTS_CATEGORY_KEY = "manageAccountsCategory"; + private static final String BACKGROUND_DATA_CHECKBOX_KEY = "backgroundDataCheckBox"; + private static final int DIALOG_DISABLE_BACKGROUND_DATA = 1; + + private static final int MENU_ADD_ACCOUNT = Menu.FIRST; + + private static final int REQUEST_SHOW_SYNC_SETTINGS = 1; + + private CheckBoxPreference mBackgroundDataCheckBox; + private PreferenceCategory mManageAccountsCategory; + private String[] mAuthorities; + private TextView mErrorInfoView; + private Button mAddAccountButton; + private CheckBoxPreference mAutoSyncCheckbox; + + private SettingsDialogFragment mDialogFragment; + + private AuthenticatorDescription[] mAuthDescs; + private Map mTypeToAuthDescription + = new HashMap(); + private HashMap> mAccountTypeToAuthorities = null; + + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + + addPreferencesFromResource(R.xml.manage_accounts_settings); + AccountManager.get(getActivity()).addOnAccountsUpdatedListener(this, null, true); + setHasOptionsMenu(true); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + final View view = inflater.inflate(R.layout.manage_accounts_screen, container, false); + return view; + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + + final Activity activity = getActivity(); + final View view = getView(); + + mErrorInfoView = (TextView)view.findViewById(R.id.sync_settings_error_info); + mErrorInfoView.setVisibility(View.GONE); + mErrorInfoView.setCompoundDrawablesWithIntrinsicBounds( + activity.getResources().getDrawable(R.drawable.ic_list_syncerror), + null, null, null); + + mBackgroundDataCheckBox = (CheckBoxPreference) findPreference(BACKGROUND_DATA_CHECKBOX_KEY); + mAutoSyncCheckbox = (CheckBoxPreference) findPreference(AUTO_SYNC_CHECKBOX_KEY); + + mManageAccountsCategory = (PreferenceCategory)findPreference(MANAGE_ACCOUNTS_CATEGORY_KEY); + mAuthorities = activity.getIntent().getStringArrayExtra(AUTHORITIES_FILTER_KEY); + + updateAuthDescriptions(); + } + + @Override + public void onDestroy() { + AccountManager.get(getActivity()).removeOnAccountsUpdatedListener(this); + super.onDestroy(); + } + + @Override + public boolean onPreferenceTreeClick(PreferenceScreen preferences, Preference preference) { + if (preference == mBackgroundDataCheckBox) { + final ConnectivityManager connManager = (ConnectivityManager) + getActivity().getSystemService(Context.CONNECTIVITY_SERVICE); + final boolean oldBackgroundDataSetting = connManager.getBackgroundDataSetting(); + final boolean backgroundDataSetting = mBackgroundDataCheckBox.isChecked(); + if (oldBackgroundDataSetting != backgroundDataSetting) { + if (backgroundDataSetting) { + setBackgroundDataInt(true); + onSyncStateUpdated(); + } else { + // This will get unchecked only if the user hits "Ok" + mBackgroundDataCheckBox.setChecked(true); + showDialog(DIALOG_DISABLE_BACKGROUND_DATA); + } + } + } else if (preference == mAutoSyncCheckbox) { + ContentResolver.setMasterSyncAutomatically(mAutoSyncCheckbox.isChecked()); + onSyncStateUpdated(); + } else if (preference instanceof AccountPreference) { + startAccountSettings((AccountPreference) preference); + } else { + return false; + } + return true; + } + + private void startAccountSettings(AccountPreference acctPref) { + Bundle args = new Bundle(); + args.putParcelable(AccountSyncSettings.ACCOUNT_KEY, acctPref.getAccount()); + ((PreferenceActivity) getActivity()).startPreferencePanel( + AccountSyncSettings.class.getCanonicalName(), args, + R.string.account_sync_settings_title, acctPref.getAccount().name, + this, REQUEST_SHOW_SYNC_SETTINGS); + } + + @Override + public Dialog onCreateDialog(int id) { + switch (id) { + case DIALOG_DISABLE_BACKGROUND_DATA: + final CheckBoxPreference pref = + (CheckBoxPreference) findPreference(BACKGROUND_DATA_CHECKBOX_KEY); + return new AlertDialog.Builder(getActivity()) + .setTitle(R.string.background_data_dialog_title) + .setIcon(android.R.drawable.ic_dialog_alert) + .setMessage(R.string.background_data_dialog_message) + .setPositiveButton(android.R.string.ok, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + setBackgroundDataInt(false); + pref.setChecked(false); + onSyncStateUpdated(); + } + }) + .setNegativeButton(android.R.string.cancel, null) + .create(); + } + + return null; + } + + public void showDialog(int dialogId) { + if (mDialogFragment != null) { + Log.e(TAG, "Old dialog fragment not null!"); + } + mDialogFragment = new SettingsDialogFragment(this, dialogId); + mDialogFragment.show(getActivity().getFragmentManager(), Integer.toString(dialogId)); + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + MenuItem actionItem = + menu.add(0, MENU_ADD_ACCOUNT, 0, R.string.add_account_label) + .setIcon(R.drawable.ic_menu_add); + actionItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM + | MenuItem.SHOW_AS_ACTION_WITH_TEXT); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + onAddAccountClicked(); + return true; + } + + private void setBackgroundDataInt(boolean enabled) { + final ConnectivityManager connManager = (ConnectivityManager) + getActivity().getSystemService(Context.CONNECTIVITY_SERVICE); + connManager.setBackgroundDataSetting(enabled); + } + + protected void onSyncStateUpdated() { + // Set background connection state + final ConnectivityManager connManager = (ConnectivityManager) + getActivity().getSystemService(Context.CONNECTIVITY_SERVICE); + final boolean backgroundDataSetting = connManager.getBackgroundDataSetting(); + mBackgroundDataCheckBox.setChecked(backgroundDataSetting); + boolean masterSyncAutomatically = ContentResolver.getMasterSyncAutomatically(); + mAutoSyncCheckbox.setChecked(masterSyncAutomatically); + + // iterate over all the preferences, setting the state properly for each + SyncInfo currentSync = ContentResolver.getCurrentSync(); + + boolean anySyncFailed = false; // true if sync on any account failed + + // only track userfacing sync adapters when deciding if account is synced or not + final SyncAdapterType[] syncAdapters = ContentResolver.getSyncAdapterTypes(); + HashSet userFacing = new HashSet(); + for (int k = 0, n = syncAdapters.length; k < n; k++) { + final SyncAdapterType sa = syncAdapters[k]; + if (sa.isUserVisible()) { + userFacing.add(sa.authority); + } + } + for (int i = 0, count = mManageAccountsCategory.getPreferenceCount(); i < count; i++) { + Preference pref = mManageAccountsCategory.getPreference(i); + if (! (pref instanceof AccountPreference)) { + continue; + } + + AccountPreference accountPref = (AccountPreference) pref; + Account account = accountPref.getAccount(); + int syncCount = 0; + boolean syncIsFailing = false; + final ArrayList authorities = accountPref.getAuthorities(); + if (authorities != null) { + for (String authority : authorities) { + SyncStatusInfo status = ContentResolver.getSyncStatus(account, authority); + boolean syncEnabled = ContentResolver.getSyncAutomatically(account, authority) + && masterSyncAutomatically + && backgroundDataSetting + && (ContentResolver.getIsSyncable(account, authority) > 0); + boolean authorityIsPending = ContentResolver.isSyncPending(account, authority); + boolean activelySyncing = currentSync != null + && currentSync.authority.equals(authority) + && new Account(currentSync.account.name, currentSync.account.type) + .equals(account); + boolean lastSyncFailed = status != null + && syncEnabled + && status.lastFailureTime != 0 + && status.getLastFailureMesgAsInt(0) + != ContentResolver.SYNC_ERROR_SYNC_ALREADY_IN_PROGRESS; + if (lastSyncFailed && !activelySyncing && !authorityIsPending) { + syncIsFailing = true; + anySyncFailed = true; + } + syncCount += syncEnabled && userFacing.contains(authority) ? 1 : 0; + } + } else { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "no syncadapters found for " + account); + } + } + int syncStatus = AccountPreference.SYNC_DISABLED; + if (syncIsFailing) { + syncStatus = AccountPreference.SYNC_ERROR; + } else if (syncCount == 0) { + syncStatus = AccountPreference.SYNC_DISABLED; + } else if (syncCount > 0) { + syncStatus = AccountPreference.SYNC_ENABLED; + } + accountPref.setSyncStatus(syncStatus); + } + + mErrorInfoView.setVisibility(anySyncFailed ? View.VISIBLE : View.GONE); + } + + @Override + public void onAccountsUpdated(Account[] accounts) { + mManageAccountsCategory.removeAll(); + for (int i = 0, n = accounts.length; i < n; i++) { + final Account account = accounts[i]; + final ArrayList auths = getAuthoritiesForAccountType(account.type); + + boolean showAccount = true; + if (mAuthorities != null && auths != null) { + showAccount = false; + for (String requestedAuthority : mAuthorities) { + if (auths.contains(requestedAuthority)) { + showAccount = true; + break; + } + } + } + + if (showAccount) { + final Drawable icon = getDrawableForType(account.type); + final AccountPreference preference = + new AccountPreference(getActivity(), account, icon, auths); + mManageAccountsCategory.addPreference(preference); + } + } + onSyncStateUpdated(); + } + + protected void onAuthDescriptionsUpdated() { + // Update account icons for all account preference items + for (int i = 0; i < mManageAccountsCategory.getPreferenceCount(); i++) { + AccountPreference pref = (AccountPreference) mManageAccountsCategory.getPreference(i); + pref.setProviderIcon(getDrawableForType(pref.getAccount().type)); + pref.setSummary(getLabelForType(pref.getAccount().type)); + } + } + + public void onAddAccountClicked() { + Intent intent = new Intent("android.settings.ADD_ACCOUNT_SETTINGS"); + intent.putExtra(AUTHORITIES_FILTER_KEY, mAuthorities); + startActivity(intent); + } +} diff --git a/src/com/android/settings/accounts/ProviderPreference.java b/src/com/android/settings/accounts/ProviderPreference.java new file mode 100644 index 000000000..399652394 --- /dev/null +++ b/src/com/android/settings/accounts/ProviderPreference.java @@ -0,0 +1,46 @@ +/* + * 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 com.android.settings.R; + +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.preference.Preference; +import android.view.View; +import android.widget.ImageView; + +/** + * ProviderPreference is used to display an image to the left of a provider name. + * The preference ultimately calls AccountManager.addAccount() for the account type. + */ +public class ProviderPreference extends Preference { + private String mAccountType; + + public ProviderPreference( + Context context, String accountType, Drawable icon, CharSequence providerName) { + super(context); + mAccountType = accountType; + setIcon(icon); + setPersistent(false); + setTitle(providerName); + } + + public String getAccountType() { + return mAccountType; + } +} diff --git a/src/com/android/settings/accounts/SyncActivityTooManyDeletes.java b/src/com/android/settings/accounts/SyncActivityTooManyDeletes.java new file mode 100644 index 000000000..b31561cc1 --- /dev/null +++ b/src/com/android/settings/accounts/SyncActivityTooManyDeletes.java @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2007 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 com.android.settings.R; + +import android.accounts.Account; +import android.app.Activity; +import android.content.ContentResolver; +import android.os.Bundle; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.LinearLayout; +import android.widget.ListAdapter; +import android.widget.ListView; +import android.widget.TextView; + +/** + * Presents multiple options for handling the case where a sync was aborted because there + * were too many pending deletes. One option is to force the delete, another is to rollback + * the deletes, the third is to do nothing. + */ +public class SyncActivityTooManyDeletes extends Activity + implements AdapterView.OnItemClickListener { + + private long mNumDeletes; + private Account mAccount; + private String mAuthority; + private String mProvider; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + Bundle extras = getIntent().getExtras(); + if (extras == null) { + finish(); + return; + } + + mNumDeletes = extras.getLong("numDeletes"); + mAccount = (Account) extras.getParcelable("account"); + mAuthority = extras.getString("authority"); + mProvider = extras.getString("provider"); + + // the order of these must match up with the constants for position used in onItemClick + CharSequence[] options = new CharSequence[]{ + getResources().getText(R.string.sync_really_delete), + getResources().getText(R.string.sync_undo_deletes), + getResources().getText(R.string.sync_do_nothing) + }; + + ListAdapter adapter = new ArrayAdapter(this, + android.R.layout.simple_list_item_1, + android.R.id.text1, + options); + + ListView listView = new ListView(this); + listView.setAdapter(adapter); + listView.setItemsCanFocus(true); + listView.setOnItemClickListener(this); + + TextView textView = new TextView(this); + CharSequence tooManyDeletesDescFormat = + getResources().getText(R.string.sync_too_many_deletes_desc); + textView.setText(String.format(tooManyDeletesDescFormat.toString(), + mNumDeletes, mProvider, mAccount.name)); + + final LinearLayout ll = new LinearLayout(this); + ll.setOrientation(LinearLayout.VERTICAL); + final LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT, 0); + ll.addView(textView, lp); + ll.addView(listView, lp); + + // TODO: consider displaying the icon of the account type +// AuthenticatorDescription[] descs = AccountManager.get(this).getAuthenticatorTypes(); +// for (AuthenticatorDescription desc : descs) { +// if (desc.type.equals(mAccount.type)) { +// try { +// final Context authContext = createPackageContext(desc.packageName, 0); +// ImageView imageView = new ImageView(this); +// imageView.setImageDrawable(authContext.getResources().getDrawable(desc.iconId)); +// ll.addView(imageView, lp); +// } catch (PackageManager.NameNotFoundException e) { +// } +// break; +// } +// } + + setContentView(ll); + } + + public void onItemClick(AdapterView parent, View view, int position, long id) { + // the constants for position correspond to the items options array in onCreate() + if (position == 0) startSyncReallyDelete(); + else if (position == 1) startSyncUndoDeletes(); + finish(); + } + + private void startSyncReallyDelete() { + Bundle extras = new Bundle(); + extras.putBoolean(ContentResolver.SYNC_EXTRAS_OVERRIDE_TOO_MANY_DELETIONS, true); + extras.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true); + extras.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true); + extras.putBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, true); + ContentResolver.requestSync(mAccount, mAuthority, extras); + } + + private void startSyncUndoDeletes() { + Bundle extras = new Bundle(); + extras.putBoolean(ContentResolver.SYNC_EXTRAS_DISCARD_LOCAL_DELETIONS, true); + extras.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true); + extras.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true); + extras.putBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, true); + ContentResolver.requestSync(mAccount, mAuthority, extras); + } +} diff --git a/src/com/android/settings/accounts/SyncStateCheckBoxPreference.java b/src/com/android/settings/accounts/SyncStateCheckBoxPreference.java new file mode 100644 index 000000000..b200eb67d --- /dev/null +++ b/src/com/android/settings/accounts/SyncStateCheckBoxPreference.java @@ -0,0 +1,165 @@ +/* + * 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 com.android.settings.R; + +import android.content.Context; +import android.graphics.drawable.AnimationDrawable; +import android.preference.CheckBoxPreference; +import android.util.AttributeSet; +import android.view.View; +import android.widget.ImageView; +import android.widget.TextView; +import android.accounts.Account; + +public class SyncStateCheckBoxPreference extends CheckBoxPreference { + + private boolean mIsActive = false; + private boolean mIsPending = false; + private boolean mFailed = false; + private Account mAccount; + private String mAuthority; + + /** + * A mode for this preference where clicking does a one-time sync instead of + * toggling whether the provider will do autosync. + */ + private boolean mOneTimeSyncMode = false; + + public SyncStateCheckBoxPreference(Context context, AttributeSet attrs) { + super(context, attrs); + setWidgetLayoutResource(R.layout.preference_widget_sync_toggle); + mAccount = null; + mAuthority = null; + } + + public SyncStateCheckBoxPreference(Context context, Account account, String authority) { + super(context, null); + mAccount = account; + mAuthority = authority; + setWidgetLayoutResource(R.layout.preference_widget_sync_toggle); + } + + @Override + public void onBindView(View view) { + super.onBindView(view); + ImageView syncActiveView = (ImageView) view.findViewById(R.id.sync_active); + View syncPendingView = view.findViewById(R.id.sync_pending); + View syncFailedView = view.findViewById(R.id.sync_failed); + + syncActiveView.setVisibility(mIsActive ? View.VISIBLE : View.GONE); + final AnimationDrawable anim = (AnimationDrawable) syncActiveView.getDrawable(); + boolean showError; + boolean showPending; + if (mIsActive) { + syncActiveView.post(new Runnable() { + public void run() { + anim.start(); + } + }); + showPending = false; + showError = false; + } else { + anim.stop(); + if (mIsPending) { + showPending = true; + showError = false; + } else { + showPending = false; + showError = mFailed; + } + } + + syncFailedView.setVisibility(showError ? View.VISIBLE : View.GONE); + syncPendingView.setVisibility((showPending && !mIsActive) ? View.VISIBLE : View.GONE); + + View checkBox = view.findViewById(android.R.id.checkbox); + if (mOneTimeSyncMode) { + checkBox.setVisibility(View.GONE); + + /* + * Override the summary. Fill in the %1$s with the existing summary + * (what ends up happening is the old summary is shown on the next + * line). + */ + TextView summary = (TextView) view.findViewById(android.R.id.summary); + summary.setText(getContext().getString(R.string.sync_one_time_sync, getSummary())); + } else { + checkBox.setVisibility(View.VISIBLE); + } + } + + /** + * Set whether the sync is active. + * @param isActive whether or not the sync is active + */ + public void setActive(boolean isActive) { + mIsActive = isActive; + notifyChanged(); + } + + /** + * Set whether a sync is pending. + * @param isPending whether or not the sync is pending + */ + public void setPending(boolean isPending) { + mIsPending = isPending; + notifyChanged(); + } + + /** + * Set whether the corresponding sync failed. + * @param failed whether or not the sync failed + */ + public void setFailed(boolean failed) { + mFailed = failed; + notifyChanged(); + } + + /** + * Sets whether the preference is in one-time sync mode. + */ + public void setOneTimeSyncMode(boolean oneTimeSyncMode) { + mOneTimeSyncMode = oneTimeSyncMode; + notifyChanged(); + } + + /** + * Gets whether the preference is in one-time sync mode. + */ + public boolean isOneTimeSyncMode() { + return mOneTimeSyncMode; + } + + @Override + protected void onClick() { + // When we're in one-time sync mode, we don't want a click to change the + // checkbox state + if (!mOneTimeSyncMode) { + super.onClick(); + } + } + + public Account getAccount() { + return mAccount; + } + + public String getAuthority() { + return mAuthority; + } +} -- cgit v1.2.3