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.example.android.contactmanager; 18 19 import android.accounts.Account; 20 import android.accounts.AccountManager; 21 import android.accounts.AuthenticatorDescription; 22 import android.accounts.OnAccountsUpdateListener; 23 import android.app.Activity; 24 import android.content.ContentProviderOperation; 25 import android.content.Context; 26 import android.content.pm.PackageManager; 27 import android.graphics.drawable.Drawable; 28 import android.os.Bundle; 29 import android.provider.ContactsContract; 30 import android.util.Log; 31 import android.view.LayoutInflater; 32 import android.view.View; 33 import android.view.ViewGroup; 34 import android.widget.AdapterView; 35 import android.widget.ArrayAdapter; 36 import android.widget.Button; 37 import android.widget.EditText; 38 import android.widget.ImageView; 39 import android.widget.Spinner; 40 import android.widget.TextView; 41 import android.widget.Toast; 42 import android.widget.AdapterView.OnItemSelectedListener; 43 44 import java.util.ArrayList; 45 import java.util.Iterator; 46 47 public final class ContactAdder extends Activity implements OnAccountsUpdateListener 48 { 49 public static final String TAG = "ContactsAdder"; 50 public static final String ACCOUNT_NAME = 51 "com.example.android.contactmanager.ContactsAdder.ACCOUNT_NAME"; 52 public static final String ACCOUNT_TYPE = 53 "com.example.android.contactmanager.ContactsAdder.ACCOUNT_TYPE"; 54 55 private ArrayList<AccountData> mAccounts; 56 private AccountAdapter mAccountAdapter; 57 private Spinner mAccountSpinner; 58 private EditText mContactEmailEditText; 59 private ArrayList<Integer> mContactEmailTypes; 60 private Spinner mContactEmailTypeSpinner; 61 private EditText mContactNameEditText; 62 private EditText mContactPhoneEditText; 63 private ArrayList<Integer> mContactPhoneTypes; 64 private Spinner mContactPhoneTypeSpinner; 65 private Button mContactSaveButton; 66 private AccountData mSelectedAccount; 67 68 /** 69 * Called when the activity is first created. Responsible for initializing the UI. 70 */ 71 @Override onCreate(Bundle savedInstanceState)72 public void onCreate(Bundle savedInstanceState) 73 { 74 Log.v(TAG, "Activity State: onCreate()"); 75 super.onCreate(savedInstanceState); 76 setContentView(R.layout.contact_adder); 77 78 // Obtain handles to UI objects 79 mAccountSpinner = (Spinner) findViewById(R.id.accountSpinner); 80 mContactNameEditText = (EditText) findViewById(R.id.contactNameEditText); 81 mContactPhoneEditText = (EditText) findViewById(R.id.contactPhoneEditText); 82 mContactEmailEditText = (EditText) findViewById(R.id.contactEmailEditText); 83 mContactPhoneTypeSpinner = (Spinner) findViewById(R.id.contactPhoneTypeSpinner); 84 mContactEmailTypeSpinner = (Spinner) findViewById(R.id.contactEmailTypeSpinner); 85 mContactSaveButton = (Button) findViewById(R.id.contactSaveButton); 86 87 // Prepare list of supported account types 88 // Note: Other types are available in ContactsContract.CommonDataKinds 89 // Also, be aware that type IDs differ between Phone and Email, and MUST be computed 90 // separately. 91 mContactPhoneTypes = new ArrayList<Integer>(); 92 mContactPhoneTypes.add(ContactsContract.CommonDataKinds.Phone.TYPE_HOME); 93 mContactPhoneTypes.add(ContactsContract.CommonDataKinds.Phone.TYPE_WORK); 94 mContactPhoneTypes.add(ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE); 95 mContactPhoneTypes.add(ContactsContract.CommonDataKinds.Phone.TYPE_OTHER); 96 mContactEmailTypes = new ArrayList<Integer>(); 97 mContactEmailTypes.add(ContactsContract.CommonDataKinds.Email.TYPE_HOME); 98 mContactEmailTypes.add(ContactsContract.CommonDataKinds.Email.TYPE_WORK); 99 mContactEmailTypes.add(ContactsContract.CommonDataKinds.Email.TYPE_MOBILE); 100 mContactEmailTypes.add(ContactsContract.CommonDataKinds.Email.TYPE_OTHER); 101 102 // Prepare model for account spinner 103 mAccounts = new ArrayList<AccountData>(); 104 mAccountAdapter = new AccountAdapter(this, mAccounts); 105 mAccountSpinner.setAdapter(mAccountAdapter); 106 107 // Populate list of account types for phone 108 ArrayAdapter<String> adapter; 109 adapter = new ArrayAdapter<String>(this, android.R.layout.simple_spinner_item); 110 adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); 111 Iterator<Integer> iter; 112 iter = mContactPhoneTypes.iterator(); 113 while (iter.hasNext()) { 114 adapter.add(ContactsContract.CommonDataKinds.Phone.getTypeLabel( 115 this.getResources(), 116 iter.next(), 117 getString(R.string.undefinedTypeLabel)).toString()); 118 } 119 mContactPhoneTypeSpinner.setAdapter(adapter); 120 mContactPhoneTypeSpinner.setPrompt(getString(R.string.selectLabel)); 121 122 // Populate list of account types for email 123 adapter = new ArrayAdapter<String>(this, android.R.layout.simple_spinner_item); 124 adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); 125 iter = mContactEmailTypes.iterator(); 126 while (iter.hasNext()) { 127 adapter.add(ContactsContract.CommonDataKinds.Email.getTypeLabel( 128 this.getResources(), 129 iter.next(), 130 getString(R.string.undefinedTypeLabel)).toString()); 131 } 132 mContactEmailTypeSpinner.setAdapter(adapter); 133 mContactEmailTypeSpinner.setPrompt(getString(R.string.selectLabel)); 134 135 // Prepare the system account manager. On registering the listener below, we also ask for 136 // an initial callback to pre-populate the account list. 137 AccountManager.get(this).addOnAccountsUpdatedListener(this, null, true); 138 139 // Register handlers for UI elements 140 mAccountSpinner.setOnItemSelectedListener(new OnItemSelectedListener() { 141 public void onItemSelected(AdapterView<?> parent, View view, int position, long i) { 142 updateAccountSelection(); 143 } 144 145 public void onNothingSelected(AdapterView<?> parent) { 146 // We don't need to worry about nothing being selected, since Spinners don't allow 147 // this. 148 } 149 }); 150 mContactSaveButton.setOnClickListener(new View.OnClickListener() { 151 public void onClick(View v) { 152 onSaveButtonClicked(); 153 } 154 }); 155 } 156 157 /** 158 * Actions for when the Save button is clicked. Creates a contact entry and terminates the 159 * activity. 160 */ onSaveButtonClicked()161 private void onSaveButtonClicked() { 162 Log.v(TAG, "Save button clicked"); 163 createContactEntry(); 164 finish(); 165 } 166 167 /** 168 * Creates a contact entry from the current UI values in the account named by mSelectedAccount. 169 */ createContactEntry()170 protected void createContactEntry() { 171 // Get values from UI 172 String name = mContactNameEditText.getText().toString(); 173 String phone = mContactPhoneEditText.getText().toString(); 174 String email = mContactEmailEditText.getText().toString(); 175 int phoneType = mContactPhoneTypes.get( 176 mContactPhoneTypeSpinner.getSelectedItemPosition()); 177 int emailType = mContactEmailTypes.get( 178 mContactEmailTypeSpinner.getSelectedItemPosition());; 179 180 // Prepare contact creation request 181 // 182 // Note: We use RawContacts because this data must be associated with a particular account. 183 // The system will aggregate this with any other data for this contact and create a 184 // coresponding entry in the ContactsContract.Contacts provider for us. 185 ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>(); 186 ops.add(ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI) 187 .withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, mSelectedAccount.getType()) 188 .withValue(ContactsContract.RawContacts.ACCOUNT_NAME, mSelectedAccount.getName()) 189 .build()); 190 ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI) 191 .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0) 192 .withValue(ContactsContract.Data.MIMETYPE, 193 ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE) 194 .withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, name) 195 .build()); 196 ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI) 197 .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0) 198 .withValue(ContactsContract.Data.MIMETYPE, 199 ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE) 200 .withValue(ContactsContract.CommonDataKinds.Phone.NUMBER, phone) 201 .withValue(ContactsContract.CommonDataKinds.Phone.TYPE, phoneType) 202 .build()); 203 ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI) 204 .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0) 205 .withValue(ContactsContract.Data.MIMETYPE, 206 ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE) 207 .withValue(ContactsContract.CommonDataKinds.Email.DATA, email) 208 .withValue(ContactsContract.CommonDataKinds.Email.TYPE, emailType) 209 .build()); 210 211 // Ask the Contact provider to create a new contact 212 Log.i(TAG,"Selected account: " + mSelectedAccount.getName() + " (" + 213 mSelectedAccount.getType() + ")"); 214 Log.i(TAG,"Creating contact: " + name); 215 try { 216 getContentResolver().applyBatch(ContactsContract.AUTHORITY, ops); 217 } catch (Exception e) { 218 // Display warning 219 Context ctx = getApplicationContext(); 220 CharSequence txt = getString(R.string.contactCreationFailure); 221 int duration = Toast.LENGTH_SHORT; 222 Toast toast = Toast.makeText(ctx, txt, duration); 223 toast.show(); 224 225 // Log exception 226 Log.e(TAG, "Exceptoin encoutered while inserting contact: " + e); 227 } 228 } 229 230 /** 231 * Called when this activity is about to be destroyed by the system. 232 */ 233 @Override onDestroy()234 public void onDestroy() { 235 // Remove AccountManager callback 236 AccountManager.get(this).removeOnAccountsUpdatedListener(this); 237 super.onDestroy(); 238 } 239 240 /** 241 * Updates account list spinner when the list of Accounts on the system changes. Satisfies 242 * OnAccountsUpdateListener implementation. 243 */ onAccountsUpdated(Account[] a)244 public void onAccountsUpdated(Account[] a) { 245 Log.i(TAG, "Account list update detected"); 246 // Clear out any old data to prevent duplicates 247 mAccounts.clear(); 248 249 // Get account data from system 250 AuthenticatorDescription[] accountTypes = AccountManager.get(this).getAuthenticatorTypes(); 251 252 // Populate tables 253 for (int i = 0; i < a.length; i++) { 254 // The user may have multiple accounts with the same name, so we need to construct a 255 // meaningful display name for each. 256 String systemAccountType = a[i].type; 257 AuthenticatorDescription ad = getAuthenticatorDescription(systemAccountType, 258 accountTypes); 259 AccountData data = new AccountData(a[i].name, ad); 260 mAccounts.add(data); 261 } 262 263 // Update the account spinner 264 mAccountAdapter.notifyDataSetChanged(); 265 } 266 267 /** 268 * Obtain the AuthenticatorDescription for a given account type. 269 * @param type The account type to locate. 270 * @param dictionary An array of AuthenticatorDescriptions, as returned by AccountManager. 271 * @return The description for the specified account type. 272 */ getAuthenticatorDescription(String type, AuthenticatorDescription[] dictionary)273 private static AuthenticatorDescription getAuthenticatorDescription(String type, 274 AuthenticatorDescription[] dictionary) { 275 for (int i = 0; i < dictionary.length; i++) { 276 if (dictionary[i].type.equals(type)) { 277 return dictionary[i]; 278 } 279 } 280 // No match found 281 throw new RuntimeException("Unable to find matching authenticator"); 282 } 283 284 /** 285 * Update account selection. If NO_ACCOUNT is selected, then we prohibit inserting new contacts. 286 */ updateAccountSelection()287 private void updateAccountSelection() { 288 // Read current account selection 289 mSelectedAccount = (AccountData) mAccountSpinner.getSelectedItem(); 290 } 291 292 /** 293 * A container class used to repreresent all known information about an account. 294 */ 295 private class AccountData { 296 private String mName; 297 private String mType; 298 private CharSequence mTypeLabel; 299 private Drawable mIcon; 300 301 /** 302 * @param name The name of the account. This is usually the user's email address or 303 * username. 304 * @param description The description for this account. This will be dictated by the 305 * type of account returned, and can be obtained from the system AccountManager. 306 */ AccountData(String name, AuthenticatorDescription description)307 public AccountData(String name, AuthenticatorDescription description) { 308 mName = name; 309 if (description != null) { 310 mType = description.type; 311 312 // The type string is stored in a resource, so we need to convert it into something 313 // human readable. 314 String packageName = description.packageName; 315 PackageManager pm = getPackageManager(); 316 317 if (description.labelId != 0) { 318 mTypeLabel = pm.getText(packageName, description.labelId, null); 319 if (mTypeLabel == null) { 320 throw new IllegalArgumentException("LabelID provided, but label not found"); 321 } 322 } else { 323 mTypeLabel = ""; 324 } 325 326 if (description.iconId != 0) { 327 mIcon = pm.getDrawable(packageName, description.iconId, null); 328 if (mIcon == null) { 329 throw new IllegalArgumentException("IconID provided, but drawable not " + 330 "found"); 331 } 332 } else { 333 mIcon = getResources().getDrawable(android.R.drawable.sym_def_app_icon); 334 } 335 } 336 } 337 getName()338 public String getName() { 339 return mName; 340 } 341 getType()342 public String getType() { 343 return mType; 344 } 345 getTypeLabel()346 public CharSequence getTypeLabel() { 347 return mTypeLabel; 348 } 349 getIcon()350 public Drawable getIcon() { 351 return mIcon; 352 } 353 toString()354 public String toString() { 355 return mName; 356 } 357 } 358 359 /** 360 * Custom adapter used to display account icons and descriptions in the account spinner. 361 */ 362 private class AccountAdapter extends ArrayAdapter<AccountData> { AccountAdapter(Context context, ArrayList<AccountData> accountData)363 public AccountAdapter(Context context, ArrayList<AccountData> accountData) { 364 super(context, android.R.layout.simple_spinner_item, accountData); 365 setDropDownViewResource(R.layout.account_entry); 366 } 367 getDropDownView(int position, View convertView, ViewGroup parent)368 public View getDropDownView(int position, View convertView, ViewGroup parent) { 369 // Inflate a view template 370 if (convertView == null) { 371 LayoutInflater layoutInflater = getLayoutInflater(); 372 convertView = layoutInflater.inflate(R.layout.account_entry, parent, false); 373 } 374 TextView firstAccountLine = (TextView) convertView.findViewById(R.id.firstAccountLine); 375 TextView secondAccountLine = (TextView) convertView.findViewById(R.id.secondAccountLine); 376 ImageView accountIcon = (ImageView) convertView.findViewById(R.id.accountIcon); 377 378 // Populate template 379 AccountData data = getItem(position); 380 firstAccountLine.setText(data.getName()); 381 secondAccountLine.setText(data.getTypeLabel()); 382 Drawable icon = data.getIcon(); 383 if (icon == null) { 384 icon = getResources().getDrawable(android.R.drawable.ic_menu_search); 385 } 386 accountIcon.setImageDrawable(icon); 387 return convertView; 388 } 389 } 390 } 391