1 /* 2 * Copyright (C) 2009 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.development; 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.AuthenticatorDescription; 24 import android.accounts.AuthenticatorException; 25 import android.accounts.OnAccountsUpdateListener; 26 import android.accounts.OperationCanceledException; 27 import android.app.Activity; 28 import android.app.AlertDialog; 29 import android.app.Dialog; 30 import android.content.Context; 31 import android.content.DialogInterface; 32 import android.content.pm.PackageManager; 33 import android.content.res.Resources; 34 import android.os.Bundle; 35 import android.os.Handler; 36 import android.os.Parcelable; 37 import android.text.TextUtils; 38 import android.util.Log; 39 import android.view.ContextMenu; 40 import android.view.LayoutInflater; 41 import android.view.MenuInflater; 42 import android.view.MenuItem; 43 import android.view.View; 44 import android.view.ViewGroup; 45 import android.widget.AdapterView; 46 import android.widget.ArrayAdapter; 47 import android.widget.EditText; 48 import android.widget.ImageView; 49 import android.widget.ListView; 50 import android.widget.Spinner; 51 import android.widget.TextView; 52 53 import java.io.IOException; 54 import java.util.ArrayList; 55 import java.util.List; 56 57 public class AccountsTester extends Activity implements OnAccountsUpdateListener { 58 private static final String TAG = "AccountsTester"; 59 private Spinner mAccountTypesSpinner; 60 private ListView mAccountsListView; 61 private AccountManager mAccountManager; 62 private Account mLongPressedAccount = null; 63 private AuthenticatorDescription[] mAuthenticatorDescs; 64 65 private static final int GET_AUTH_TOKEN_DIALOG_ID = 1; 66 private static final int UPDATE_CREDENTIALS_DIALOG_ID = 2; 67 private static final int INVALIDATE_AUTH_TOKEN_DIALOG_ID = 3; 68 private static final int TEST_HAS_FEATURES_DIALOG_ID = 4; 69 private static final int MESSAGE_DIALOG_ID = 5; 70 private static final int GET_AUTH_TOKEN_BY_TYPE_AND_FEATURE_DIALOG_ID = 6; 71 72 private EditText mDesiredAuthTokenTypeEditText; 73 private EditText mDesiredFeaturesEditText; 74 private volatile CharSequence mDialogMessage; 75 76 @Override onCreate(Bundle savedInstanceState)77 protected void onCreate(Bundle savedInstanceState) { 78 super.onCreate(savedInstanceState); 79 mAccountManager = AccountManager.get(this); 80 setContentView(R.layout.accounts_tester); 81 ButtonClickListener buttonClickListener = new ButtonClickListener(); 82 83 mAccountTypesSpinner = (Spinner) findViewById(R.id.accounts_tester_account_types_spinner); 84 mAccountsListView = (ListView) findViewById(R.id.accounts_tester_accounts_list); 85 registerForContextMenu(mAccountsListView); 86 initializeAuthenticatorsSpinner(); 87 findViewById(R.id.accounts_tester_get_all_accounts).setOnClickListener(buttonClickListener); 88 findViewById(R.id.accounts_tester_get_accounts_by_type).setOnClickListener( 89 buttonClickListener); 90 findViewById(R.id.accounts_tester_add_account).setOnClickListener(buttonClickListener); 91 findViewById(R.id.accounts_tester_edit_properties).setOnClickListener(buttonClickListener); 92 findViewById(R.id.accounts_tester_get_auth_token_by_type_and_feature).setOnClickListener( 93 buttonClickListener); 94 mDesiredAuthTokenTypeEditText = 95 (EditText) findViewById(R.id.accounts_tester_desired_authtokentype); 96 mDesiredFeaturesEditText = (EditText) findViewById(R.id.accounts_tester_desired_features); 97 } 98 99 private class AccountArrayAdapter extends ArrayAdapter<Account> { 100 protected LayoutInflater mInflater; 101 AccountArrayAdapter(Context context, Account[] accounts)102 public AccountArrayAdapter(Context context, Account[] accounts) { 103 super(context, R.layout.account_list_item, accounts); 104 mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 105 } 106 107 class ViewHolder { 108 TextView name; 109 ImageView icon; 110 Account account; 111 } 112 113 @Override getView(int position, View convertView, ViewGroup parent)114 public View getView(int position, View convertView, ViewGroup parent) { 115 // A ViewHolder keeps references to children views to avoid unneccessary calls 116 // to findViewById() on each row. 117 ViewHolder holder; 118 119 // When convertView is not null, we can reuse it directly, there is no need 120 // to reinflate it. We only inflate a new View when the convertView supplied 121 // by ListView is null. 122 if (convertView == null) { 123 convertView = mInflater.inflate(R.layout.account_list_item, null); 124 125 // Creates a ViewHolder and store references to the two children views 126 // we want to bind data to. 127 holder = new ViewHolder(); 128 holder.name = (TextView) convertView.findViewById( 129 R.id.accounts_tester_account_name); 130 holder.icon = (ImageView) convertView.findViewById( 131 R.id.accounts_tester_account_type_icon); 132 133 convertView.setTag(holder); 134 } else { 135 // Get the ViewHolder back to get fast access to the TextView 136 // and the ImageView. 137 holder = (ViewHolder) convertView.getTag(); 138 } 139 140 final Account account = getItem(position); 141 holder.account = account; 142 holder.icon.setVisibility(View.INVISIBLE); 143 for (AuthenticatorDescription desc : mAuthenticatorDescs) { 144 if (desc.type.equals(account.type)) { 145 final String packageName = desc.packageName; 146 try { 147 final Context authContext = getContext().createPackageContext(packageName, 148 0); 149 holder.icon.setImageDrawable(authContext.getResources().getDrawable( 150 desc.iconId)); 151 holder.icon.setVisibility(View.VISIBLE); 152 } catch (PackageManager.NameNotFoundException e) { 153 Log.d(TAG, "error getting the Package Context for " + packageName, e); 154 } 155 } 156 } 157 158 // Set text field 159 holder.name.setText(account.name); 160 return convertView; 161 } 162 } 163 initializeAuthenticatorsSpinner()164 private void initializeAuthenticatorsSpinner() { 165 mAuthenticatorDescs = mAccountManager.getAuthenticatorTypes(); 166 List<String> names = new ArrayList(mAuthenticatorDescs.length); 167 for (int i = 0; i < mAuthenticatorDescs.length; i++) { 168 Context authContext; 169 try { 170 authContext = createPackageContext(mAuthenticatorDescs[i].packageName, 0); 171 } catch (PackageManager.NameNotFoundException e) { 172 continue; 173 } 174 try { 175 names.add(authContext.getString(mAuthenticatorDescs[i].labelId)); 176 } catch (Resources.NotFoundException e) { 177 continue; 178 } 179 } 180 181 String[] namesArray = names.toArray(new String[names.size()]); 182 ArrayAdapter<String> adapter = 183 new ArrayAdapter<String>(AccountsTester.this, 184 android.R.layout.simple_spinner_item, namesArray); 185 mAccountTypesSpinner.setAdapter(adapter); 186 } 187 onAccountsUpdated(Account[] accounts)188 public void onAccountsUpdated(Account[] accounts) { 189 Log.d(TAG, "onAccountsUpdated: \n " + TextUtils.join("\n ", accounts)); 190 mAccountsListView.setAdapter(new AccountArrayAdapter(this, accounts)); 191 } 192 onStart()193 protected void onStart() { 194 super.onStart(); 195 final Handler mainHandler = new Handler(getMainLooper()); 196 mAccountManager.addOnAccountsUpdatedListener(this, mainHandler, 197 true /* updateImmediately */); 198 } 199 onStop()200 protected void onStop() { 201 super.onStop(); 202 mAccountManager.removeOnAccountsUpdatedListener(this); 203 } 204 205 class ButtonClickListener implements View.OnClickListener { onClick(View v)206 public void onClick(View v) { 207 if (R.id.accounts_tester_get_all_accounts == v.getId()) { 208 onAccountsUpdated(mAccountManager.getAccounts()); 209 } else if (R.id.accounts_tester_get_accounts_by_type == v.getId()) { 210 String type = getSelectedAuthenticator().type; 211 onAccountsUpdated(mAccountManager.getAccountsByType(type)); 212 } else if (R.id.accounts_tester_add_account == v.getId()) { 213 String authTokenType = mDesiredAuthTokenTypeEditText.getText().toString(); 214 if (TextUtils.isEmpty(authTokenType)) { 215 authTokenType = null; 216 } 217 String featureString = mDesiredFeaturesEditText.getText().toString(); 218 String[] requiredFeatures = TextUtils.split(featureString, " "); 219 if (requiredFeatures.length == 0) { 220 requiredFeatures = null; 221 } 222 mAccountManager.addAccount(getSelectedAuthenticator().type, 223 authTokenType, requiredFeatures, null /* options */, 224 AccountsTester.this, 225 new CallbackToDialog(AccountsTester.this, "add account"), 226 null /* handler */); 227 } else if (R.id.accounts_tester_edit_properties == v.getId()) { 228 mAccountManager.editProperties(getSelectedAuthenticator().type, 229 AccountsTester.this, 230 new CallbackToDialog(AccountsTester.this, "edit properties"), 231 null /* handler */); 232 } else if (R.id.accounts_tester_get_auth_token_by_type_and_feature == v.getId()) { 233 showDialog(GET_AUTH_TOKEN_BY_TYPE_AND_FEATURE_DIALOG_ID); 234 } else { 235 // unknown button 236 } 237 } 238 } 239 getSelectedAuthenticator()240 private AuthenticatorDescription getSelectedAuthenticator() { 241 return mAuthenticatorDescs[mAccountTypesSpinner.getSelectedItemPosition()]; 242 } 243 244 @Override onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo)245 public void onCreateContextMenu(ContextMenu menu, View v, 246 ContextMenu.ContextMenuInfo menuInfo) { 247 menu.setHeaderTitle(R.string.accounts_tester_account_context_menu_title); 248 249 AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo)menuInfo; 250 251 MenuInflater inflater = getMenuInflater(); 252 inflater.inflate(R.layout.account_list_context_menu, menu); 253 AccountArrayAdapter.ViewHolder holder = 254 (AccountArrayAdapter.ViewHolder)info.targetView.getTag(); 255 mLongPressedAccount = holder.account; 256 } 257 onSaveInstanceState(Bundle outState)258 protected void onSaveInstanceState(Bundle outState) { 259 outState.putParcelable("account", mLongPressedAccount); 260 } 261 onRestoreInstanceState(Bundle savedInstanceState)262 protected void onRestoreInstanceState(Bundle savedInstanceState) { 263 mLongPressedAccount = savedInstanceState.getParcelable("account"); 264 } 265 266 @Override onContextItemSelected(MenuItem item)267 public boolean onContextItemSelected(MenuItem item) { 268 if (item.getItemId() == R.id.accounts_tester_remove_account) { 269 final Account account = mLongPressedAccount; 270 mAccountManager.removeAccount(account, new AccountManagerCallback<Boolean>() { 271 public void run(AccountManagerFuture<Boolean> future) { 272 try { 273 Log.d(TAG, "removeAccount(" + account + ") = " + future.getResult()); 274 } catch (OperationCanceledException e) { 275 } catch (IOException e) { 276 } catch (AuthenticatorException e) { 277 } 278 } 279 }, null /* handler */); 280 } else if (item.getItemId() == R.id.accounts_tester_clear_password) { 281 final Account account = mLongPressedAccount; 282 mAccountManager.clearPassword(account); 283 showMessageDialog("cleared"); 284 } else if (item.getItemId() == R.id.accounts_tester_get_auth_token) { 285 showDialog(GET_AUTH_TOKEN_DIALOG_ID); 286 } else if (item.getItemId() == R.id.accounts_tester_test_has_features) { 287 showDialog(TEST_HAS_FEATURES_DIALOG_ID); 288 } else if (item.getItemId() == R.id.accounts_tester_invalidate_auth_token) { 289 showDialog(INVALIDATE_AUTH_TOKEN_DIALOG_ID); 290 } else if (item.getItemId() == R.id.accounts_tester_update_credentials) { 291 showDialog(UPDATE_CREDENTIALS_DIALOG_ID); 292 } else if (item.getItemId() == R.id.accounts_tester_confirm_credentials) { 293 mAccountManager.confirmCredentials(mLongPressedAccount, null, 294 AccountsTester.this, new CallbackToDialog(this, "confirm credentials"), 295 null /* handler */); 296 } 297 return true; 298 } 299 300 @Override onCreateDialog(final int id)301 protected Dialog onCreateDialog(final int id) { 302 switch (id) { 303 case GET_AUTH_TOKEN_DIALOG_ID: 304 case INVALIDATE_AUTH_TOKEN_DIALOG_ID: 305 case UPDATE_CREDENTIALS_DIALOG_ID: 306 case TEST_HAS_FEATURES_DIALOG_ID: { 307 final View view = LayoutInflater.from(this).inflate(R.layout.get_auth_token_view, 308 null); 309 AlertDialog.Builder builder = new AlertDialog.Builder(this); 310 builder.setPositiveButton(R.string.accounts_tester_ok_button, 311 new DialogInterface.OnClickListener() { 312 public void onClick(DialogInterface dialog, int which) { 313 EditText value = (EditText) view.findViewById( 314 R.id.accounts_tester_auth_token_type); 315 316 String authTokenType = value.getText().toString(); 317 final Account account = mLongPressedAccount; 318 if (id == GET_AUTH_TOKEN_DIALOG_ID) { 319 mAccountManager.getAuthToken(account, 320 authTokenType, 321 null /* loginOptions */, 322 AccountsTester.this, 323 new CallbackToDialog(AccountsTester.this, 324 "get auth token"), 325 null /* handler */); 326 } else if (id == INVALIDATE_AUTH_TOKEN_DIALOG_ID) { 327 mAccountManager.getAuthToken(account, authTokenType, false, 328 new GetAndInvalidateAuthTokenCallback(account), null); 329 } else if (id == TEST_HAS_FEATURES_DIALOG_ID) { 330 String[] features = TextUtils.split(authTokenType, ","); 331 mAccountManager.hasFeatures(account, features, 332 new TestHasFeaturesCallback(), null); 333 } else { 334 mAccountManager.updateCredentials( 335 account, 336 authTokenType, null /* loginOptions */, 337 AccountsTester.this, 338 new CallbackToDialog(AccountsTester.this, "update"), 339 null /* handler */); 340 } 341 } 342 }); 343 builder.setView(view); 344 return builder.create(); 345 } 346 347 case GET_AUTH_TOKEN_BY_TYPE_AND_FEATURE_DIALOG_ID: { 348 final View view = LayoutInflater.from(this).inflate(R.layout.get_features_view, 349 null); 350 AlertDialog.Builder builder = new AlertDialog.Builder(this); 351 builder.setPositiveButton(R.string.accounts_tester_ok_button, 352 new DialogInterface.OnClickListener() { 353 public void onClick(DialogInterface dialog, int which) { 354 EditText value = (EditText) view.findViewById( 355 R.id.accounts_tester_auth_token_type); 356 357 String authTokenType = value.getText().toString(); 358 359 value = (EditText) view.findViewById( 360 R.id.accounts_tester_features); 361 362 String features = value.getText().toString(); 363 364 final Account account = mLongPressedAccount; 365 mAccountManager.getAuthTokenByFeatures( 366 getSelectedAuthenticator().type, 367 authTokenType, 368 TextUtils.isEmpty(features) ? null : features.split(" "), 369 AccountsTester.this, 370 null /* addAccountOptions */, 371 null /* getAuthTokenOptions */, 372 new CallbackToDialog(AccountsTester.this, 373 "get auth token by features"), 374 null /* handler */); 375 } 376 }); 377 builder.setView(view); 378 return builder.create(); 379 } 380 381 case MESSAGE_DIALOG_ID: { 382 AlertDialog.Builder builder = new AlertDialog.Builder(this); 383 builder.setMessage(mDialogMessage); 384 return builder.create(); 385 } 386 } 387 388 return super.onCreateDialog(id); 389 } 390 newAccountsCallback(String type, String[] features)391 AccountManagerCallback<Bundle> newAccountsCallback(String type, String[] features) { 392 return new GetAccountsCallback(type, features); 393 } 394 395 class GetAccountsCallback implements AccountManagerCallback<Bundle> { 396 final String[] mFeatures; 397 final String mAccountType; 398 GetAccountsCallback(String type, String[] features)399 public GetAccountsCallback(String type, String[] features) { 400 mFeatures = features; 401 mAccountType = type; 402 } 403 run(AccountManagerFuture<Bundle> future)404 public void run(AccountManagerFuture<Bundle> future) { 405 Log.d(TAG, "GetAccountsCallback: type " + mAccountType 406 + ", features " 407 + (mFeatures == null ? "none" : TextUtils.join(",", mFeatures))); 408 try { 409 Bundle result = future.getResult(); 410 Parcelable[] accounts = result.getParcelableArray(AccountManager.KEY_ACCOUNTS); 411 Log.d(TAG, "found " + accounts.length + " accounts"); 412 for (Parcelable account : accounts) { 413 Log.d(TAG, " " + account); 414 } 415 } catch (OperationCanceledException e) { 416 Log.d(TAG, "failure", e); 417 } catch (IOException e) { 418 Log.d(TAG, "failure", e); 419 } catch (AuthenticatorException e) { 420 Log.d(TAG, "failure", e); 421 } 422 } 423 } 424 newAuthTokensCallback(String type, String authTokenType, String[] features)425 AccountManagerCallback<Bundle> newAuthTokensCallback(String type, String authTokenType, 426 String[] features) { 427 return new GetAuthTokenCallback(type, authTokenType, features); 428 } 429 430 class GetAuthTokenCallback implements AccountManagerCallback<Bundle> { 431 final String[] mFeatures; 432 final String mAccountType; 433 final String mAuthTokenType; 434 GetAuthTokenCallback(String type, String authTokenType, String[] features)435 public GetAuthTokenCallback(String type, String authTokenType, String[] features) { 436 mFeatures = features; 437 mAccountType = type; 438 mAuthTokenType = authTokenType; 439 } 440 run(AccountManagerFuture<Bundle> future)441 public void run(AccountManagerFuture<Bundle> future) { 442 Log.d(TAG, "GetAuthTokenCallback: type " + mAccountType 443 + ", features " 444 + (mFeatures == null ? "none" : TextUtils.join(",", mFeatures))); 445 getAndLogResult(future, "get auth token"); 446 } 447 } 448 449 private class GetAndInvalidateAuthTokenCallback implements AccountManagerCallback<Bundle> { 450 private final Account mAccount; 451 GetAndInvalidateAuthTokenCallback(Account account)452 private GetAndInvalidateAuthTokenCallback(Account account) { 453 mAccount = account; 454 } 455 run(AccountManagerFuture<Bundle> future)456 public void run(AccountManagerFuture<Bundle> future) { 457 Bundle result = getAndLogResult(future, "get and invalidate"); 458 if (result != null) { 459 String authToken = result.getString(AccountManager.KEY_AUTHTOKEN); 460 mAccountManager.invalidateAuthToken(mAccount.type, authToken); 461 } 462 } 463 } 464 showMessageDialog(String message)465 private void showMessageDialog(String message) { 466 mDialogMessage = message; 467 removeDialog(MESSAGE_DIALOG_ID); 468 showDialog(MESSAGE_DIALOG_ID); 469 } 470 471 private class TestHasFeaturesCallback implements AccountManagerCallback<Boolean> { run(AccountManagerFuture<Boolean> future)472 public void run(AccountManagerFuture<Boolean> future) { 473 try { 474 Boolean hasFeatures = future.getResult(); 475 Log.d(TAG, "hasFeatures: " + hasFeatures); 476 showMessageDialog("hasFeatures: " + hasFeatures); 477 } catch (OperationCanceledException e) { 478 Log.d(TAG, "interrupted"); 479 showMessageDialog("operation was canceled"); 480 } catch (IOException e) { 481 Log.d(TAG, "error", e); 482 showMessageDialog("operation got an IOException"); 483 } catch (AuthenticatorException e) { 484 Log.d(TAG, "error", e); 485 showMessageDialog("operation got an AuthenticationException"); 486 } 487 } 488 } 489 490 private static class CallbackToDialog implements AccountManagerCallback<Bundle> { 491 private final AccountsTester mActivity; 492 private final String mLabel; 493 CallbackToDialog(AccountsTester activity, String label)494 private CallbackToDialog(AccountsTester activity, String label) { 495 mActivity = activity; 496 mLabel = label; 497 } 498 run(AccountManagerFuture<Bundle> future)499 public void run(AccountManagerFuture<Bundle> future) { 500 mActivity.getAndLogResult(future, mLabel); 501 } 502 } 503 getAndLogResult(AccountManagerFuture<Bundle> future, String label)504 private Bundle getAndLogResult(AccountManagerFuture<Bundle> future, String label) { 505 try { 506 Bundle result = future.getResult(); 507 result.keySet(); 508 Log.d(TAG, label + ": " + result); 509 StringBuffer sb = new StringBuffer(); 510 sb.append(label).append(" result:"); 511 for (String key : result.keySet()) { 512 Object value = result.get(key); 513 if (AccountManager.KEY_AUTHTOKEN.equals(key)) { 514 value = "<redacted>"; 515 } 516 sb.append("\n ").append(key).append(" -> ").append(value); 517 } 518 showMessageDialog(sb.toString()); 519 return result; 520 } catch (OperationCanceledException e) { 521 Log.d(TAG, label + " failed", e); 522 showMessageDialog(label + " was canceled"); 523 return null; 524 } catch (IOException e) { 525 Log.d(TAG, label + " failed", e); 526 showMessageDialog(label + " failed with IOException: " + e.getMessage()); 527 return null; 528 } catch (AuthenticatorException e) { 529 Log.d(TAG, label + " failed", e); 530 showMessageDialog(label + " failed with an AuthenticatorException: " + e.getMessage()); 531 return null; 532 } 533 } 534 } 535