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