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