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.contacts.activities; 18 19 import android.app.Activity; 20 import android.app.AlertDialog; 21 import android.app.Dialog; 22 import android.content.ComponentName; 23 import android.content.DialogInterface; 24 import android.content.Intent; 25 import android.database.Cursor; 26 import android.net.Uri; 27 import android.os.Bundle; 28 import android.provider.ContactsContract.CommonDataKinds.Email; 29 import android.provider.ContactsContract.Contacts; 30 import android.provider.ContactsContract.Intents; 31 import android.provider.ContactsContract.PhoneLookup; 32 import android.provider.ContactsContract.RawContacts; 33 import android.util.Log; 34 35 import com.android.contacts.common.CallUtil; 36 import com.android.contacts.ContactsActivity; 37 import com.android.contacts.R; 38 import com.android.contacts.util.NotifyingAsyncQueryHandler; 39 40 /** 41 * Handle several edge cases around showing or possibly creating contacts in 42 * connected with a specific E-mail address or phone number. Will search based 43 * on incoming {@link Intent#getData()} as described by 44 * {@link Intents#SHOW_OR_CREATE_CONTACT}. 45 * <ul> 46 * <li>If no matching contacts found, will prompt user with dialog to add to a 47 * contact, then will use {@link Intent#ACTION_INSERT_OR_EDIT} to let create new 48 * contact or edit new data into an existing one. 49 * <li>If one matching contact found, directly show {@link Intent#ACTION_VIEW} 50 * that specific contact. 51 * <li>If more than one matching found, show list of matching contacts using 52 * {@link Intent#ACTION_SEARCH}. 53 * </ul> 54 */ 55 public final class ShowOrCreateActivity extends ContactsActivity 56 implements NotifyingAsyncQueryHandler.AsyncQueryListener { 57 static final String TAG = "ShowOrCreateActivity"; 58 static final boolean LOGD = false; 59 60 static final String[] PHONES_PROJECTION = new String[] { 61 PhoneLookup._ID, 62 PhoneLookup.LOOKUP_KEY, 63 }; 64 65 static final String[] CONTACTS_PROJECTION = new String[] { 66 Email.CONTACT_ID, 67 Email.LOOKUP_KEY, 68 }; 69 70 static final int CONTACT_ID_INDEX = 0; 71 static final int LOOKUP_KEY_INDEX = 1; 72 73 static final int CREATE_CONTACT_DIALOG = 1; 74 75 static final int QUERY_TOKEN = 42; 76 77 private NotifyingAsyncQueryHandler mQueryHandler; 78 79 private Bundle mCreateExtras; 80 private String mCreateDescrip; 81 private boolean mCreateForce; 82 83 @Override onCreate(Bundle icicle)84 protected void onCreate(Bundle icicle) { 85 super.onCreate(icicle); 86 87 // Create handler if doesn't exist, otherwise cancel any running 88 if (mQueryHandler == null) { 89 mQueryHandler = new NotifyingAsyncQueryHandler(this, this); 90 } else { 91 mQueryHandler.cancelOperation(QUERY_TOKEN); 92 } 93 94 final Intent intent = getIntent(); 95 final Uri data = intent.getData(); 96 97 // Unpack scheme and target data from intent 98 String scheme = null; 99 String ssp = null; 100 if (data != null) { 101 scheme = data.getScheme(); 102 ssp = data.getSchemeSpecificPart(); 103 } 104 105 // Build set of extras for possible use when creating contact 106 mCreateExtras = new Bundle(); 107 Bundle originalExtras = intent.getExtras(); 108 if (originalExtras != null) { 109 mCreateExtras.putAll(originalExtras); 110 } 111 112 // Read possible extra with specific title 113 mCreateDescrip = intent.getStringExtra(Intents.EXTRA_CREATE_DESCRIPTION); 114 if (mCreateDescrip == null) { 115 mCreateDescrip = ssp; 116 } 117 118 // Allow caller to bypass dialog prompt 119 mCreateForce = intent.getBooleanExtra(Intents.EXTRA_FORCE_CREATE, false); 120 121 // Handle specific query request 122 if (CallUtil.SCHEME_MAILTO.equals(scheme)) { 123 mCreateExtras.putString(Intents.Insert.EMAIL, ssp); 124 125 Uri uri = Uri.withAppendedPath(Email.CONTENT_FILTER_URI, Uri.encode(ssp)); 126 mQueryHandler.startQuery(QUERY_TOKEN, null, uri, CONTACTS_PROJECTION, null, null, null); 127 128 } else if (CallUtil.SCHEME_TEL.equals(scheme)) { 129 mCreateExtras.putString(Intents.Insert.PHONE, ssp); 130 131 Uri uri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, ssp); 132 mQueryHandler.startQuery(QUERY_TOKEN, null, uri, PHONES_PROJECTION, null, null, null); 133 134 } else { 135 Log.w(TAG, "Invalid intent:" + getIntent()); 136 finish(); 137 } 138 } 139 140 @Override onStop()141 protected void onStop() { 142 super.onStop(); 143 if (mQueryHandler != null) { 144 mQueryHandler.cancelOperation(QUERY_TOKEN); 145 } 146 } 147 148 /** {@inheritDoc} */ onQueryComplete(int token, Object cookie, Cursor cursor)149 public void onQueryComplete(int token, Object cookie, Cursor cursor) { 150 if (cursor == null) { 151 // Bail when problem running query in background 152 finish(); 153 return; 154 } 155 156 // Count contacts found by query 157 int count = 0; 158 long contactId = -1; 159 String lookupKey = null; 160 try { 161 count = cursor.getCount(); 162 if (count == 1 && cursor.moveToFirst()) { 163 // Try reading ID if only one contact returned 164 contactId = cursor.getLong(CONTACT_ID_INDEX); 165 lookupKey = cursor.getString(LOOKUP_KEY_INDEX); 166 } 167 } finally { 168 cursor.close(); 169 } 170 171 if (count == 1 && contactId != -1) { 172 // If we only found one item, jump right to viewing it 173 final Uri contactUri = Contacts.getLookupUri(contactId, lookupKey); 174 final Intent viewIntent = new Intent(Intent.ACTION_VIEW, contactUri); 175 startActivity(viewIntent); 176 finish(); 177 178 } else if (count > 1) { 179 // If more than one, show pick list 180 Intent listIntent = new Intent(Intent.ACTION_SEARCH); 181 listIntent.setComponent(new ComponentName(this, PeopleActivity.class)); 182 listIntent.putExtras(mCreateExtras); 183 startActivity(listIntent); 184 finish(); 185 186 } else { 187 // No matching contacts found 188 if (mCreateForce) { 189 // Forced to create new contact 190 Intent createIntent = new Intent(Intent.ACTION_INSERT, RawContacts.CONTENT_URI); 191 createIntent.putExtras(mCreateExtras); 192 createIntent.setType(RawContacts.CONTENT_TYPE); 193 194 startActivity(createIntent); 195 finish(); 196 197 } else { 198 showDialog(CREATE_CONTACT_DIALOG); 199 } 200 } 201 } 202 203 @Override onCreateDialog(int id)204 protected Dialog onCreateDialog(int id) { 205 switch(id) { 206 case CREATE_CONTACT_DIALOG: 207 // Prompt user to insert or edit contact 208 final Intent createIntent = new Intent(Intent.ACTION_INSERT_OR_EDIT); 209 createIntent.putExtras(mCreateExtras); 210 createIntent.setType(RawContacts.CONTENT_ITEM_TYPE); 211 212 final CharSequence message = getResources().getString( 213 R.string.add_contact_dlg_message_fmt, mCreateDescrip); 214 215 return new AlertDialog.Builder(this, AlertDialog.THEME_HOLO_LIGHT) 216 .setMessage(message) 217 .setPositiveButton(android.R.string.ok, 218 new IntentClickListener(this, createIntent)) 219 .setNegativeButton(android.R.string.cancel, 220 new IntentClickListener(this, null)) 221 .setOnCancelListener(new DialogInterface.OnCancelListener() { 222 @Override 223 public void onCancel(DialogInterface dialog) { 224 finish(); // Close the activity. 225 }}) 226 .create(); 227 } 228 return super.onCreateDialog(id); 229 } 230 231 /** 232 * Listener for {@link DialogInterface} that launches a given {@link Intent} 233 * when clicked. When clicked, this also closes the parent using 234 * {@link Activity#finish()}. 235 */ 236 private static class IntentClickListener implements DialogInterface.OnClickListener { 237 private Activity mParent; 238 private Intent mIntent; 239 240 /** 241 * @param parent {@link Activity} to use for launching target. 242 * @param intent Target {@link Intent} to launch when clicked. 243 */ 244 public IntentClickListener(Activity parent, Intent intent) { 245 mParent = parent; 246 mIntent = intent; 247 } 248 249 public void onClick(DialogInterface dialog, int which) { 250 if (mIntent != null) { 251 mParent.startActivity(mIntent); 252 } 253 mParent.finish(); 254 } 255 } 256 } 257