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