Home | History | Annotate | Download | only in contactmanager
      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
     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      */
    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      */
    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
    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      */
    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      */
    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      */
    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          */
    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 
    338         public String getName() {
    339             return mName;
    340         }
    341 
    342         public String getType() {
    343             return mType;
    344         }
    345 
    346         public CharSequence getTypeLabel() {
    347             return mTypeLabel;
    348         }
    349 
    350         public Drawable getIcon() {
    351             return mIcon;
    352         }
    353 
    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> {
    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 
    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