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