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