blob: 9dc898fc3c4922d343d2c1fbc7ce8892b77e7d44 [file] [log] [blame]
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -08001/*
2 * Copyright (C) 2007 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;
18
Evan Millar66388be2009-05-28 15:41:07 -070019import static com.android.contacts.ContactEntryAdapter.AGGREGATE_DISPLAY_NAME_COLUMN;
20import static com.android.contacts.ContactEntryAdapter.AGGREGATE_PROJECTION;
21import static com.android.contacts.ContactEntryAdapter.AGGREGATE_STARRED_COLUMN;
22import static com.android.contacts.ContactEntryAdapter.DATA_ID_COLUMN;
23import static com.android.contacts.ContactEntryAdapter.DATA_PACKAGE_COLUMN;
24import static com.android.contacts.ContactEntryAdapter.DATA_MIMETYPE_COLUMN;
Evan Millar45e0ed32009-06-01 16:44:38 -070025import static com.android.contacts.ContactEntryAdapter.DATA_IS_PRIMARY_COLUMN;
26import static com.android.contacts.ContactEntryAdapter.DATA_IS_SUPER_PRIMARY_COLUMN;
Evan Millar66388be2009-05-28 15:41:07 -070027import static com.android.contacts.ContactEntryAdapter.DATA_1_COLUMN;
28import static com.android.contacts.ContactEntryAdapter.DATA_2_COLUMN;
29import static com.android.contacts.ContactEntryAdapter.DATA_3_COLUMN;
30import static com.android.contacts.ContactEntryAdapter.DATA_4_COLUMN;
31import static com.android.contacts.ContactEntryAdapter.DATA_5_COLUMN;
32import static com.android.contacts.ContactEntryAdapter.DATA_6_COLUMN;
33import static com.android.contacts.ContactEntryAdapter.DATA_7_COLUMN;
34import static com.android.contacts.ContactEntryAdapter.DATA_8_COLUMN;
35import static com.android.contacts.ContactEntryAdapter.DATA_9_COLUMN;
36import static com.android.contacts.ContactEntryAdapter.DATA_10_COLUMN;
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -080037
38import com.android.internal.telephony.ITelephony;
39
40import android.app.AlertDialog;
41import android.app.Dialog;
42import android.app.ListActivity;
43import android.content.ActivityNotFoundException;
44import android.content.ContentResolver;
45import android.content.ContentUris;
46import android.content.ContentValues;
47import android.content.Context;
48import android.content.DialogInterface;
49import android.content.Intent;
50import android.content.pm.PackageManager;
51import android.content.pm.ResolveInfo;
52import android.content.res.Resources;
53import android.database.ContentObserver;
54import android.database.Cursor;
Evan Millar45e0ed32009-06-01 16:44:38 -070055import android.graphics.Bitmap;
Evan Millar66388be2009-05-28 15:41:07 -070056import android.graphics.BitmapFactory;
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -080057import android.graphics.drawable.Drawable;
58import android.media.Ringtone;
59import android.media.RingtoneManager;
60import android.net.Uri;
61import android.os.Bundle;
62import android.os.Handler;
63import android.os.RemoteException;
64import android.os.ServiceManager;
65import android.os.SystemClock;
Evan Millar66388be2009-05-28 15:41:07 -070066import android.provider.ContactsContract.CommonDataKinds;
67import android.provider.ContactsContract.Aggregates;
Evan Millar45e0ed32009-06-01 16:44:38 -070068import android.provider.ContactsContract.Contacts;
Evan Millar66388be2009-05-28 15:41:07 -070069import android.provider.ContactsContract.Data;
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -080070import android.provider.Im;
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -080071import android.text.TextUtils;
72import android.util.Log;
73import android.view.ContextMenu;
74import android.view.KeyEvent;
75import android.view.Menu;
76import android.view.MenuItem;
77import android.view.View;
78import android.view.ViewGroup;
79import android.view.ContextMenu.ContextMenuInfo;
80import android.widget.AdapterView;
81import android.widget.CheckBox;
82import android.widget.ImageView;
83import android.widget.ListView;
84import android.widget.TextView;
85import android.widget.Toast;
86
87import java.util.ArrayList;
88import java.util.List;
89
90/**
91 * Displays the details of a specific contact.
92 */
Evan Millar5c22c3b2009-05-29 11:37:54 -070093public class ViewContactActivity extends ListActivity
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -080094 implements View.OnCreateContextMenuListener, View.OnClickListener,
95 DialogInterface.OnClickListener {
96 private static final String TAG = "ViewContact";
97 private static final String SHOW_BARCODE_INTENT = "com.google.zxing.client.android.ENCODE";
98
99 private static final boolean SHOW_SEPARATORS = false;
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800100
101 private static final int DIALOG_CONFIRM_DELETE = 1;
102
103 public static final int MENU_ITEM_DELETE = 1;
104 public static final int MENU_ITEM_MAKE_DEFAULT = 2;
105 public static final int MENU_ITEM_SHOW_BARCODE = 3;
106
107 private Uri mUri;
Evan Millar45e0ed32009-06-01 16:44:38 -0700108 private Uri mAggDataUri;
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800109 private ContentResolver mResolver;
110 private ViewAdapter mAdapter;
111 private int mNumPhoneNumbers = 0;
112
113 /* package */ ArrayList<ViewEntry> mPhoneEntries = new ArrayList<ViewEntry>();
114 /* package */ ArrayList<ViewEntry> mSmsEntries = new ArrayList<ViewEntry>();
115 /* package */ ArrayList<ViewEntry> mEmailEntries = new ArrayList<ViewEntry>();
116 /* package */ ArrayList<ViewEntry> mPostalEntries = new ArrayList<ViewEntry>();
117 /* package */ ArrayList<ViewEntry> mImEntries = new ArrayList<ViewEntry>();
118 /* package */ ArrayList<ViewEntry> mOrganizationEntries = new ArrayList<ViewEntry>();
The Android Open Source Projectcac191e2009-03-18 22:20:27 -0700119 /* package */ ArrayList<ViewEntry> mGroupEntries = new ArrayList<ViewEntry>();
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800120 /* package */ ArrayList<ViewEntry> mOtherEntries = new ArrayList<ViewEntry>();
121 /* package */ ArrayList<ArrayList<ViewEntry>> mSections = new ArrayList<ArrayList<ViewEntry>>();
122
123 private Cursor mCursor;
124 private boolean mObserverRegistered;
Evan Millar5c22c3b2009-05-29 11:37:54 -0700125
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800126 private ContentObserver mObserver = new ContentObserver(new Handler()) {
127 @Override
128 public boolean deliverSelfNotifications() {
129 return true;
130 }
131
132 @Override
133 public void onChange(boolean selfChange) {
134 if (mCursor != null && !mCursor.isClosed()){
135 dataChanged();
136 }
137 }
138 };
139
140 public void onClick(DialogInterface dialog, int which) {
141 if (mCursor != null) {
142 if (mObserverRegistered) {
143 mCursor.unregisterContentObserver(mObserver);
144 mObserverRegistered = false;
145 }
146 mCursor.close();
147 mCursor = null;
148 }
149 getContentResolver().delete(mUri, null, null);
150 finish();
151 }
152
153 public void onClick(View view) {
154 if (!mObserverRegistered) {
155 return;
156 }
157 switch (view.getId()) {
158 case R.id.star: {
Evan Millar7e4accf2009-06-08 10:43:26 -0700159 mCursor.moveToFirst();
Evan Millar66388be2009-05-28 15:41:07 -0700160 int oldStarredState = mCursor.getInt(AGGREGATE_STARRED_COLUMN);
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800161 ContentValues values = new ContentValues(1);
Evan Millar66388be2009-05-28 15:41:07 -0700162 values.put(Aggregates.STARRED, oldStarredState == 1 ? 0 : 1);
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800163 getContentResolver().update(mUri, values, null, null);
164 break;
165 }
166 }
167 }
168
169 private TextView mNameView;
170 private TextView mPhoneticNameView; // may be null in some locales
171 private ImageView mPhotoView;
172 private int mNoPhotoResource;
173 private CheckBox mStarView;
174 private boolean mShowSmsLinksForAllPhones;
175
176 @Override
177 protected void onCreate(Bundle icicle) {
178 super.onCreate(icicle);
179
180 setContentView(R.layout.view_contact);
181 getListView().setOnCreateContextMenuListener(this);
182
183 mNameView = (TextView) findViewById(R.id.name);
184 mPhoneticNameView = (TextView) findViewById(R.id.phonetic_name);
185 mPhotoView = (ImageView) findViewById(R.id.photo);
186 mStarView = (CheckBox) findViewById(R.id.star);
187 mStarView.setOnClickListener(this);
188
189 // Set the photo with a random "no contact" image
190 long now = SystemClock.elapsedRealtime();
191 int num = (int) now & 0xf;
192 if (num < 9) {
193 // Leaning in from right, common
194 mNoPhotoResource = R.drawable.ic_contact_picture;
195 } else if (num < 14) {
196 // Leaning in from left uncommon
197 mNoPhotoResource = R.drawable.ic_contact_picture_2;
198 } else {
199 // Coming in from the top, rare
200 mNoPhotoResource = R.drawable.ic_contact_picture_3;
201 }
202
Evan Millar45e0ed32009-06-01 16:44:38 -0700203 mUri = getIntent().getData();
204 mAggDataUri = Uri.withAppendedPath(mUri, "data");
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800205 mResolver = getContentResolver();
206
207 // Build the list of sections. The order they're added to mSections dictates the
208 // order they are displayed in the list.
209 mSections.add(mPhoneEntries);
210 mSections.add(mSmsEntries);
211 mSections.add(mEmailEntries);
212 mSections.add(mImEntries);
213 mSections.add(mPostalEntries);
214 mSections.add(mOrganizationEntries);
The Android Open Source Projectcac191e2009-03-18 22:20:27 -0700215 mSections.add(mGroupEntries);
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800216 mSections.add(mOtherEntries);
217
218 //TODO Read this value from a preference
219 mShowSmsLinksForAllPhones = true;
220
Evan Millar45e0ed32009-06-01 16:44:38 -0700221 mCursor = mResolver.query(mAggDataUri,
222 AGGREGATE_PROJECTION, null, null, null);
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800223 }
224
225 @Override
226 protected void onResume() {
227 super.onResume();
228 mObserverRegistered = true;
229 mCursor.registerContentObserver(mObserver);
230 dataChanged();
231 }
232
233 @Override
234 protected void onPause() {
235 super.onPause();
236 if (mCursor != null) {
237 if (mObserverRegistered) {
238 mObserverRegistered = false;
239 mCursor.unregisterContentObserver(mObserver);
240 }
241 mCursor.deactivate();
242 }
243 }
244
245 @Override
246 protected void onDestroy() {
247 super.onDestroy();
248
249 if (mCursor != null) {
250 if (mObserverRegistered) {
251 mCursor.unregisterContentObserver(mObserver);
252 mObserverRegistered = false;
253 }
254 mCursor.close();
255 }
256 }
257
258 @Override
259 protected Dialog onCreateDialog(int id) {
260 switch (id) {
261 case DIALOG_CONFIRM_DELETE:
262 return new AlertDialog.Builder(this)
263 .setTitle(R.string.deleteConfirmation_title)
264 .setIcon(android.R.drawable.ic_dialog_alert)
265 .setMessage(R.string.deleteConfirmation)
266 .setNegativeButton(android.R.string.cancel, null)
267 .setPositiveButton(android.R.string.ok, this)
268 .setCancelable(false)
269 .create();
270 }
271 return null;
272 }
273
274 private void dataChanged() {
275 mCursor.requery();
276 if (mCursor.moveToFirst()) {
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800277 // Set the star
Evan Millar66388be2009-05-28 15:41:07 -0700278 mStarView.setChecked(mCursor.getInt(AGGREGATE_STARRED_COLUMN) == 1 ? true : false);
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800279
280 // Build up the contact entries
281 buildEntries(mCursor);
282 if (mAdapter == null) {
283 mAdapter = new ViewAdapter(this, mSections);
284 setListAdapter(mAdapter);
285 } else {
286 mAdapter.setSections(mSections, SHOW_SEPARATORS);
287 }
288 } else {
289 Toast.makeText(this, R.string.invalidContactMessage, Toast.LENGTH_SHORT).show();
290 Log.e(TAG, "invalid contact uri: " + mUri);
291 finish();
292 }
293 }
294
295 @Override
296 public boolean onCreateOptionsMenu(Menu menu) {
297 menu.add(0, 0, 0, R.string.menu_editContact)
298 .setIcon(android.R.drawable.ic_menu_edit)
299 .setIntent(new Intent(Intent.ACTION_EDIT, mUri))
300 .setAlphabeticShortcut('e');
301 menu.add(0, MENU_ITEM_DELETE, 0, R.string.menu_deleteContact)
302 .setIcon(android.R.drawable.ic_menu_delete);
303
304 return true;
305 }
306
307 @Override
308 public boolean onPrepareOptionsMenu(Menu menu) {
309 super.onPrepareOptionsMenu(menu);
310 // Perform this check each time the menu is about to be shown, because the Barcode Scanner
311 // could be installed or uninstalled at any time.
312 if (isBarcodeScannerInstalled()) {
313 if (menu.findItem(MENU_ITEM_SHOW_BARCODE) == null) {
314 menu.add(0, MENU_ITEM_SHOW_BARCODE, 0, R.string.menu_showBarcode)
315 .setIcon(R.drawable.ic_menu_show_barcode);
316 }
317 } else {
318 menu.removeItem(MENU_ITEM_SHOW_BARCODE);
319 }
320 return true;
321 }
322
323 private boolean isBarcodeScannerInstalled() {
324 final Intent intent = new Intent(SHOW_BARCODE_INTENT);
325 ResolveInfo ri = getPackageManager().resolveActivity(intent,
326 PackageManager.MATCH_DEFAULT_ONLY);
327 return ri != null;
328 }
329
330 @Override
331 public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfo) {
332 AdapterView.AdapterContextMenuInfo info;
333 try {
334 info = (AdapterView.AdapterContextMenuInfo) menuInfo;
335 } catch (ClassCastException e) {
336 Log.e(TAG, "bad menuInfo", e);
337 return;
338 }
339
340 // This can be null sometimes, don't crash...
341 if (info == null) {
342 Log.e(TAG, "bad menuInfo");
343 return;
344 }
Evan Millar5c22c3b2009-05-29 11:37:54 -0700345
Evan Millar45e0ed32009-06-01 16:44:38 -0700346 ViewEntry entry = ContactEntryAdapter.getEntry(mSections, info.position, SHOW_SEPARATORS);
347 if (entry.mimetype.equals(CommonDataKinds.Phone.CONTENT_ITEM_TYPE)) {
348 menu.add(0, 0, 0, R.string.menu_call).setIntent(entry.intent);
349 menu.add(0, 0, 0, R.string.menu_sendSMS).setIntent(entry.auxIntent);
350 if (entry.primaryIcon == -1) {
351 menu.add(0, MENU_ITEM_MAKE_DEFAULT, 0, R.string.menu_makeDefaultNumber);
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800352 }
Evan Millar45e0ed32009-06-01 16:44:38 -0700353 } else if (entry.mimetype.equals(CommonDataKinds.Email.CONTENT_ITEM_TYPE)) {
354 menu.add(0, 0, 0, R.string.menu_sendEmail).setIntent(entry.intent);
355 if (entry.primaryIcon == -1) {
356 menu.add(0, MENU_ITEM_MAKE_DEFAULT, 0, R.string.menu_makeDefaultEmail);
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800357 }
Evan Millar45e0ed32009-06-01 16:44:38 -0700358 } else if (entry.mimetype.equals(CommonDataKinds.Postal.CONTENT_ITEM_TYPE)) {
359 menu.add(0, 0, 0, R.string.menu_viewAddress).setIntent(entry.intent);
360 }
361 // TODO(emillar): add back with group support.
362 /* else if (entry.mimetype.equals()) {
363 menu.add(0, 0, 0, R.string.menu_viewGroup).setIntent(entry.intent);
364 } */
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800365 }
366
367 @Override
368 public boolean onOptionsItemSelected(MenuItem item) {
369 switch (item.getItemId()) {
370 case MENU_ITEM_DELETE: {
371 // Get confirmation
372 showDialog(DIALOG_CONFIRM_DELETE);
373 return true;
374 }
Evan Millar5c22c3b2009-05-29 11:37:54 -0700375
Evan Millar66388be2009-05-28 15:41:07 -0700376 // TODO(emillar) Bring this back.
377 /*case MENU_ITEM_SHOW_BARCODE:
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800378 if (mCursor.moveToFirst()) {
379 Intent intent = new Intent(SHOW_BARCODE_INTENT);
380 intent.putExtra("ENCODE_TYPE", "CONTACT_TYPE");
381 Bundle bundle = new Bundle();
Evan Millar66388be2009-05-28 15:41:07 -0700382 String name = mCursor.getString(AGGREGATE_DISPLAY_NAME_COLUMN);
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800383 if (!TextUtils.isEmpty(name)) {
Jeffrey Sharkey715b32a2009-03-27 18:56:05 -0700384 // Correctly handle when section headers are hidden
385 int sepAdjust = SHOW_SEPARATORS ? 1 : 0;
Evan Millar5c22c3b2009-05-29 11:37:54 -0700386
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800387 bundle.putString(Contacts.Intents.Insert.NAME, name);
388 // The 0th ViewEntry in each ArrayList below is a separator item
Jeffrey Sharkey715b32a2009-03-27 18:56:05 -0700389 int entriesToAdd = Math.min(mPhoneEntries.size() - sepAdjust, PHONE_KEYS.length);
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800390 for (int x = 0; x < entriesToAdd; x++) {
Jeffrey Sharkey715b32a2009-03-27 18:56:05 -0700391 ViewEntry entry = mPhoneEntries.get(x + sepAdjust);
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800392 bundle.putString(PHONE_KEYS[x], entry.data);
393 }
Jeffrey Sharkey715b32a2009-03-27 18:56:05 -0700394 entriesToAdd = Math.min(mEmailEntries.size() - sepAdjust, EMAIL_KEYS.length);
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800395 for (int x = 0; x < entriesToAdd; x++) {
Jeffrey Sharkey715b32a2009-03-27 18:56:05 -0700396 ViewEntry entry = mEmailEntries.get(x + sepAdjust);
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800397 bundle.putString(EMAIL_KEYS[x], entry.data);
398 }
Jeffrey Sharkey715b32a2009-03-27 18:56:05 -0700399 if (mPostalEntries.size() >= 1 + sepAdjust) {
400 ViewEntry entry = mPostalEntries.get(sepAdjust);
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800401 bundle.putString(Contacts.Intents.Insert.POSTAL, entry.data);
402 }
403 intent.putExtra("ENCODE_DATA", bundle);
404 try {
405 startActivity(intent);
406 } catch (ActivityNotFoundException e) {
407 // The check in onPrepareOptionsMenu() should make this impossible, but
408 // for safety I'm catching the exception rather than crashing. Ideally
409 // I'd call Menu.removeItem() here too, but I don't see a way to get
410 // the options menu.
411 Log.e(TAG, "Show barcode menu item was clicked but Barcode Scanner " +
412 "was not installed.");
413 }
414 return true;
415 }
416 }
Evan Millar66388be2009-05-28 15:41:07 -0700417 break; */
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800418 }
419 return super.onOptionsItemSelected(item);
420 }
Evan Millar5c22c3b2009-05-29 11:37:54 -0700421
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800422 @Override
423 public boolean onContextItemSelected(MenuItem item) {
424 switch (item.getItemId()) {
425 case MENU_ITEM_MAKE_DEFAULT: {
Evan Millar45e0ed32009-06-01 16:44:38 -0700426 AdapterView.AdapterContextMenuInfo info;
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800427 try {
428 info = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo();
429 } catch (ClassCastException e) {
430 Log.e(TAG, "bad menuInfo", e);
431 break;
432 }
433
434 ViewEntry entry = ContactEntryAdapter.getEntry(mSections, info.position,
435 SHOW_SEPARATORS);
Evan Millar45e0ed32009-06-01 16:44:38 -0700436
437 // Update the primary values in the data record.
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800438 ContentValues values = new ContentValues(1);
Evan Millar45e0ed32009-06-01 16:44:38 -0700439 values.put(Data.IS_PRIMARY, 1);
440 values.put(Data.IS_SUPER_PRIMARY, 1);
441
442 Log.i(TAG, mUri.toString());
443 getContentResolver().update(ContentUris.withAppendedId(Data.CONTENT_URI, entry.id),
444 values, null, null);
445 dataChanged();
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800446 return true;
447 }
448 }
449 return super.onContextItemSelected(item);
450 }
451
452 @Override
453 public boolean onKeyDown(int keyCode, KeyEvent event) {
454 switch (keyCode) {
455 case KeyEvent.KEYCODE_CALL: {
456 try {
457 ITelephony phone = ITelephony.Stub.asInterface(
458 ServiceManager.checkService("phone"));
459 if (phone != null && !phone.isIdle()) {
460 // Skip out and let the key be handled at a higher level
461 break;
462 }
463 } catch (RemoteException re) {
464 // Fall through and try to call the contact
465 }
466
467 int index = getListView().getSelectedItemPosition();
468 if (index != -1) {
469 ViewEntry entry = ViewAdapter.getEntry(mSections, index, SHOW_SEPARATORS);
Evan Millar66388be2009-05-28 15:41:07 -0700470 if (entry.intent.getAction() == Intent.ACTION_CALL_PRIVILEGED) {
471 startActivity(entry.intent);
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800472 }
473 } else if (mNumPhoneNumbers != 0) {
474 // There isn't anything selected, call the default number
475 Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED, mUri);
476 startActivity(intent);
477 }
478 return true;
479 }
480
481 case KeyEvent.KEYCODE_DEL: {
482 showDialog(DIALOG_CONFIRM_DELETE);
483 return true;
484 }
485 }
486
487 return super.onKeyDown(keyCode, event);
488 }
489
490 @Override
491 protected void onListItemClick(ListView l, View v, int position, long id) {
492 ViewEntry entry = ViewAdapter.getEntry(mSections, position, SHOW_SEPARATORS);
493 if (entry != null) {
494 Intent intent = entry.intent;
495 if (intent != null) {
496 try {
497 startActivity(intent);
498 } catch (ActivityNotFoundException e) {
499 Log.e(TAG, "No activity found for intent: " + intent);
500 signalError();
501 }
502 } else {
503 signalError();
504 }
505 } else {
506 signalError();
507 }
508 }
509
510 /**
511 * Signal an error to the user via a beep, or some other method.
512 */
513 private void signalError() {
514 //TODO: implement this when we have the sonification APIs
515 }
516
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800517 private Uri constructImToUrl(String host, String data) {
Evan Millar45e0ed32009-06-01 16:44:38 -0700518 // don't encode the url, because the Activity Manager can't find using the encoded url
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800519 StringBuilder buf = new StringBuilder("imto://");
520 buf.append(host);
521 buf.append('/');
522 buf.append(data);
523 return Uri.parse(buf.toString());
524 }
525
526 /**
527 * Build up the entries to display on the screen.
Evan Millar5c22c3b2009-05-29 11:37:54 -0700528 *
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800529 * @param personCursor the URI for the contact being displayed
530 */
Evan Millar66388be2009-05-28 15:41:07 -0700531 private final void buildEntries(Cursor aggCursor) {
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800532 // Clear out the old entries
533 final int numSections = mSections.size();
534 for (int i = 0; i < numSections; i++) {
535 mSections.get(i).clear();
536 }
537
Evan Millar66388be2009-05-28 15:41:07 -0700538 // Build up method entries
539 if (mUri != null) {
Evan Millar45e0ed32009-06-01 16:44:38 -0700540 Bitmap photoBitmap = null;
Evan Millar66388be2009-05-28 15:41:07 -0700541 while (aggCursor.moveToNext()) {
542 final String mimetype = aggCursor.getString(DATA_MIMETYPE_COLUMN);
Evan Millar5c22c3b2009-05-29 11:37:54 -0700543
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800544 ViewEntry entry = new ViewEntry();
Evan Millar66388be2009-05-28 15:41:07 -0700545
546 final long id = aggCursor.getLong(DATA_ID_COLUMN);
547 final Uri uri = ContentUris.withAppendedId(Data.CONTENT_URI, id);
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800548 entry.id = id;
549 entry.uri = uri;
Evan Millar45e0ed32009-06-01 16:44:38 -0700550 entry.mimetype = mimetype;
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800551
Evan Millar66388be2009-05-28 15:41:07 -0700552 if (mimetype.equals(CommonDataKinds.Phone.CONTENT_ITEM_TYPE)
Evan Millar5c22c3b2009-05-29 11:37:54 -0700553 || mimetype.equals(CommonDataKinds.Email.CONTENT_ITEM_TYPE)
554 || mimetype.equals(CommonDataKinds.Postal.CONTENT_ITEM_TYPE)
Evan Millar66388be2009-05-28 15:41:07 -0700555 || mimetype.equals(CommonDataKinds.Im.CONTENT_ITEM_TYPE)) {
556 final int type = aggCursor.getInt(DATA_1_COLUMN);
Evan Millar45e0ed32009-06-01 16:44:38 -0700557 final String label = aggCursor.getString(DATA_3_COLUMN);
558 final String data = aggCursor.getString(DATA_2_COLUMN);
559 final boolean isSuperPrimary = "1".equals(
560 aggCursor.getString(DATA_IS_SUPER_PRIMARY_COLUMN));
Evan Millar66388be2009-05-28 15:41:07 -0700561
562 // Don't crash if the data is bogus
563 if (TextUtils.isEmpty(data)) {
564 Log.w(TAG, "empty data for contact method " + id);
565 continue;
566 }
567
568 // Build phone entries
569 if (mimetype.equals(CommonDataKinds.Phone.CONTENT_ITEM_TYPE)) {
570 mNumPhoneNumbers++;
Evan Millar5c22c3b2009-05-29 11:37:54 -0700571
Evan Millar66388be2009-05-28 15:41:07 -0700572 final CharSequence displayLabel = ContactsUtils.getDisplayLabel(
573 this, mimetype, type, label);
574 entry.label = buildActionString(R.string.actionCall, displayLabel, true);
575 entry.data = data;
576 entry.intent = new Intent(Intent.ACTION_CALL_PRIVILEGED, entry.uri);
577 entry.auxIntent = new Intent(Intent.ACTION_SENDTO,
578 Uri.fromParts("sms", data, null));
Evan Millar45e0ed32009-06-01 16:44:38 -0700579 if (isSuperPrimary) {
Evan Millar66388be2009-05-28 15:41:07 -0700580 entry.primaryIcon = R.drawable.ic_default_number;
581 }
582 entry.actionIcon = android.R.drawable.sym_action_call;
583 mPhoneEntries.add(entry);
584
Evan Millar5c22c3b2009-05-29 11:37:54 -0700585 if (type == CommonDataKinds.Phone.TYPE_MOBILE
Evan Millar66388be2009-05-28 15:41:07 -0700586 || mShowSmsLinksForAllPhones) {
587 // Add an SMS entry
588 ViewEntry smsEntry = new ViewEntry();
589 smsEntry.label = buildActionString(
590 R.string.actionText, displayLabel, true);
591 smsEntry.data = data;
592 smsEntry.id = id;
593 smsEntry.uri = uri;
594 smsEntry.intent = entry.auxIntent;
Evan Millar45e0ed32009-06-01 16:44:38 -0700595 smsEntry.mimetype = FastTrackWindow.MIME_SMS_ADDRESS;
Evan Millar66388be2009-05-28 15:41:07 -0700596 smsEntry.actionIcon = R.drawable.sym_action_sms;
597 mSmsEntries.add(smsEntry);
598 }
599 // Build email entries
600 } else if (mimetype.equals(CommonDataKinds.Email.CONTENT_ITEM_TYPE)) {
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800601 entry.label = buildActionString(R.string.actionEmail,
Evan Millar66388be2009-05-28 15:41:07 -0700602 ContactsUtils.getDisplayLabel(this, mimetype, type, label), true);
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800603 entry.data = data;
604 entry.intent = new Intent(Intent.ACTION_SENDTO,
605 Uri.fromParts("mailto", data, null));
606 entry.actionIcon = android.R.drawable.sym_action_email;
Evan Millar45e0ed32009-06-01 16:44:38 -0700607 if (isSuperPrimary) {
Evan Millar66388be2009-05-28 15:41:07 -0700608 entry.primaryIcon = R.drawable.ic_default_number;
609 }
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800610 mEmailEntries.add(entry);
Evan Millar66388be2009-05-28 15:41:07 -0700611 // Build postal entries
612 } else if (mimetype.equals(CommonDataKinds.Postal.CONTENT_ITEM_TYPE)) {
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800613 entry.label = buildActionString(R.string.actionMap,
Evan Millar66388be2009-05-28 15:41:07 -0700614 ContactsUtils.getDisplayLabel(this, mimetype, type, label), true);
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800615 entry.data = data;
616 entry.maxLines = 4;
617 entry.intent = new Intent(Intent.ACTION_VIEW, uri);
618 entry.actionIcon = R.drawable.sym_action_map;
619 mPostalEntries.add(entry);
Evan Millar66388be2009-05-28 15:41:07 -0700620 // Build im entries
621 } else if (mimetype.equals(CommonDataKinds.Im.CONTENT_ITEM_TYPE)) {
622 String[] protocolStrings = getResources().getStringArray(
623 android.R.array.imProtocols);
624 Object protocolObj = ContactsUtils.decodeImProtocol(
625 aggCursor.getString(DATA_5_COLUMN));
Alex Kennberg87fc3172009-03-28 06:43:06 -0700626 String host = null;
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800627 if (protocolObj instanceof Number) {
628 int protocol = ((Number) protocolObj).intValue();
629 entry.label = buildActionString(R.string.actionChat,
630 protocolStrings[protocol], false);
Evan Millar66388be2009-05-28 15:41:07 -0700631 host = ContactsUtils.lookupProviderNameFromId(
632 protocol).toLowerCase();
633 if (protocol == CommonDataKinds.Im.PROTOCOL_GOOGLE_TALK
634 || protocol == CommonDataKinds.Im.PROTOCOL_MSN) {
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800635 entry.maxLabelLines = 2;
636 }
Alex Kennberg87fc3172009-03-28 06:43:06 -0700637 } else if (protocolObj != null) {
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800638 String providerName = (String) protocolObj;
639 entry.label = buildActionString(R.string.actionChat,
640 providerName, false);
641 host = providerName.toLowerCase();
642 }
643
644 // Only add the intent if there is a valid host
645 if (!TextUtils.isEmpty(host)) {
646 entry.intent = new Intent(Intent.ACTION_SENDTO,
647 constructImToUrl(host, data));
648 }
649 entry.data = data;
Evan Millar66388be2009-05-28 15:41:07 -0700650 //TODO(emillar) Do we want presence info?
651 /*if (!aggCursor.isNull(METHODS_STATUS_COLUMN)) {
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800652 entry.presenceIcon = Presence.getPresenceIconResourceId(
Evan Millar66388be2009-05-28 15:41:07 -0700653 aggCursor.getInt(METHODS_STATUS_COLUMN));
654 }*/
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800655 entry.actionIcon = android.R.drawable.sym_action_chat;
656 mImEntries.add(entry);
Evan Millar5c22c3b2009-05-29 11:37:54 -0700657 }
Evan Millar7e4accf2009-06-08 10:43:26 -0700658 // Set the name
659 } else if (mimetype.equals(CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)) {
660 String name = mCursor.getString(DATA_9_COLUMN);
661 if (TextUtils.isEmpty(name)) {
662 mNameView.setText(getText(android.R.string.unknownName));
663 } else {
664 mNameView.setText(name);
665 }
666 /*
667 if (mPhoneticNameView != null) {
668 String phoneticName = mCursor.getString(CONTACT_PHONETIC_NAME_COLUMN);
669 mPhoneticNameView.setText(phoneticName);
670 }
671 */
Evan Millar66388be2009-05-28 15:41:07 -0700672 // Build organization entries
673 } else if (mimetype.equals(CommonDataKinds.Organization.CONTENT_ITEM_TYPE)) {
Evan Millar66388be2009-05-28 15:41:07 -0700674 final String company = aggCursor.getString(DATA_3_COLUMN);
675 final String title = aggCursor.getString(DATA_4_COLUMN);
676 final boolean isPrimary = "1".equals(aggCursor.getString(DATA_5_COLUMN));
677
678 if (isPrimary) {
679 entry.primaryIcon = R.drawable.ic_default_number;
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800680 }
Evan Millar5c22c3b2009-05-29 11:37:54 -0700681
Evan Millar66388be2009-05-28 15:41:07 -0700682 // Don't crash if the data is bogus
683 if (TextUtils.isEmpty(company) && TextUtils.isEmpty(title)) {
684 Log.w(TAG, "empty data for contact method " + id);
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800685 continue;
686 }
Evan Millar5c22c3b2009-05-29 11:37:54 -0700687
Evan Millar66388be2009-05-28 15:41:07 -0700688 entry.data = title;
689 entry.actionIcon = R.drawable.sym_action_organization;
690 entry.label = company;
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800691
Evan Millar66388be2009-05-28 15:41:07 -0700692 mOrganizationEntries.add(entry);
693 // Build note entries
694 } else if (mimetype.equals(CommonDataKinds.Note.CONTENT_ITEM_TYPE)) {
695 entry.label = getString(R.string.label_notes);
696 entry.data = aggCursor.getString(DATA_1_COLUMN);
697 entry.id = 0;
698 entry.uri = null;
699 entry.intent = null;
700 entry.maxLines = 10;
701 entry.actionIcon = R.drawable.sym_note;
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800702
Evan Millar66388be2009-05-28 15:41:07 -0700703 if (TextUtils.isEmpty(entry.data)) {
704 Log.w(TAG, "empty data for contact method " + id);
Alex Kennberg87fc3172009-03-28 06:43:06 -0700705 continue;
706 }
Evan Millar5c22c3b2009-05-29 11:37:54 -0700707
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800708 mOtherEntries.add(entry);
Evan Millar66388be2009-05-28 15:41:07 -0700709 // Build the ringtone and send to voicemail entries
710 } else if (mimetype.equals(CommonDataKinds.CustomRingtone.CONTENT_ITEM_TYPE)) {
711 final Boolean sendToVoicemail = "1".equals(aggCursor.getString(DATA_1_COLUMN));
712 final String ringtoneStr = aggCursor.getString(DATA_2_COLUMN);
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800713
Evan Millar5c22c3b2009-05-29 11:37:54 -0700714 // TODO(emillar) we need to enforce uniqueness of custom ringtone entries on a
Evan Millar66388be2009-05-28 15:41:07 -0700715 // single aggregate. This could be done by checking against the reference ID stored
716 // in the aggregate.
Evan Millar5c22c3b2009-05-29 11:37:54 -0700717
Evan Millar66388be2009-05-28 15:41:07 -0700718 // Build the send directly to voice mail entry
719 if (sendToVoicemail) {
720 entry.label = getString(R.string.actionIncomingCall);
721 entry.data = getString(R.string.detailIncomingCallsGoToVoicemail);
722 entry.actionIcon = R.drawable.sym_send_to_voicemail;
723 mOtherEntries.add(entry);
724 } else if (!TextUtils.isEmpty(ringtoneStr)) {
725 // Get the URI
726 Uri ringtoneUri = Uri.parse(ringtoneStr);
727 if (ringtoneUri != null) {
728 Ringtone ringtone = RingtoneManager.getRingtone(this, ringtoneUri);
729 if (ringtone != null) {
730 entry.label = getString(R.string.label_ringtone);
731 entry.data = ringtone.getTitle(this);
732 entry.uri = ringtoneUri;
733 entry.actionIcon = R.drawable.sym_ringtone;
734 mOtherEntries.add(entry);
735 }
736 }
737 }
Evan Millar45e0ed32009-06-01 16:44:38 -0700738 // Load the photo
739 } else if (mimetype.equals(CommonDataKinds.Photo.CONTENT_ITEM_TYPE)) {
740 photoBitmap = ContactsUtils.loadContactPhoto(aggCursor, DATA_1_COLUMN, null);
Evan Millar66388be2009-05-28 15:41:07 -0700741 }
Evan Millar5c22c3b2009-05-29 11:37:54 -0700742
Evan Millar45e0ed32009-06-01 16:44:38 -0700743
Evan Millar66388be2009-05-28 15:41:07 -0700744 // TODO(emillar) Add group entries
745// // Build the group entries
746// final Uri groupsUri = Uri.withAppendedPath(mUri, GroupMembership.CONTENT_DIRECTORY);
747// Cursor groupCursor = mResolver.query(groupsUri, ContactsListActivity.GROUPS_PROJECTION,
748// null, null, Groups.DEFAULT_SORT_ORDER);
749// if (groupCursor != null) {
750// try {
751// StringBuilder sb = new StringBuilder();
752//
753// while (groupCursor.moveToNext()) {
754// String systemId = groupCursor.getString(
755// ContactsListActivity.GROUPS_COLUMN_INDEX_SYSTEM_ID);
756//
757// if (systemId != null || Groups.GROUP_MY_CONTACTS.equals(systemId)) {
758// continue;
759// }
760//
761// String name = groupCursor.getString(ContactsListActivity.GROUPS_COLUMN_INDEX_NAME);
762// if (!TextUtils.isEmpty(name)) {
763// if (sb.length() == 0) {
764// sb.append(name);
765// } else {
766// sb.append(getString(R.string.group_list, name));
767// }
768// }
769// }
770//
771// if (sb.length() > 0) {
772// ViewEntry entry = new ViewEntry();
773// entry.kind = ContactEntryAdapter.Entry.KIND_GROUP;
774// entry.label = getString(R.string.label_groups);
775// entry.data = sb.toString();
776// entry.intent = new Intent(Intent.ACTION_EDIT, mUri);
777//
778// // TODO: Add an icon for the groups item.
779//
780// mGroupEntries.add(entry);
781// }
782// } finally {
783// groupCursor.close();
784// }
785// }
Evan Millar5c22c3b2009-05-29 11:37:54 -0700786
Evan Millar66388be2009-05-28 15:41:07 -0700787 }
Evan Millar45e0ed32009-06-01 16:44:38 -0700788
789 if (photoBitmap == null) {
790 photoBitmap = ContactsUtils.loadPlaceholderPhoto(mNoPhotoResource, this, null);
791 }
792 mPhotoView.setImageBitmap(photoBitmap);
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800793 }
794 }
795
Evan Millar66388be2009-05-28 15:41:07 -0700796
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800797 String buildActionString(int actionResId, CharSequence type, boolean lowerCase) {
Jeff Hamilton8350e5b2009-03-24 21:01:34 -0700798 // If there is no type just display an empty string
799 if (type == null) {
800 type = "";
801 }
802
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800803 if (lowerCase) {
804 return getString(actionResId, type.toString().toLowerCase());
805 } else {
806 return getString(actionResId, type.toString());
807 }
808 }
Evan Millar5c22c3b2009-05-29 11:37:54 -0700809
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800810 /**
811 * A basic structure with the data for a contact entry in the list.
812 */
813 final static class ViewEntry extends ContactEntryAdapter.Entry {
814 public int primaryIcon = -1;
815 public Intent intent;
816 public Intent auxIntent = null;
817 public int presenceIcon = -1;
818 public int actionIcon = -1;
819 public int maxLabelLines = 1;
820 }
821
822 private static final class ViewAdapter extends ContactEntryAdapter<ViewEntry> {
823 /** Cache of the children views of a row */
824 static class ViewCache {
825 public TextView label;
826 public TextView data;
827 public ImageView actionIcon;
828 public ImageView presenceIcon;
Evan Millar5c22c3b2009-05-29 11:37:54 -0700829
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800830 // Need to keep track of this too
831 ViewEntry entry;
832 }
Evan Millar5c22c3b2009-05-29 11:37:54 -0700833
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800834 ViewAdapter(Context context, ArrayList<ArrayList<ViewEntry>> sections) {
835 super(context, sections, SHOW_SEPARATORS);
836 }
837
838 @Override
839 public View getView(int position, View convertView, ViewGroup parent) {
Evan Millar5c22c3b2009-05-29 11:37:54 -0700840 ViewEntry entry = getEntry(mSections, position, false);
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800841 View v;
842
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800843 ViewCache views;
844
845 // Check to see if we can reuse convertView
846 if (convertView != null) {
847 v = convertView;
848 views = (ViewCache) v.getTag();
849 } else {
850 // Create a new view if needed
851 v = mInflater.inflate(R.layout.list_item_text_icons, parent, false);
852
853 // Cache the children
854 views = new ViewCache();
855 views.label = (TextView) v.findViewById(android.R.id.text1);
856 views.data = (TextView) v.findViewById(android.R.id.text2);
857 views.actionIcon = (ImageView) v.findViewById(R.id.icon1);
858 views.presenceIcon = (ImageView) v.findViewById(R.id.icon2);
859 v.setTag(views);
860 }
861
862 // Update the entry in the view cache
863 views.entry = entry;
864
865 // Bind the data to the view
866 bindView(v, entry);
867 return v;
868 }
869
870 @Override
871 protected View newView(int position, ViewGroup parent) {
872 // getView() handles this
873 throw new UnsupportedOperationException();
874 }
875
876 @Override
877 protected void bindView(View view, ViewEntry entry) {
878 final Resources resources = mContext.getResources();
879 ViewCache views = (ViewCache) view.getTag();
880
881 // Set the label
882 TextView label = views.label;
883 setMaxLines(label, entry.maxLabelLines);
884 label.setText(entry.label);
885
886 // Set the data
887 TextView data = views.data;
888 if (data != null) {
889 data.setText(entry.data);
890 setMaxLines(data, entry.maxLines);
891 }
892
893 // Set the action icon
894 ImageView action = views.actionIcon;
895 if (entry.actionIcon != -1) {
896 action.setImageDrawable(resources.getDrawable(entry.actionIcon));
897 action.setVisibility(View.VISIBLE);
898 } else {
899 // Things should still line up as if there was an icon, so make it invisible
900 action.setVisibility(View.INVISIBLE);
901 }
902
903 // Set the presence icon
904 Drawable presenceIcon = null;
905 if (entry.primaryIcon != -1) {
906 presenceIcon = resources.getDrawable(entry.primaryIcon);
907 } else if (entry.presenceIcon != -1) {
908 presenceIcon = resources.getDrawable(entry.presenceIcon);
909 }
910
911 ImageView presence = views.presenceIcon;
912 if (presenceIcon != null) {
913 presence.setImageDrawable(presenceIcon);
914 presence.setVisibility(View.VISIBLE);
915 } else {
916 presence.setVisibility(View.GONE);
917 }
918 }
919
920 private void setMaxLines(TextView textView, int maxLines) {
921 if (maxLines == 1) {
922 textView.setSingleLine(true);
923 textView.setEllipsize(TextUtils.TruncateAt.END);
924 } else {
925 textView.setSingleLine(false);
926 textView.setMaxLines(maxLines);
927 textView.setEllipsize(null);
928 }
929 }
930 }
931}