diff options
author | Amith Yamasani <yamasani@google.com> | 2010-12-01 09:04:36 -0800 |
---|---|---|
committer | Amith Yamasani <yamasani@google.com> | 2010-12-01 15:14:09 -0800 |
commit | 43c697854c7e373fbc1dae8b7a5259a32de346b4 (patch) | |
tree | 5d75968ceed0c4454af580f56311bd0ccf52ceac /src/com | |
parent | f3c32f49cdafef93ef783947d5ed4aaa287cba61 (diff) | |
download | packages_apps_Settings-43c697854c7e373fbc1dae8b7a5259a32de346b4.tar.gz packages_apps_Settings-43c697854c7e373fbc1dae8b7a5259a32de346b4.tar.bz2 packages_apps_Settings-43c697854c7e373fbc1dae8b7a5259a32de346b4.zip |
Move Account & sync settings into Settings app.
Fragmentized some of the activities and moved buttons into the menu area.
Bug: 3148844
Diffstat (limited to 'src/com')
-rw-r--r-- | src/com/android/settings/AccountPreference.java | 4 | ||||
-rw-r--r-- | src/com/android/settings/DialogCreatable.java | 29 | ||||
-rw-r--r-- | src/com/android/settings/Settings.java | 2 | ||||
-rw-r--r-- | src/com/android/settings/SettingsPreferenceFragment.java | 13 | ||||
-rw-r--r-- | src/com/android/settings/accounts/AccountPreferenceBase.java | 198 | ||||
-rw-r--r-- | src/com/android/settings/accounts/AccountSyncSettings.java | 505 | ||||
-rw-r--r-- | src/com/android/settings/accounts/AccountSyncSettingsInAddAccount.java | 42 | ||||
-rw-r--r-- | src/com/android/settings/accounts/AddAccountSettings.java | 114 | ||||
-rw-r--r-- | src/com/android/settings/accounts/ChooseAccountActivity.java | 233 | ||||
-rw-r--r-- | src/com/android/settings/accounts/ManageAccountsSettings.java (renamed from src/com/android/settings/ManageAccountsSettings.java) | 106 | ||||
-rw-r--r-- | src/com/android/settings/accounts/ProviderPreference.java | 46 | ||||
-rw-r--r-- | src/com/android/settings/accounts/SyncActivityTooManyDeletes.java | 134 | ||||
-rw-r--r-- | src/com/android/settings/accounts/SyncStateCheckBoxPreference.java | 165 |
13 files changed, 1498 insertions, 93 deletions
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/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 @@ -34,15 +34,6 @@ 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. */ public class SettingsPreferenceFragment extends PreferenceFragment @@ -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<String, AuthenticatorDescription> mTypeToAuthDescription + = new HashMap<String, AuthenticatorDescription>(); + protected AuthenticatorDescription[] mAuthDescs; + private final Handler mHandler = new Handler(); + private Object mStatusChangeListenerHandle; + private HashMap<String, ArrayList<String>> 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<String> 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<String> authorities = mAccountTypeToAuthorities.get(sa.accountType); + if (authorities == null) { + authorities = new ArrayList<String>(); + 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<SyncStateCheckBoxPreference> mCheckBoxes = + new ArrayList<SyncStateCheckBoxPreference>(); + private ArrayList<String> 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); + 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<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() { + // 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.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<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); + } + } + } + } + } + + /** + * 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<Bundle> mCallback = new AccountManagerCallback<Bundle>() { + public void run(AccountManagerFuture<Bundle> 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<ProviderEntry> mProviderList = new ArrayList<ProviderEntry>(); + public HashSet<String> mAccountTypesFilter; + private AuthenticatorDescription[] mAuthDescs; + private HashMap<String, ArrayList<String>> mAccountTypeToAuthorities = null; + private Map<String, AuthenticatorDescription> mTypeToAuthDescription + = new HashMap<String, AuthenticatorDescription>(); + + 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<String>(); + 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<String> 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<String> 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<String> authorities = mAccountTypeToAuthorities.get(sa.accountType); + if (authorities == null) { + authorities = new ArrayList<String>(); + 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/ManageAccountsSettings.java b/src/com/android/settings/accounts/ManageAccountsSettings.java index 93053f166..8f61516c2 100644 --- a/src/com/android/settings/ManageAccountsSettings.java +++ b/src/com/android/settings/accounts/ManageAccountsSettings.java @@ -14,9 +14,12 @@ * limitations under the License. */ -package com.android.settings; +package com.android.settings.accounts; -import com.android.settings.SettingsPreferenceFragment.SettingsDialogFragment; +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; @@ -39,8 +42,8 @@ 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.PreferenceFragment; import android.preference.PreferenceScreen; import android.util.Log; import android.view.LayoutInflater; @@ -57,9 +60,9 @@ import java.util.HashMap; import java.util.HashSet; import java.util.Map; -public class ManageAccountsSettings extends PreferenceFragment - implements OnAccountsUpdateListener, - DialogCreatable { +public class ManageAccountsSettings extends AccountPreferenceBase + implements OnAccountsUpdateListener, DialogCreatable { + private static final String TAG = ManageAccountsSettings.class.getSimpleName(); private static final String AUTHORITIES_FILTER_KEY = "authorities"; @@ -72,6 +75,8 @@ public class ManageAccountsSettings extends PreferenceFragment 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; @@ -90,6 +95,8 @@ public class ManageAccountsSettings extends PreferenceFragment public void onCreate(Bundle icicle) { super.onCreate(icicle); + addPreferencesFromResource(R.xml.manage_accounts_settings); + AccountManager.get(getActivity()).addOnAccountsUpdatedListener(this, null, true); setHasOptionsMenu(true); } @@ -101,17 +108,10 @@ public class ManageAccountsSettings extends PreferenceFragment } @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); @@ -126,8 +126,7 @@ public class ManageAccountsSettings extends PreferenceFragment mManageAccountsCategory = (PreferenceCategory)findPreference(MANAGE_ACCOUNTS_CATEGORY_KEY); mAuthorities = activity.getIntent().getStringArrayExtra(AUTHORITIES_FILTER_KEY); - AccountManager.get(activity).addOnAccountsUpdatedListener(this, null, true); - updateAuthDescriptions(activity); + updateAuthDescriptions(); } @Override @@ -156,12 +155,23 @@ public class ManageAccountsSettings extends PreferenceFragment } 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) { @@ -187,7 +197,7 @@ public class ManageAccountsSettings extends PreferenceFragment return null; } - void showDialog(int dialogId) { + public void showDialog(int dialogId) { if (mDialogFragment != null) { Log.e(TAG, "Old dialog fragment not null!"); } @@ -216,7 +226,7 @@ public class ManageAccountsSettings extends PreferenceFragment connManager.setBackgroundDataSetting(enabled); } - private void onSyncStateUpdated() { + protected void onSyncStateUpdated() { // Set background connection state final ConnectivityManager connManager = (ConnectivityManager) getActivity().getSystemService(Context.CONNECTIVITY_SERVICE); @@ -320,7 +330,7 @@ public class ManageAccountsSettings extends PreferenceFragment onSyncStateUpdated(); } - private void onAuthDescriptionsUpdated() { + 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); @@ -334,64 +344,4 @@ public class ManageAccountsSettings extends PreferenceFragment 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<String> 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<String> authorities = mAccountTypeToAuthorities.get(sa.accountType); - if (authorities == null) { - authorities = new ArrayList<String>(); - 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/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<CharSequence>(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; + } +} |