1 /* 2 * Copyright (C) 2011 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 package android.accounts; 17 18 import com.google.android.collect.Sets; 19 20 import android.app.Activity; 21 import android.app.ActivityManagerNative; 22 import android.content.Intent; 23 import android.os.Bundle; 24 import android.os.IBinder; 25 import android.os.Parcelable; 26 import android.os.RemoteException; 27 import android.os.UserHandle; 28 import android.os.UserManager; 29 import android.text.TextUtils; 30 import android.util.Log; 31 import android.view.View; 32 import android.view.Window; 33 import android.widget.AdapterView; 34 import android.widget.ArrayAdapter; 35 import android.widget.Button; 36 import android.widget.ListView; 37 import android.widget.TextView; 38 39 import com.android.internal.R; 40 41 import java.io.IOException; 42 import java.util.ArrayList; 43 import java.util.HashSet; 44 import java.util.Set; 45 46 /** 47 * @hide 48 */ 49 public class ChooseTypeAndAccountActivity extends Activity 50 implements AccountManagerCallback<Bundle> { 51 private static final String TAG = "AccountChooser"; 52 53 /** 54 * A Parcelable ArrayList of Account objects that limits the choosable accounts to those 55 * in this list, if this parameter is supplied. 56 */ 57 public static final String EXTRA_ALLOWABLE_ACCOUNTS_ARRAYLIST = "allowableAccounts"; 58 59 /** 60 * A Parcelable ArrayList of String objects that limits the accounts to choose to those 61 * that match the types in this list, if this parameter is supplied. This list is also 62 * used to filter the allowable account types if add account is selected. 63 */ 64 public static final String EXTRA_ALLOWABLE_ACCOUNT_TYPES_STRING_ARRAY = "allowableAccountTypes"; 65 66 /** 67 * This is passed as the addAccountOptions parameter in AccountManager.addAccount() 68 * if it is called. 69 */ 70 public static final String EXTRA_ADD_ACCOUNT_OPTIONS_BUNDLE = "addAccountOptions"; 71 72 /** 73 * This is passed as the requiredFeatures parameter in AccountManager.addAccount() 74 * if it is called. 75 */ 76 public static final String EXTRA_ADD_ACCOUNT_REQUIRED_FEATURES_STRING_ARRAY = 77 "addAccountRequiredFeatures"; 78 79 /** 80 * This is passed as the authTokenType string in AccountManager.addAccount() 81 * if it is called. 82 */ 83 public static final String EXTRA_ADD_ACCOUNT_AUTH_TOKEN_TYPE_STRING = "authTokenType"; 84 85 /** 86 * If set then the specified account is already "selected". 87 */ 88 public static final String EXTRA_SELECTED_ACCOUNT = "selectedAccount"; 89 90 /** 91 * If true then display the account selection list even if there is just 92 * one account to choose from. boolean. 93 */ 94 public static final String EXTRA_ALWAYS_PROMPT_FOR_ACCOUNT = 95 "alwaysPromptForAccount"; 96 97 /** 98 * If set then this string willb e used as the description rather than 99 * the default. 100 */ 101 public static final String EXTRA_DESCRIPTION_TEXT_OVERRIDE = 102 "descriptionTextOverride"; 103 104 public static final int REQUEST_NULL = 0; 105 public static final int REQUEST_CHOOSE_TYPE = 1; 106 public static final int REQUEST_ADD_ACCOUNT = 2; 107 108 private static final String KEY_INSTANCE_STATE_PENDING_REQUEST = "pendingRequest"; 109 private static final String KEY_INSTANCE_STATE_EXISTING_ACCOUNTS = "existingAccounts"; 110 private static final String KEY_INSTANCE_STATE_SELECTED_ACCOUNT_NAME = "selectedAccountName"; 111 private static final String KEY_INSTANCE_STATE_SELECTED_ADD_ACCOUNT = "selectedAddAccount"; 112 private static final String KEY_INSTANCE_STATE_ACCOUNT_LIST = "accountList"; 113 114 private static final int SELECTED_ITEM_NONE = -1; 115 116 private Set<Account> mSetOfAllowableAccounts; 117 private Set<String> mSetOfRelevantAccountTypes; 118 private String mSelectedAccountName = null; 119 private boolean mSelectedAddNewAccount = false; 120 private boolean mAlwaysPromptForAccount = false; 121 private String mDescriptionOverride; 122 123 private ArrayList<Account> mAccounts; 124 private int mPendingRequest = REQUEST_NULL; 125 private Parcelable[] mExistingAccounts = null; 126 private int mSelectedItemIndex; 127 private Button mOkButton; 128 private int mCallingUid; 129 private String mCallingPackage; 130 private boolean mDisallowAddAccounts; 131 private boolean mDontShowPicker; 132 133 @Override 134 public void onCreate(Bundle savedInstanceState) { 135 super.onCreate(savedInstanceState); 136 if (Log.isLoggable(TAG, Log.VERBOSE)) { 137 Log.v(TAG, "ChooseTypeAndAccountActivity.onCreate(savedInstanceState=" 138 + savedInstanceState + ")"); 139 } 140 141 String message = null; 142 143 try { 144 IBinder activityToken = getActivityToken(); 145 mCallingUid = ActivityManagerNative.getDefault().getLaunchedFromUid(activityToken); 146 mCallingPackage = ActivityManagerNative.getDefault().getLaunchedFromPackage( 147 activityToken); 148 if (mCallingUid != 0 && mCallingPackage != null) { 149 Bundle restrictions = UserManager.get(this) 150 .getUserRestrictions(new UserHandle(UserHandle.getUserId(mCallingUid))); 151 mDisallowAddAccounts = 152 restrictions.getBoolean(UserManager.DISALLOW_MODIFY_ACCOUNTS, false); 153 } 154 } catch (RemoteException re) { 155 // Couldn't figure out caller details 156 Log.w(getClass().getSimpleName(), "Unable to get caller identity \n" + re); 157 } 158 159 // save some items we use frequently 160 final Intent intent = getIntent(); 161 162 if (savedInstanceState != null) { 163 mPendingRequest = savedInstanceState.getInt(KEY_INSTANCE_STATE_PENDING_REQUEST); 164 mExistingAccounts = 165 savedInstanceState.getParcelableArray(KEY_INSTANCE_STATE_EXISTING_ACCOUNTS); 166 167 // Makes sure that any user selection is preserved across orientation changes. 168 mSelectedAccountName = savedInstanceState.getString( 169 KEY_INSTANCE_STATE_SELECTED_ACCOUNT_NAME); 170 171 mSelectedAddNewAccount = savedInstanceState.getBoolean( 172 KEY_INSTANCE_STATE_SELECTED_ADD_ACCOUNT, false); 173 mAccounts = savedInstanceState.getParcelableArrayList(KEY_INSTANCE_STATE_ACCOUNT_LIST); 174 } else { 175 mPendingRequest = REQUEST_NULL; 176 mExistingAccounts = null; 177 // If the selected account as specified in the intent matches one in the list we will 178 // show is as pre-selected. 179 Account selectedAccount = (Account) intent.getParcelableExtra(EXTRA_SELECTED_ACCOUNT); 180 if (selectedAccount != null) { 181 mSelectedAccountName = selectedAccount.name; 182 } 183 } 184 185 if (Log.isLoggable(TAG, Log.VERBOSE)) { 186 Log.v(TAG, "selected account name is " + mSelectedAccountName); 187 } 188 189 190 mSetOfAllowableAccounts = getAllowableAccountSet(intent); 191 mSetOfRelevantAccountTypes = getReleventAccountTypes(intent); 192 mAlwaysPromptForAccount = intent.getBooleanExtra(EXTRA_ALWAYS_PROMPT_FOR_ACCOUNT, false); 193 mDescriptionOverride = intent.getStringExtra(EXTRA_DESCRIPTION_TEXT_OVERRIDE); 194 195 // Need to do this once here to request the window feature. Can't do it in onResume 196 mAccounts = getAcceptableAccountChoices(AccountManager.get(this)); 197 if (mAccounts.isEmpty() 198 && mDisallowAddAccounts) { 199 requestWindowFeature(Window.FEATURE_NO_TITLE); 200 setContentView(R.layout.app_not_authorized); 201 mDontShowPicker = true; 202 } 203 } 204 205 @Override 206 protected void onResume() { 207 super.onResume(); 208 209 if (mDontShowPicker) return; 210 211 final AccountManager accountManager = AccountManager.get(this); 212 213 mAccounts = getAcceptableAccountChoices(accountManager); 214 215 // In cases where the activity does not need to show an account picker, cut the chase 216 // and return the result directly. Eg: 217 // Single account -> select it directly 218 // No account -> launch add account activity directly 219 if (mPendingRequest == REQUEST_NULL) { 220 // If there are no relevant accounts and only one relevant account type go directly to 221 // add account. Otherwise let the user choose. 222 if (mAccounts.isEmpty()) { 223 if (mSetOfRelevantAccountTypes.size() == 1) { 224 runAddAccountForAuthenticator(mSetOfRelevantAccountTypes.iterator().next()); 225 } else { 226 startChooseAccountTypeActivity(); 227 } 228 return; 229 } 230 231 // if there is only one allowable account return it 232 if (!mAlwaysPromptForAccount && mAccounts.size() == 1) { 233 Account account = mAccounts.get(0); 234 setResultAndFinish(account.name, account.type); 235 return; 236 } 237 } 238 239 String[] listItems = getListOfDisplayableOptions(mAccounts); 240 mSelectedItemIndex = getItemIndexToSelect( 241 mAccounts, mSelectedAccountName, mSelectedAddNewAccount); 242 243 // Cannot set content view until we know that mPendingRequest is not null, otherwise 244 // would cause screen flicker. 245 setContentView(R.layout.choose_type_and_account); 246 overrideDescriptionIfSupplied(mDescriptionOverride); 247 populateUIAccountList(listItems); 248 249 // Only enable "OK" button if something has been selected. 250 mOkButton = (Button) findViewById(android.R.id.button2); 251 mOkButton.setEnabled(mSelectedItemIndex != SELECTED_ITEM_NONE); 252 } 253 254 @Override 255 protected void onDestroy() { 256 if (Log.isLoggable(TAG, Log.VERBOSE)) { 257 Log.v(TAG, "ChooseTypeAndAccountActivity.onDestroy()"); 258 } 259 super.onDestroy(); 260 } 261 262 @Override 263 protected void onSaveInstanceState(final Bundle outState) { 264 super.onSaveInstanceState(outState); 265 outState.putInt(KEY_INSTANCE_STATE_PENDING_REQUEST, mPendingRequest); 266 if (mPendingRequest == REQUEST_ADD_ACCOUNT) { 267 outState.putParcelableArray(KEY_INSTANCE_STATE_EXISTING_ACCOUNTS, mExistingAccounts); 268 } 269 if (mSelectedItemIndex != SELECTED_ITEM_NONE) { 270 if (mSelectedItemIndex == mAccounts.size()) { 271 outState.putBoolean(KEY_INSTANCE_STATE_SELECTED_ADD_ACCOUNT, true); 272 } else { 273 outState.putBoolean(KEY_INSTANCE_STATE_SELECTED_ADD_ACCOUNT, false); 274 outState.putString(KEY_INSTANCE_STATE_SELECTED_ACCOUNT_NAME, 275 mAccounts.get(mSelectedItemIndex).name); 276 } 277 } 278 outState.putParcelableArrayList(KEY_INSTANCE_STATE_ACCOUNT_LIST, mAccounts); 279 } 280 281 public void onCancelButtonClicked(View view) { 282 onBackPressed(); 283 } 284 285 public void onOkButtonClicked(View view) { 286 if (mSelectedItemIndex == mAccounts.size()) { 287 // Selected "Add New Account" option 288 startChooseAccountTypeActivity(); 289 } else if (mSelectedItemIndex != SELECTED_ITEM_NONE) { 290 onAccountSelected(mAccounts.get(mSelectedItemIndex)); 291 } 292 } 293 294 // Called when the choose account type activity (for adding an account) returns. 295 // If it was a success read the account and set it in the result. In all cases 296 // return the result and finish this activity. 297 @Override 298 protected void onActivityResult(final int requestCode, final int resultCode, 299 final Intent data) { 300 if (Log.isLoggable(TAG, Log.VERBOSE)) { 301 if (data != null && data.getExtras() != null) data.getExtras().keySet(); 302 Bundle extras = data != null ? data.getExtras() : null; 303 Log.v(TAG, "ChooseTypeAndAccountActivity.onActivityResult(reqCode=" + requestCode 304 + ", resCode=" + resultCode + ", extras=" + extras + ")"); 305 } 306 307 // we got our result, so clear the fact that we had a pending request 308 mPendingRequest = REQUEST_NULL; 309 310 if (resultCode == RESULT_CANCELED) { 311 // if canceling out of addAccount and the original state caused us to skip this, 312 // finish this activity 313 if (mAccounts.isEmpty()) { 314 setResult(Activity.RESULT_CANCELED); 315 finish(); 316 } 317 return; 318 } 319 320 if (resultCode == RESULT_OK) { 321 if (requestCode == REQUEST_CHOOSE_TYPE) { 322 if (data != null) { 323 String accountType = data.getStringExtra(AccountManager.KEY_ACCOUNT_TYPE); 324 if (accountType != null) { 325 runAddAccountForAuthenticator(accountType); 326 return; 327 } 328 } 329 Log.d(TAG, "ChooseTypeAndAccountActivity.onActivityResult: unable to find account " 330 + "type, pretending the request was canceled"); 331 } else if (requestCode == REQUEST_ADD_ACCOUNT) { 332 String accountName = null; 333 String accountType = null; 334 335 if (data != null) { 336 accountName = data.getStringExtra(AccountManager.KEY_ACCOUNT_NAME); 337 accountType = data.getStringExtra(AccountManager.KEY_ACCOUNT_TYPE); 338 } 339 340 if (accountName == null || accountType == null) { 341 Account[] currentAccounts = AccountManager.get(this).getAccountsForPackage( 342 mCallingPackage, mCallingUid); 343 Set<Account> preExistingAccounts = new HashSet<Account>(); 344 for (Parcelable accountParcel : mExistingAccounts) { 345 preExistingAccounts.add((Account) accountParcel); 346 } 347 for (Account account : currentAccounts) { 348 if (!preExistingAccounts.contains(account)) { 349 accountName = account.name; 350 accountType = account.type; 351 break; 352 } 353 } 354 } 355 356 if (accountName != null || accountType != null) { 357 setResultAndFinish(accountName, accountType); 358 return; 359 } 360 } 361 Log.d(TAG, "ChooseTypeAndAccountActivity.onActivityResult: unable to find added " 362 + "account, pretending the request was canceled"); 363 } 364 if (Log.isLoggable(TAG, Log.VERBOSE)) { 365 Log.v(TAG, "ChooseTypeAndAccountActivity.onActivityResult: canceled"); 366 } 367 setResult(Activity.RESULT_CANCELED); 368 finish(); 369 } 370 371 protected void runAddAccountForAuthenticator(String type) { 372 if (Log.isLoggable(TAG, Log.VERBOSE)) { 373 Log.v(TAG, "runAddAccountForAuthenticator: " + type); 374 } 375 final Bundle options = getIntent().getBundleExtra( 376 ChooseTypeAndAccountActivity.EXTRA_ADD_ACCOUNT_OPTIONS_BUNDLE); 377 final String[] requiredFeatures = getIntent().getStringArrayExtra( 378 ChooseTypeAndAccountActivity.EXTRA_ADD_ACCOUNT_REQUIRED_FEATURES_STRING_ARRAY); 379 final String authTokenType = getIntent().getStringExtra( 380 ChooseTypeAndAccountActivity.EXTRA_ADD_ACCOUNT_AUTH_TOKEN_TYPE_STRING); 381 AccountManager.get(this).addAccount(type, authTokenType, requiredFeatures, 382 options, null /* activity */, this /* callback */, null /* Handler */); 383 } 384 385 @Override 386 public void run(final AccountManagerFuture<Bundle> accountManagerFuture) { 387 try { 388 final Bundle accountManagerResult = accountManagerFuture.getResult(); 389 final Intent intent = (Intent)accountManagerResult.getParcelable( 390 AccountManager.KEY_INTENT); 391 if (intent != null) { 392 mPendingRequest = REQUEST_ADD_ACCOUNT; 393 mExistingAccounts = AccountManager.get(this).getAccountsForPackage(mCallingPackage, 394 mCallingUid); 395 intent.setFlags(intent.getFlags() & ~Intent.FLAG_ACTIVITY_NEW_TASK); 396 startActivityForResult(intent, REQUEST_ADD_ACCOUNT); 397 return; 398 } 399 } catch (OperationCanceledException e) { 400 setResult(Activity.RESULT_CANCELED); 401 finish(); 402 return; 403 } catch (IOException e) { 404 } catch (AuthenticatorException e) { 405 } 406 Bundle bundle = new Bundle(); 407 bundle.putString(AccountManager.KEY_ERROR_MESSAGE, "error communicating with server"); 408 setResult(Activity.RESULT_OK, new Intent().putExtras(bundle)); 409 finish(); 410 } 411 412 private void onAccountSelected(Account account) { 413 Log.d(TAG, "selected account " + account); 414 setResultAndFinish(account.name, account.type); 415 } 416 417 private void setResultAndFinish(final String accountName, final String accountType) { 418 Bundle bundle = new Bundle(); 419 bundle.putString(AccountManager.KEY_ACCOUNT_NAME, accountName); 420 bundle.putString(AccountManager.KEY_ACCOUNT_TYPE, accountType); 421 setResult(Activity.RESULT_OK, new Intent().putExtras(bundle)); 422 if (Log.isLoggable(TAG, Log.VERBOSE)) { 423 Log.v(TAG, "ChooseTypeAndAccountActivity.setResultAndFinish: " 424 + "selected account " + accountName + ", " + accountType); 425 } 426 finish(); 427 } 428 429 private void startChooseAccountTypeActivity() { 430 if (Log.isLoggable(TAG, Log.VERBOSE)) { 431 Log.v(TAG, "ChooseAccountTypeActivity.startChooseAccountTypeActivity()"); 432 } 433 final Intent intent = new Intent(this, ChooseAccountTypeActivity.class); 434 intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); 435 intent.putExtra(EXTRA_ALLOWABLE_ACCOUNT_TYPES_STRING_ARRAY, 436 getIntent().getStringArrayExtra(EXTRA_ALLOWABLE_ACCOUNT_TYPES_STRING_ARRAY)); 437 intent.putExtra(EXTRA_ADD_ACCOUNT_OPTIONS_BUNDLE, 438 getIntent().getBundleExtra(EXTRA_ADD_ACCOUNT_OPTIONS_BUNDLE)); 439 intent.putExtra(EXTRA_ADD_ACCOUNT_REQUIRED_FEATURES_STRING_ARRAY, 440 getIntent().getStringArrayExtra(EXTRA_ADD_ACCOUNT_REQUIRED_FEATURES_STRING_ARRAY)); 441 intent.putExtra(EXTRA_ADD_ACCOUNT_AUTH_TOKEN_TYPE_STRING, 442 getIntent().getStringExtra(EXTRA_ADD_ACCOUNT_AUTH_TOKEN_TYPE_STRING)); 443 startActivityForResult(intent, REQUEST_CHOOSE_TYPE); 444 mPendingRequest = REQUEST_CHOOSE_TYPE; 445 } 446 447 /** 448 * @return a value between 0 (inclusive) and accounts.size() (inclusive) or SELECTED_ITEM_NONE. 449 * An index value of accounts.size() indicates 'Add account' option. 450 */ 451 private int getItemIndexToSelect(ArrayList<Account> accounts, String selectedAccountName, 452 boolean selectedAddNewAccount) { 453 // If "Add account" option was previously selected by user, preserve it across 454 // orientation changes. 455 if (selectedAddNewAccount) { 456 return accounts.size(); 457 } 458 // search for the selected account name if present 459 for (int i = 0; i < accounts.size(); i++) { 460 if (accounts.get(i).name.equals(selectedAccountName)) { 461 return i; 462 } 463 } 464 // no account selected. 465 return SELECTED_ITEM_NONE; 466 } 467 468 private String[] getListOfDisplayableOptions(ArrayList<Account> accounts) { 469 // List of options includes all accounts found together with "Add new account" as the 470 // last item in the list. 471 String[] listItems = new String[accounts.size() + (mDisallowAddAccounts ? 0 : 1)]; 472 for (int i = 0; i < accounts.size(); i++) { 473 listItems[i] = accounts.get(i).name; 474 } 475 if (!mDisallowAddAccounts) { 476 listItems[accounts.size()] = getResources().getString( 477 R.string.add_account_button_label); 478 } 479 return listItems; 480 } 481 482 /** 483 * Create a list of Account objects for each account that is acceptable. Filter out 484 * accounts that don't match the allowable types, if provided, or that don't match the 485 * allowable accounts, if provided. 486 */ 487 private ArrayList<Account> getAcceptableAccountChoices(AccountManager accountManager) { 488 final Account[] accounts = accountManager.getAccountsForPackage(mCallingPackage, 489 mCallingUid); 490 ArrayList<Account> accountsToPopulate = new ArrayList<Account>(accounts.length); 491 for (Account account : accounts) { 492 if (mSetOfAllowableAccounts != null 493 && !mSetOfAllowableAccounts.contains(account)) { 494 continue; 495 } 496 if (mSetOfRelevantAccountTypes != null 497 && !mSetOfRelevantAccountTypes.contains(account.type)) { 498 continue; 499 } 500 accountsToPopulate.add(account); 501 } 502 return accountsToPopulate; 503 } 504 505 /** 506 * Return a set of account types speficied by the intent as well as supported by the 507 * AccountManager. 508 */ 509 private Set<String> getReleventAccountTypes(final Intent intent) { 510 // An account type is relevant iff it is allowed by the caller and supported by the account 511 // manager. 512 Set<String> setOfRelevantAccountTypes = null; 513 final String[] allowedAccountTypes = 514 intent.getStringArrayExtra(EXTRA_ALLOWABLE_ACCOUNT_TYPES_STRING_ARRAY); 515 if (allowedAccountTypes != null) { 516 setOfRelevantAccountTypes = Sets.newHashSet(allowedAccountTypes); 517 AuthenticatorDescription[] descs = AccountManager.get(this).getAuthenticatorTypes(); 518 Set<String> supportedAccountTypes = new HashSet<String>(descs.length); 519 for (AuthenticatorDescription desc : descs) { 520 supportedAccountTypes.add(desc.type); 521 } 522 setOfRelevantAccountTypes.retainAll(supportedAccountTypes); 523 } 524 return setOfRelevantAccountTypes; 525 } 526 527 /** 528 * Returns a set of whitelisted accounts given by the intent or null if none specified by the 529 * intent. 530 */ 531 private Set<Account> getAllowableAccountSet(final Intent intent) { 532 Set<Account> setOfAllowableAccounts = null; 533 final ArrayList<Parcelable> validAccounts = 534 intent.getParcelableArrayListExtra(EXTRA_ALLOWABLE_ACCOUNTS_ARRAYLIST); 535 if (validAccounts != null) { 536 setOfAllowableAccounts = new HashSet<Account>(validAccounts.size()); 537 for (Parcelable parcelable : validAccounts) { 538 setOfAllowableAccounts.add((Account)parcelable); 539 } 540 } 541 return setOfAllowableAccounts; 542 } 543 544 /** 545 * Overrides the description text view for the picker activity if specified by the intent. 546 * If not specified then makes the description invisible. 547 */ 548 private void overrideDescriptionIfSupplied(String descriptionOverride) { 549 TextView descriptionView = (TextView) findViewById(R.id.description); 550 if (!TextUtils.isEmpty(descriptionOverride)) { 551 descriptionView.setText(descriptionOverride); 552 } else { 553 descriptionView.setVisibility(View.GONE); 554 } 555 } 556 557 /** 558 * Populates the UI ListView with the given list of items and selects an item 559 * based on {@code mSelectedItemIndex} member variable. 560 */ 561 private final void populateUIAccountList(String[] listItems) { 562 ListView list = (ListView) findViewById(android.R.id.list); 563 list.setAdapter(new ArrayAdapter<String>(this, 564 android.R.layout.simple_list_item_single_choice, listItems)); 565 list.setChoiceMode(ListView.CHOICE_MODE_SINGLE); 566 list.setItemsCanFocus(false); 567 list.setOnItemClickListener( 568 new AdapterView.OnItemClickListener() { 569 @Override 570 public void onItemClick(AdapterView<?> parent, View v, int position, long id) { 571 mSelectedItemIndex = position; 572 mOkButton.setEnabled(true); 573 } 574 }); 575 if (mSelectedItemIndex != SELECTED_ITEM_NONE) { 576 list.setItemChecked(mSelectedItemIndex, true); 577 if (Log.isLoggable(TAG, Log.VERBOSE)) { 578 Log.v(TAG, "List item " + mSelectedItemIndex + " should be selected"); 579 } 580 } 581 } 582 } 583