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 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 88 public int getMetricsCategory() { 89 return MetricsEvent.ACCOUNTS_ACCOUNT_SYNC; 90 } 91 92 @Override 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 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 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 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 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 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 173 public void onResume() { 174 removePreference("dummy"); 175 mAuthenticatorHelper.listenToAccountUpdates(); 176 updateAuthDescriptions(); 177 onAccountsUpdate(Binder.getCallingUserHandle()); 178 super.onResume(); 179 } 180 181 @Override 182 public void onPause() { 183 super.onPause(); 184 mAuthenticatorHelper.stopListeningToAccountUpdates(); 185 } 186 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 final PackageManager packageManager = getPackageManager(); 198 item.setPersistent(false); 199 final ProviderInfo providerInfo = packageManager.resolveContentProviderAsUser( 200 authority, 0, mUserHandle.getIdentifier()); 201 if (providerInfo == null) { 202 return; 203 } 204 final CharSequence providerLabel = providerInfo.loadLabel(packageManager); 205 if (TextUtils.isEmpty(providerLabel)) { 206 Log.e(TAG, "Provider needs a label for authority '" + authority + "'"); 207 return; 208 } 209 item.setTitle(providerLabel); 210 item.setKey(authority); 211 } 212 213 @Override 214 public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { 215 MenuItem syncNow = menu.add(0, MENU_SYNC_NOW_ID, 0, 216 getString(R.string.sync_menu_sync_now)) 217 .setIcon(R.drawable.ic_menu_refresh_holo_dark); 218 MenuItem syncCancel = menu.add(0, MENU_SYNC_CANCEL_ID, 0, 219 getString(R.string.sync_menu_sync_cancel)) 220 .setIcon(com.android.internal.R.drawable.ic_menu_close_clear_cancel); 221 222 syncNow.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER | 223 MenuItem.SHOW_AS_ACTION_WITH_TEXT); 224 syncCancel.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER | 225 MenuItem.SHOW_AS_ACTION_WITH_TEXT); 226 227 super.onCreateOptionsMenu(menu, inflater); 228 } 229 230 @Override 231 public void onPrepareOptionsMenu(Menu menu) { 232 super.onPrepareOptionsMenu(menu); 233 // Note that this also counts accounts that are not currently displayed 234 boolean syncActive = !ContentResolver.getCurrentSyncsAsUser( 235 mUserHandle.getIdentifier()).isEmpty(); 236 menu.findItem(MENU_SYNC_NOW_ID).setVisible(!syncActive); 237 menu.findItem(MENU_SYNC_CANCEL_ID).setVisible(syncActive); 238 } 239 240 @Override 241 public boolean onOptionsItemSelected(MenuItem item) { 242 switch (item.getItemId()) { 243 case MENU_SYNC_NOW_ID: 244 startSyncForEnabledProviders(); 245 return true; 246 case MENU_SYNC_CANCEL_ID: 247 cancelSyncForEnabledProviders(); 248 return true; 249 } 250 return super.onOptionsItemSelected(item); 251 } 252 253 @Override 254 public void onActivityResult(int requestCode, int resultCode, Intent data) { 255 if (resultCode == Activity.RESULT_OK) { 256 final int uid = requestCode; 257 final int count = getPreferenceScreen().getPreferenceCount(); 258 for (int i = 0; i < count; i++) { 259 Preference preference = getPreferenceScreen().getPreference(i); 260 if (preference instanceof SyncStateSwitchPreference) { 261 SyncStateSwitchPreference syncPref = (SyncStateSwitchPreference) preference; 262 if (syncPref.getUid() == uid) { 263 onPreferenceTreeClick(syncPref); 264 return; 265 } 266 } 267 } 268 } 269 } 270 271 @Override 272 public boolean onPreferenceTreeClick(Preference preference) { 273 if (getActivity() == null) { 274 return false; 275 } 276 if (preference instanceof SyncStateSwitchPreference) { 277 SyncStateSwitchPreference syncPref = (SyncStateSwitchPreference) preference; 278 String authority = syncPref.getAuthority(); 279 Account account = syncPref.getAccount(); 280 final int userId = mUserHandle.getIdentifier(); 281 String packageName = syncPref.getPackageName(); 282 283 boolean syncAutomatically = ContentResolver.getSyncAutomaticallyAsUser(account, 284 authority, userId); 285 if (syncPref.isOneTimeSyncMode()) { 286 // If the sync adapter doesn't have access to the account we either 287 // request access by starting an activity if possible or kick off the 288 // sync which will end up posting an access request notification. 289 if (requestAccountAccessIfNeeded(packageName)) { 290 return true; 291 } 292 requestOrCancelSync(account, authority, true); 293 } else { 294 boolean syncOn = syncPref.isChecked(); 295 boolean oldSyncState = syncAutomatically; 296 if (syncOn != oldSyncState) { 297 // Toggling this switch triggers sync but we may need a user approval. 298 // If the sync adapter doesn't have access to the account we either 299 // request access by starting an activity if possible or kick off the 300 // sync which will end up posting an access request notification. 301 if (syncOn && requestAccountAccessIfNeeded(packageName)) { 302 return true; 303 } 304 // if we're enabling sync, this will request a sync as well 305 ContentResolver.setSyncAutomaticallyAsUser(account, authority, syncOn, userId); 306 // if the master sync switch is off, the request above will 307 // get dropped. when the user clicks on this toggle, 308 // we want to force the sync, however. 309 if (!ContentResolver.getMasterSyncAutomaticallyAsUser(userId) || !syncOn) { 310 requestOrCancelSync(account, authority, syncOn); 311 } 312 } 313 } 314 return true; 315 } else { 316 return super.onPreferenceTreeClick(preference); 317 } 318 } 319 320 private boolean requestAccountAccessIfNeeded(String packageName) { 321 if (packageName == null) { 322 return false; 323 } 324 325 final int uid; 326 try { 327 uid = getContext().getPackageManager().getPackageUidAsUser( 328 packageName, mUserHandle.getIdentifier()); 329 } catch (PackageManager.NameNotFoundException e) { 330 Log.e(TAG, "Invalid sync ", e); 331 return false; 332 } 333 334 AccountManager accountManager = getContext().getSystemService(AccountManager.class); 335 if (!accountManager.hasAccountAccess(mAccount, packageName, mUserHandle)) { 336 IntentSender intent = accountManager.createRequestAccountAccessIntentSenderAsUser( 337 mAccount, packageName, mUserHandle); 338 if (intent != null) { 339 try { 340 startIntentSenderForResult(intent, uid, null, 0, 0, 0, null); 341 return true; 342 } catch (IntentSender.SendIntentException e) { 343 Log.e(TAG, "Error requesting account access", e); 344 } 345 } 346 } 347 return false; 348 } 349 350 private void startSyncForEnabledProviders() { 351 requestOrCancelSyncForEnabledProviders(true /* start them */); 352 final Activity activity = getActivity(); 353 if (activity != null) { 354 activity.invalidateOptionsMenu(); 355 } 356 } 357 358 private void cancelSyncForEnabledProviders() { 359 requestOrCancelSyncForEnabledProviders(false /* cancel them */); 360 final Activity activity = getActivity(); 361 if (activity != null) { 362 activity.invalidateOptionsMenu(); 363 } 364 } 365 366 private void requestOrCancelSyncForEnabledProviders(boolean startSync) { 367 // sync everything that the user has enabled 368 int count = getPreferenceScreen().getPreferenceCount(); 369 for (int i = 0; i < count; i++) { 370 Preference pref = getPreferenceScreen().getPreference(i); 371 if (!(pref instanceof SyncStateSwitchPreference)) { 372 continue; 373 } 374 SyncStateSwitchPreference syncPref = (SyncStateSwitchPreference) pref; 375 if (!syncPref.isChecked()) { 376 continue; 377 } 378 requestOrCancelSync(syncPref.getAccount(), syncPref.getAuthority(), startSync); 379 } 380 // plus whatever the system needs to sync, e.g., invisible sync adapters 381 if (mAccount != null) { 382 for (SyncAdapterType syncAdapter : mInvisibleAdapters) { 383 requestOrCancelSync(mAccount, syncAdapter.authority, startSync); 384 } 385 } 386 } 387 388 private void requestOrCancelSync(Account account, String authority, boolean flag) { 389 if (flag) { 390 Bundle extras = new Bundle(); 391 extras.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true); 392 ContentResolver.requestSyncAsUser(account, authority, mUserHandle.getIdentifier(), 393 extras); 394 } else { 395 ContentResolver.cancelSyncAsUser(account, authority, mUserHandle.getIdentifier()); 396 } 397 } 398 399 private boolean isSyncing(List<SyncInfo> currentSyncs, Account account, String authority) { 400 for (SyncInfo syncInfo : currentSyncs) { 401 if (syncInfo.account.equals(account) && syncInfo.authority.equals(authority)) { 402 return true; 403 } 404 } 405 return false; 406 } 407 408 @Override 409 protected void onSyncStateUpdated() { 410 if (!isResumed()) return; 411 setFeedsState(); 412 final Activity activity = getActivity(); 413 if (activity != null) { 414 activity.invalidateOptionsMenu(); 415 } 416 } 417 418 private void setFeedsState() { 419 // iterate over all the preferences, setting the state properly for each 420 Date date = new Date(); 421 final int userId = mUserHandle.getIdentifier(); 422 List<SyncInfo> currentSyncs = ContentResolver.getCurrentSyncsAsUser(userId); 423 boolean syncIsFailing = false; 424 425 // Refresh the sync status switches - some syncs may have become active. 426 updateAccountSwitches(); 427 428 for (int i = 0, count = getPreferenceScreen().getPreferenceCount(); i < count; i++) { 429 Preference pref = getPreferenceScreen().getPreference(i); 430 if (!(pref instanceof SyncStateSwitchPreference)) { 431 continue; 432 } 433 SyncStateSwitchPreference syncPref = (SyncStateSwitchPreference) pref; 434 435 String authority = syncPref.getAuthority(); 436 Account account = syncPref.getAccount(); 437 438 SyncStatusInfo status = ContentResolver.getSyncStatusAsUser(account, authority, userId); 439 boolean syncEnabled = ContentResolver.getSyncAutomaticallyAsUser(account, authority, 440 userId); 441 boolean authorityIsPending = status == null ? false : status.pending; 442 boolean initialSync = status == null ? false : status.initialize; 443 444 boolean activelySyncing = isSyncing(currentSyncs, account, authority); 445 boolean lastSyncFailed = status != null 446 && status.lastFailureTime != 0 447 && status.getLastFailureMesgAsInt(0) 448 != ContentResolver.SYNC_ERROR_SYNC_ALREADY_IN_PROGRESS; 449 if (!syncEnabled) lastSyncFailed = false; 450 if (lastSyncFailed && !activelySyncing && !authorityIsPending) { 451 syncIsFailing = true; 452 } 453 if (Log.isLoggable(TAG, Log.DEBUG)) { 454 Log.d(TAG, "Update sync status: " + account + " " + authority + 455 " active = " + activelySyncing + " pend =" + authorityIsPending); 456 } 457 458 final long successEndTime = (status == null) ? 0 : status.lastSuccessTime; 459 if (!syncEnabled) { 460 syncPref.setSummary(R.string.sync_disabled); 461 } else if (activelySyncing) { 462 syncPref.setSummary(R.string.sync_in_progress); 463 } else if (successEndTime != 0) { 464 date.setTime(successEndTime); 465 final String timeString = formatSyncDate(date); 466 syncPref.setSummary(getResources().getString(R.string.last_synced, timeString)); 467 } else { 468 syncPref.setSummary(""); 469 } 470 int syncState = ContentResolver.getIsSyncableAsUser(account, authority, userId); 471 472 syncPref.setActive(activelySyncing && (syncState >= 0) && 473 !initialSync); 474 syncPref.setPending(authorityIsPending && (syncState >= 0) && 475 !initialSync); 476 477 syncPref.setFailed(lastSyncFailed); 478 final boolean oneTimeSyncMode = !ContentResolver.getMasterSyncAutomaticallyAsUser( 479 userId); 480 syncPref.setOneTimeSyncMode(oneTimeSyncMode); 481 syncPref.setChecked(oneTimeSyncMode || syncEnabled); 482 } 483 mErrorInfoView.setVisibility(syncIsFailing ? View.VISIBLE : View.GONE); 484 } 485 486 @Override 487 public void onAccountsUpdate(final UserHandle userHandle) { 488 super.onAccountsUpdate(userHandle); 489 if (!accountExists(mAccount)) { 490 // The account was deleted 491 finish(); 492 return; 493 } 494 updateAccountSwitches(); 495 onSyncStateUpdated(); 496 } 497 498 private boolean accountExists(Account account) { 499 if (account == null) return false; 500 501 Account[] accounts = AccountManager.get(getActivity()).getAccountsByTypeAsUser( 502 account.type, mUserHandle); 503 final int count = accounts.length; 504 for (int i = 0; i < count; i++) { 505 if (accounts[i].equals(account)) { 506 return true; 507 } 508 } 509 return false; 510 } 511 512 private void updateAccountSwitches() { 513 mInvisibleAdapters.clear(); 514 515 SyncAdapterType[] syncAdapters = ContentResolver.getSyncAdapterTypesAsUser( 516 mUserHandle.getIdentifier()); 517 ArrayList<SyncAdapterType> authorities = new ArrayList<>(); 518 for (int i = 0, n = syncAdapters.length; i < n; i++) { 519 final SyncAdapterType sa = syncAdapters[i]; 520 // Only keep track of sync adapters for this account 521 if (!sa.accountType.equals(mAccount.type)) continue; 522 if (sa.isUserVisible()) { 523 if (Log.isLoggable(TAG, Log.DEBUG)) { 524 Log.d(TAG, "updateAccountSwitches: added authority " + sa.authority 525 + " to accountType " + sa.accountType); 526 } 527 authorities.add(sa); 528 } else { 529 // keep track of invisible sync adapters, so sync now forces 530 // them to sync as well. 531 mInvisibleAdapters.add(sa); 532 } 533 } 534 535 if (Log.isLoggable(TAG, Log.DEBUG)) { 536 Log.d(TAG, "looking for sync adapters that match account " + mAccount); 537 } 538 cacheRemoveAllPrefs(getPreferenceScreen()); 539 for (int j = 0, m = authorities.size(); j < m; j++) { 540 final SyncAdapterType syncAdapter = authorities.get(j); 541 // We could check services here.... 542 int syncState = ContentResolver.getIsSyncableAsUser(mAccount, syncAdapter.authority, 543 mUserHandle.getIdentifier()); 544 if (Log.isLoggable(TAG, Log.DEBUG)) { 545 Log.d(TAG, " found authority " + syncAdapter.authority + " " + syncState); 546 } 547 if (syncState > 0) { 548 final int uid; 549 try { 550 uid = getContext().getPackageManager().getPackageUidAsUser( 551 syncAdapter.getPackageName(), mUserHandle.getIdentifier()); 552 addSyncStateSwitch(mAccount, syncAdapter.authority, 553 syncAdapter.getPackageName(), uid); 554 } catch (PackageManager.NameNotFoundException e) { 555 Log.e(TAG, "No uid for package" + syncAdapter.getPackageName(), e); 556 } 557 } 558 } 559 removeCachedPrefs(getPreferenceScreen()); 560 } 561 562 /** 563 * Updates the titlebar with an icon for the provider type. 564 */ 565 @Override 566 protected void onAuthDescriptionsUpdated() { 567 super.onAuthDescriptionsUpdated(); 568 if (mAccount != null) { 569 mProviderIcon.setImageDrawable(getDrawableForType(mAccount.type)); 570 mProviderId.setText(getLabelForType(mAccount.type)); 571 } 572 } 573 574 @Override 575 public int getHelpResource() { 576 return R.string.help_url_accounts; 577 } 578 } 579