Home | History | Annotate | Download | only in accounts
      1 /*
      2  * Copyright (C) 2008 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.settings.accounts;
     18 
     19 import android.accounts.Account;
     20 import android.accounts.AccountManager;
     21 import android.accounts.AccountManagerCallback;
     22 import android.accounts.AccountManagerFuture;
     23 import android.accounts.AuthenticatorException;
     24 import android.accounts.OperationCanceledException;
     25 import android.app.Activity;
     26 import android.app.AlertDialog;
     27 import android.app.Dialog;
     28 import android.content.ContentResolver;
     29 import android.content.Context;
     30 import android.content.DialogInterface;
     31 import android.content.SyncAdapterType;
     32 import android.content.SyncInfo;
     33 import android.content.SyncStatusInfo;
     34 import android.content.pm.ProviderInfo;
     35 import android.content.pm.UserInfo;
     36 import android.os.Binder;
     37 import android.os.Bundle;
     38 import android.os.UserHandle;
     39 import android.os.UserManager;
     40 import android.support.v7.preference.Preference;
     41 import android.text.TextUtils;
     42 import android.util.Log;
     43 import android.view.LayoutInflater;
     44 import android.view.Menu;
     45 import android.view.MenuInflater;
     46 import android.view.MenuItem;
     47 import android.view.View;
     48 import android.view.ViewGroup;
     49 import android.widget.ImageView;
     50 import android.widget.TextView;
     51 
     52 import com.google.android.collect.Lists;
     53 
     54 import com.android.internal.logging.MetricsProto.MetricsEvent;
     55 import com.android.settings.R;
     56 import com.android.settings.Utils;
     57 import com.android.settingslib.RestrictedLockUtils;
     58 
     59 import java.io.IOException;
     60 import java.util.ArrayList;
     61 import java.util.Collections;
     62 import java.util.Date;
     63 import java.util.List;
     64 
     65 import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
     66 
     67 public class AccountSyncSettings extends AccountPreferenceBase {
     68 
     69     public static final String ACCOUNT_KEY = "account";
     70     private static final int MENU_SYNC_NOW_ID       = Menu.FIRST;
     71     private static final int MENU_SYNC_CANCEL_ID    = Menu.FIRST + 1;
     72     private static final int MENU_REMOVE_ACCOUNT_ID = Menu.FIRST + 2;
     73     private static final int REALLY_REMOVE_DIALOG = 100;
     74     private static final int FAILED_REMOVAL_DIALOG = 101;
     75     private static final int CANT_DO_ONETIME_SYNC_DIALOG = 102;
     76     private TextView mUserId;
     77     private TextView mProviderId;
     78     private ImageView mProviderIcon;
     79     private TextView mErrorInfoView;
     80     private Account mAccount;
     81     private ArrayList<SyncStateSwitchPreference> mSwitches =
     82                 new ArrayList<SyncStateSwitchPreference>();
     83     private ArrayList<SyncAdapterType> mInvisibleAdapters = Lists.newArrayList();
     84 
     85     @Override
     86     public Dialog onCreateDialog(final int id) {
     87         Dialog dialog = null;
     88         if (id == REALLY_REMOVE_DIALOG) {
     89             dialog = new AlertDialog.Builder(getActivity())
     90                 .setTitle(R.string.really_remove_account_title)
     91                 .setMessage(R.string.really_remove_account_message)
     92                 .setNegativeButton(android.R.string.cancel, null)
     93                 .setPositiveButton(R.string.remove_account_label,
     94                         new DialogInterface.OnClickListener() {
     95                     @Override
     96                     public void onClick(DialogInterface dialog, int which) {
     97                         Activity activity = getActivity();
     98                         AccountManager.get(activity)
     99                                 .removeAccountAsUser(mAccount, activity,
    100                                 new AccountManagerCallback<Bundle>() {
    101                             @Override
    102                             public void run(AccountManagerFuture<Bundle> future) {
    103                                 // If already out of this screen, don't proceed.
    104                                 if (!AccountSyncSettings.this.isResumed()) {
    105                                     return;
    106                                 }
    107                                 boolean failed = true;
    108                                 try {
    109                                     if (future.getResult()
    110                                             .getBoolean(AccountManager.KEY_BOOLEAN_RESULT)) {
    111                                         failed = false;
    112                                     }
    113                                 } catch (OperationCanceledException e) {
    114                                     // handled below
    115                                 } catch (IOException e) {
    116                                     // handled below
    117                                 } catch (AuthenticatorException e) {
    118                                     // handled below
    119                                 }
    120                                 if (failed && getActivity() != null &&
    121                                         !getActivity().isFinishing()) {
    122                                     showDialog(FAILED_REMOVAL_DIALOG);
    123                                 } else {
    124                                     finish();
    125                                 }
    126                             }
    127                         }, null, mUserHandle);
    128                     }
    129                 })
    130                 .create();
    131         } else if (id == FAILED_REMOVAL_DIALOG) {
    132             dialog = new AlertDialog.Builder(getActivity())
    133                 .setTitle(R.string.really_remove_account_title)
    134                 .setPositiveButton(android.R.string.ok, null)
    135                 .setMessage(R.string.remove_account_failed)
    136                 .create();
    137         } else if (id == CANT_DO_ONETIME_SYNC_DIALOG) {
    138             dialog = new AlertDialog.Builder(getActivity())
    139                 .setTitle(R.string.cant_sync_dialog_title)
    140                 .setMessage(R.string.cant_sync_dialog_message)
    141                 .setPositiveButton(android.R.string.ok, null)
    142                 .create();
    143         }
    144         return dialog;
    145     }
    146 
    147     @Override
    148     protected int getMetricsCategory() {
    149         return MetricsEvent.ACCOUNTS_ACCOUNT_SYNC;
    150     }
    151 
    152     @Override
    153     public void onCreate(Bundle icicle) {
    154         super.onCreate(icicle);
    155         setPreferenceScreen(null);
    156         addPreferencesFromResource(R.xml.account_sync_settings);
    157         getPreferenceScreen().setOrderingAsAdded(false);
    158         setAccessibilityTitle();
    159 
    160         setHasOptionsMenu(true);
    161     }
    162 
    163     @Override
    164     public View onCreateView(LayoutInflater inflater, ViewGroup container,
    165             Bundle savedInstanceState) {
    166         final View view = inflater.inflate(R.layout.account_sync_screen, container, false);
    167 
    168         final ViewGroup prefs_container = (ViewGroup) view.findViewById(R.id.prefs_container);
    169         Utils.prepareCustomPreferencesList(container, view, prefs_container, false);
    170         View prefs = super.onCreateView(inflater, prefs_container, savedInstanceState);
    171         prefs_container.addView(prefs);
    172 
    173         initializeUi(view);
    174 
    175         return view;
    176     }
    177 
    178     protected void initializeUi(final View rootView) {
    179         mErrorInfoView = (TextView) rootView.findViewById(R.id.sync_settings_error_info);
    180         mErrorInfoView.setVisibility(View.GONE);
    181 
    182         mUserId = (TextView) rootView.findViewById(R.id.user_id);
    183         mProviderId = (TextView) rootView.findViewById(R.id.provider_id);
    184         mProviderIcon = (ImageView) rootView.findViewById(R.id.provider_icon);
    185     }
    186 
    187     @Override
    188     public void onActivityCreated(Bundle savedInstanceState) {
    189         super.onActivityCreated(savedInstanceState);
    190 
    191         Bundle arguments = getArguments();
    192         if (arguments == null) {
    193             Log.e(TAG, "No arguments provided when starting intent. ACCOUNT_KEY needed.");
    194             finish();
    195             return;
    196         }
    197         mAccount = (Account) arguments.getParcelable(ACCOUNT_KEY);
    198         if (!accountExists(mAccount)) {
    199             Log.e(TAG, "Account provided does not exist: " + mAccount);
    200             finish();
    201             return;
    202         }
    203         if (Log.isLoggable(TAG, Log.VERBOSE)) {
    204           Log.v(TAG, "Got account: " + mAccount);
    205         }
    206         mUserId.setText(mAccount.name);
    207         mProviderId.setText(mAccount.type);
    208     }
    209 
    210     private void setAccessibilityTitle() {
    211         final UserManager um = (UserManager) getSystemService(Context.USER_SERVICE);
    212         UserInfo user = um.getUserInfo(mUserHandle.getIdentifier());
    213         boolean isWorkProfile = user != null ? user.isManagedProfile() : false;
    214         CharSequence currentTitle = getActivity().getTitle();
    215         String accessibilityTitle =
    216                 getString(isWorkProfile
    217                         ? R.string.accessibility_work_account_title
    218                         : R.string.accessibility_personal_account_title, currentTitle);
    219         getActivity().setTitle(Utils.createAccessibleSequence(currentTitle, accessibilityTitle));
    220     }
    221 
    222     @Override
    223     public void onResume() {
    224         removePreference("dummy");
    225         mAuthenticatorHelper.listenToAccountUpdates();
    226         updateAuthDescriptions();
    227         onAccountsUpdate(Binder.getCallingUserHandle());
    228         super.onResume();
    229     }
    230 
    231     @Override
    232     public void onPause() {
    233         super.onPause();
    234         mAuthenticatorHelper.stopListeningToAccountUpdates();
    235     }
    236 
    237     private void addSyncStateSwitch(Account account, String authority) {
    238         SyncStateSwitchPreference item = (SyncStateSwitchPreference) getCachedPreference(authority);
    239         if (item == null) {
    240             item = new SyncStateSwitchPreference(getPrefContext(), account, authority);
    241             getPreferenceScreen().addPreference(item);
    242         } else {
    243             item.setup(account, authority);
    244         }
    245         item.setPersistent(false);
    246         final ProviderInfo providerInfo = getPackageManager().resolveContentProviderAsUser(
    247                 authority, 0, mUserHandle.getIdentifier());
    248         if (providerInfo == null) {
    249             return;
    250         }
    251         CharSequence providerLabel = providerInfo.loadLabel(getPackageManager());
    252         if (TextUtils.isEmpty(providerLabel)) {
    253             Log.e(TAG, "Provider needs a label for authority '" + authority + "'");
    254             return;
    255         }
    256         String title = getString(R.string.sync_item_title, providerLabel);
    257         item.setTitle(title);
    258         item.setKey(authority);
    259     }
    260 
    261     @Override
    262     public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
    263 
    264         MenuItem syncNow = menu.add(0, MENU_SYNC_NOW_ID, 0,
    265                 getString(R.string.sync_menu_sync_now))
    266                 .setIcon(R.drawable.ic_menu_refresh_holo_dark);
    267         MenuItem syncCancel = menu.add(0, MENU_SYNC_CANCEL_ID, 0,
    268                 getString(R.string.sync_menu_sync_cancel))
    269                 .setIcon(com.android.internal.R.drawable.ic_menu_close_clear_cancel);
    270         if (!RestrictedLockUtils.hasBaseUserRestriction(getPrefContext(),
    271                 UserManager.DISALLOW_MODIFY_ACCOUNTS, mUserHandle.getIdentifier())) {
    272             MenuItem removeAccount = menu.add(0, MENU_REMOVE_ACCOUNT_ID, 0,
    273                     getString(R.string.remove_account_label))
    274                     .setIcon(R.drawable.ic_menu_delete);
    275             removeAccount.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER |
    276                     MenuItem.SHOW_AS_ACTION_WITH_TEXT);
    277             EnforcedAdmin admin = RestrictedLockUtils.checkIfRestrictionEnforced(
    278                     getPrefContext(), UserManager.DISALLOW_MODIFY_ACCOUNTS,
    279                     mUserHandle.getIdentifier());
    280             if (admin == null) {
    281                 admin = RestrictedLockUtils.checkIfAccountManagementDisabled(
    282                         getPrefContext(), mAccount.type, mUserHandle.getIdentifier());
    283             }
    284             RestrictedLockUtils.setMenuItemAsDisabledByAdmin(getPrefContext(),
    285                     removeAccount, admin);
    286         }
    287         syncNow.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER |
    288                 MenuItem.SHOW_AS_ACTION_WITH_TEXT);
    289         syncCancel.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER |
    290                 MenuItem.SHOW_AS_ACTION_WITH_TEXT);
    291 
    292         super.onCreateOptionsMenu(menu, inflater);
    293     }
    294 
    295     @Override
    296     public void onPrepareOptionsMenu(Menu menu) {
    297         super.onPrepareOptionsMenu(menu);
    298         // Note that this also counts accounts that are not currently displayed
    299         boolean syncActive = !ContentResolver.getCurrentSyncsAsUser(
    300                 mUserHandle.getIdentifier()).isEmpty();
    301         menu.findItem(MENU_SYNC_NOW_ID).setVisible(!syncActive);
    302         menu.findItem(MENU_SYNC_CANCEL_ID).setVisible(syncActive);
    303     }
    304 
    305     @Override
    306     public boolean onOptionsItemSelected(MenuItem item) {
    307         switch (item.getItemId()) {
    308             case MENU_SYNC_NOW_ID:
    309                 startSyncForEnabledProviders();
    310                 return true;
    311             case MENU_SYNC_CANCEL_ID:
    312                 cancelSyncForEnabledProviders();
    313                 return true;
    314             case MENU_REMOVE_ACCOUNT_ID:
    315                 showDialog(REALLY_REMOVE_DIALOG);
    316                 return true;
    317         }
    318         return super.onOptionsItemSelected(item);
    319     }
    320 
    321     @Override
    322     public boolean onPreferenceTreeClick(Preference preference) {
    323         if (preference instanceof SyncStateSwitchPreference) {
    324             SyncStateSwitchPreference syncPref = (SyncStateSwitchPreference) preference;
    325             String authority = syncPref.getAuthority();
    326             Account account = syncPref.getAccount();
    327             final int userId = mUserHandle.getIdentifier();
    328             boolean syncAutomatically = ContentResolver.getSyncAutomaticallyAsUser(account,
    329                     authority, userId);
    330             if (syncPref.isOneTimeSyncMode()) {
    331                 requestOrCancelSync(account, authority, true);
    332             } else {
    333                 boolean syncOn = syncPref.isChecked();
    334                 boolean oldSyncState = syncAutomatically;
    335                 if (syncOn != oldSyncState) {
    336                     // if we're enabling sync, this will request a sync as well
    337                     ContentResolver.setSyncAutomaticallyAsUser(account, authority, syncOn, userId);
    338                     // if the master sync switch is off, the request above will
    339                     // get dropped.  when the user clicks on this toggle,
    340                     // we want to force the sync, however.
    341                     if (!ContentResolver.getMasterSyncAutomaticallyAsUser(userId) || !syncOn) {
    342                         requestOrCancelSync(account, authority, syncOn);
    343                     }
    344                 }
    345             }
    346             return true;
    347         } else {
    348             return super.onPreferenceTreeClick(preference);
    349         }
    350     }
    351 
    352     private void startSyncForEnabledProviders() {
    353         requestOrCancelSyncForEnabledProviders(true /* start them */);
    354         final Activity activity = getActivity();
    355         if (activity != null) {
    356             activity.invalidateOptionsMenu();
    357         }
    358     }
    359 
    360     private void cancelSyncForEnabledProviders() {
    361         requestOrCancelSyncForEnabledProviders(false /* cancel them */);
    362         final Activity activity = getActivity();
    363         if (activity != null) {
    364             activity.invalidateOptionsMenu();
    365         }
    366     }
    367 
    368     private void requestOrCancelSyncForEnabledProviders(boolean startSync) {
    369         // sync everything that the user has enabled
    370         int count = getPreferenceScreen().getPreferenceCount();
    371         for (int i = 0; i < count; i++) {
    372             Preference pref = getPreferenceScreen().getPreference(i);
    373             if (! (pref instanceof SyncStateSwitchPreference)) {
    374                 continue;
    375             }
    376             SyncStateSwitchPreference syncPref = (SyncStateSwitchPreference) pref;
    377             if (!syncPref.isChecked()) {
    378                 continue;
    379             }
    380             requestOrCancelSync(syncPref.getAccount(), syncPref.getAuthority(), startSync);
    381         }
    382         // plus whatever the system needs to sync, e.g., invisible sync adapters
    383         if (mAccount != null) {
    384             for (SyncAdapterType syncAdapter : mInvisibleAdapters) {
    385                   requestOrCancelSync(mAccount, syncAdapter.authority, startSync);
    386             }
    387         }
    388     }
    389 
    390     private void requestOrCancelSync(Account account, String authority, boolean flag) {
    391         if (flag) {
    392             Bundle extras = new Bundle();
    393             extras.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);
    394             ContentResolver.requestSyncAsUser(account, authority, mUserHandle.getIdentifier(),
    395                     extras);
    396         } else {
    397             ContentResolver.cancelSyncAsUser(account, authority, mUserHandle.getIdentifier());
    398         }
    399     }
    400 
    401     private boolean isSyncing(List<SyncInfo> currentSyncs, Account account, String authority) {
    402         for (SyncInfo syncInfo : currentSyncs) {
    403             if (syncInfo.account.equals(account) && syncInfo.authority.equals(authority)) {
    404                 return true;
    405             }
    406         }
    407         return false;
    408     }
    409 
    410     @Override
    411     protected void onSyncStateUpdated() {
    412         if (!isResumed()) return;
    413         setFeedsState();
    414         final Activity activity = getActivity();
    415         if (activity != null) {
    416             activity.invalidateOptionsMenu();
    417         }
    418     }
    419 
    420     private void setFeedsState() {
    421         // iterate over all the preferences, setting the state properly for each
    422         Date date = new Date();
    423         final int userId = mUserHandle.getIdentifier();
    424         List<SyncInfo> currentSyncs = ContentResolver.getCurrentSyncsAsUser(userId);
    425         boolean syncIsFailing = false;
    426 
    427         // Refresh the sync status switches - some syncs may have become active.
    428         updateAccountSwitches();
    429 
    430         for (int i = 0, count = getPreferenceScreen().getPreferenceCount(); i < count; i++) {
    431             Preference pref = getPreferenceScreen().getPreference(i);
    432             if (! (pref instanceof SyncStateSwitchPreference)) {
    433                 continue;
    434             }
    435             SyncStateSwitchPreference syncPref = (SyncStateSwitchPreference) pref;
    436 
    437             String authority = syncPref.getAuthority();
    438             Account account = syncPref.getAccount();
    439 
    440             SyncStatusInfo status = ContentResolver.getSyncStatusAsUser(account, authority, userId);
    441             boolean syncEnabled = ContentResolver.getSyncAutomaticallyAsUser(account, authority,
    442                     userId);
    443             boolean authorityIsPending = status == null ? false : status.pending;
    444             boolean initialSync = status == null ? false : status.initialize;
    445 
    446             boolean activelySyncing = isSyncing(currentSyncs, account, authority);
    447             boolean lastSyncFailed = status != null
    448                     && status.lastFailureTime != 0
    449                     && status.getLastFailureMesgAsInt(0)
    450                        != ContentResolver.SYNC_ERROR_SYNC_ALREADY_IN_PROGRESS;
    451             if (!syncEnabled) lastSyncFailed = false;
    452             if (lastSyncFailed && !activelySyncing && !authorityIsPending) {
    453                 syncIsFailing = true;
    454             }
    455             if (Log.isLoggable(TAG, Log.VERBOSE)) {
    456                 Log.d(TAG, "Update sync status: " + account + " " + authority +
    457                         " active = " + activelySyncing + " pend =" +  authorityIsPending);
    458             }
    459 
    460             final long successEndTime = (status == null) ? 0 : status.lastSuccessTime;
    461             if (!syncEnabled) {
    462                 syncPref.setSummary(R.string.sync_disabled);
    463             } else if (activelySyncing) {
    464                 syncPref.setSummary(R.string.sync_in_progress);
    465             } else if (successEndTime != 0) {
    466                 date.setTime(successEndTime);
    467                 final String timeString = formatSyncDate(date);
    468                 syncPref.setSummary(getResources().getString(R.string.last_synced, timeString));
    469             } else {
    470                 syncPref.setSummary("");
    471             }
    472             int syncState = ContentResolver.getIsSyncableAsUser(account, authority, userId);
    473 
    474             syncPref.setActive(activelySyncing && (syncState >= 0) &&
    475                     !initialSync);
    476             syncPref.setPending(authorityIsPending && (syncState >= 0) &&
    477                     !initialSync);
    478 
    479             syncPref.setFailed(lastSyncFailed);
    480             final boolean oneTimeSyncMode = !ContentResolver.getMasterSyncAutomaticallyAsUser(
    481                 userId);
    482             syncPref.setOneTimeSyncMode(oneTimeSyncMode);
    483             syncPref.setChecked(oneTimeSyncMode || syncEnabled);
    484         }
    485         mErrorInfoView.setVisibility(syncIsFailing ? View.VISIBLE : View.GONE);
    486     }
    487 
    488     @Override
    489     public void onAccountsUpdate(final UserHandle userHandle) {
    490         super.onAccountsUpdate(userHandle);
    491         if (!accountExists(mAccount)) {
    492             // The account was deleted
    493             finish();
    494             return;
    495         }
    496         updateAccountSwitches();
    497         onSyncStateUpdated();
    498     }
    499 
    500     private boolean accountExists(Account account) {
    501         if (account == null) return false;
    502 
    503         Account[] accounts = AccountManager.get(getActivity()).getAccountsByTypeAsUser(
    504                 account.type, mUserHandle);
    505         final int count = accounts.length;
    506         for (int i = 0; i < count; i++) {
    507             if (accounts[i].equals(account)) {
    508                 return true;
    509             }
    510         }
    511         return false;
    512     }
    513 
    514     private void updateAccountSwitches() {
    515         mInvisibleAdapters.clear();
    516 
    517         SyncAdapterType[] syncAdapters = ContentResolver.getSyncAdapterTypesAsUser(
    518                 mUserHandle.getIdentifier());
    519         ArrayList<String> authorities = new ArrayList<String>();
    520         for (int i = 0, n = syncAdapters.length; i < n; i++) {
    521             final SyncAdapterType sa = syncAdapters[i];
    522             // Only keep track of sync adapters for this account
    523             if (!sa.accountType.equals(mAccount.type)) continue;
    524             if (sa.isUserVisible()) {
    525                 if (Log.isLoggable(TAG, Log.VERBOSE)) {
    526                     Log.d(TAG, "updateAccountSwitches: added authority " + sa.authority
    527                             + " to accountType " + sa.accountType);
    528                 }
    529                 authorities.add(sa.authority);
    530             } else {
    531                 // keep track of invisible sync adapters, so sync now forces
    532                 // them to sync as well.
    533                 mInvisibleAdapters.add(sa);
    534             }
    535         }
    536 
    537         if (Log.isLoggable(TAG, Log.VERBOSE)) {
    538             Log.d(TAG, "looking for sync adapters that match account " + mAccount);
    539         }
    540         cacheRemoveAllPrefs(getPreferenceScreen());
    541         for (int j = 0, m = authorities.size(); j < m; j++) {
    542             final String authority = authorities.get(j);
    543             // We could check services here....
    544             int syncState = ContentResolver.getIsSyncableAsUser(mAccount, authority,
    545                     mUserHandle.getIdentifier());
    546             if (Log.isLoggable(TAG, Log.VERBOSE)) {
    547                 Log.d(TAG, "  found authority " + authority + " " + syncState);
    548             }
    549             if (syncState > 0) {
    550                 addSyncStateSwitch(mAccount, authority);
    551             }
    552         }
    553         removeCachedPrefs(getPreferenceScreen());
    554     }
    555 
    556     /**
    557      * Updates the titlebar with an icon for the provider type.
    558      */
    559     @Override
    560     protected void onAuthDescriptionsUpdated() {
    561         super.onAuthDescriptionsUpdated();
    562         if (mAccount != null) {
    563             mProviderIcon.setImageDrawable(getDrawableForType(mAccount.type));
    564             mProviderId.setText(getLabelForType(mAccount.type));
    565         }
    566     }
    567 
    568     @Override
    569     protected int getHelpResource() {
    570         return R.string.help_url_accounts;
    571     }
    572 }
    573