blob: a40d8b4b484f0a1d68d455666d46f4ac582fe249 [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;
25import static com.android.contacts.ContactEntryAdapter.DATA_1_COLUMN;
26import static com.android.contacts.ContactEntryAdapter.DATA_2_COLUMN;
27import static com.android.contacts.ContactEntryAdapter.DATA_3_COLUMN;
28import static com.android.contacts.ContactEntryAdapter.DATA_4_COLUMN;
29import static com.android.contacts.ContactEntryAdapter.DATA_5_COLUMN;
30import static com.android.contacts.ContactEntryAdapter.DATA_6_COLUMN;
31import static com.android.contacts.ContactEntryAdapter.DATA_7_COLUMN;
32import static com.android.contacts.ContactEntryAdapter.DATA_8_COLUMN;
33import static com.android.contacts.ContactEntryAdapter.DATA_9_COLUMN;
34import static com.android.contacts.ContactEntryAdapter.DATA_10_COLUMN;
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -080035
36import com.android.internal.telephony.ITelephony;
37
38import android.app.AlertDialog;
39import android.app.Dialog;
40import android.app.ListActivity;
41import android.content.ActivityNotFoundException;
42import android.content.ContentResolver;
43import android.content.ContentUris;
44import android.content.ContentValues;
45import android.content.Context;
46import android.content.DialogInterface;
47import android.content.Intent;
48import android.content.pm.PackageManager;
49import android.content.pm.ResolveInfo;
50import android.content.res.Resources;
51import android.database.ContentObserver;
52import android.database.Cursor;
Evan Millar66388be2009-05-28 15:41:07 -070053import android.graphics.BitmapFactory;
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -080054import android.graphics.drawable.Drawable;
55import android.media.Ringtone;
56import android.media.RingtoneManager;
57import android.net.Uri;
58import android.os.Bundle;
59import android.os.Handler;
60import android.os.RemoteException;
61import android.os.ServiceManager;
62import android.os.SystemClock;
Evan Millar66388be2009-05-28 15:41:07 -070063import android.provider.ContactsContract.CommonDataKinds;
64import android.provider.ContactsContract.Aggregates;
65import android.provider.ContactsContract.Data;
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -080066import android.provider.Im;
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -080067import android.text.TextUtils;
68import android.util.Log;
69import android.view.ContextMenu;
70import android.view.KeyEvent;
71import android.view.Menu;
72import android.view.MenuItem;
73import android.view.View;
74import android.view.ViewGroup;
75import android.view.ContextMenu.ContextMenuInfo;
76import android.widget.AdapterView;
77import android.widget.CheckBox;
78import android.widget.ImageView;
79import android.widget.ListView;
80import android.widget.TextView;
81import android.widget.Toast;
82
83import java.util.ArrayList;
84import java.util.List;
85
86/**
87 * Displays the details of a specific contact.
88 */
Evan Millar5c22c3b2009-05-29 11:37:54 -070089public class ViewContactActivity extends ListActivity
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -080090 implements View.OnCreateContextMenuListener, View.OnClickListener,
91 DialogInterface.OnClickListener {
92 private static final String TAG = "ViewContact";
93 private static final String SHOW_BARCODE_INTENT = "com.google.zxing.client.android.ENCODE";
94
95 private static final boolean SHOW_SEPARATORS = false;
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -080096
97 private static final int DIALOG_CONFIRM_DELETE = 1;
98
99 public static final int MENU_ITEM_DELETE = 1;
100 public static final int MENU_ITEM_MAKE_DEFAULT = 2;
101 public static final int MENU_ITEM_SHOW_BARCODE = 3;
102
103 private Uri mUri;
104 private ContentResolver mResolver;
105 private ViewAdapter mAdapter;
106 private int mNumPhoneNumbers = 0;
107
108 /* package */ ArrayList<ViewEntry> mPhoneEntries = new ArrayList<ViewEntry>();
109 /* package */ ArrayList<ViewEntry> mSmsEntries = new ArrayList<ViewEntry>();
110 /* package */ ArrayList<ViewEntry> mEmailEntries = new ArrayList<ViewEntry>();
111 /* package */ ArrayList<ViewEntry> mPostalEntries = new ArrayList<ViewEntry>();
112 /* package */ ArrayList<ViewEntry> mImEntries = new ArrayList<ViewEntry>();
113 /* package */ ArrayList<ViewEntry> mOrganizationEntries = new ArrayList<ViewEntry>();
The Android Open Source Projectcac191e2009-03-18 22:20:27 -0700114 /* package */ ArrayList<ViewEntry> mGroupEntries = new ArrayList<ViewEntry>();
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800115 /* package */ ArrayList<ViewEntry> mOtherEntries = new ArrayList<ViewEntry>();
116 /* package */ ArrayList<ArrayList<ViewEntry>> mSections = new ArrayList<ArrayList<ViewEntry>>();
117
118 private Cursor mCursor;
119 private boolean mObserverRegistered;
Evan Millar5c22c3b2009-05-29 11:37:54 -0700120
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800121 private ContentObserver mObserver = new ContentObserver(new Handler()) {
122 @Override
123 public boolean deliverSelfNotifications() {
124 return true;
125 }
126
127 @Override
128 public void onChange(boolean selfChange) {
129 if (mCursor != null && !mCursor.isClosed()){
130 dataChanged();
131 }
132 }
133 };
134
135 public void onClick(DialogInterface dialog, int which) {
136 if (mCursor != null) {
137 if (mObserverRegistered) {
138 mCursor.unregisterContentObserver(mObserver);
139 mObserverRegistered = false;
140 }
141 mCursor.close();
142 mCursor = null;
143 }
144 getContentResolver().delete(mUri, null, null);
145 finish();
146 }
147
148 public void onClick(View view) {
149 if (!mObserverRegistered) {
150 return;
151 }
152 switch (view.getId()) {
153 case R.id.star: {
Evan Millar66388be2009-05-28 15:41:07 -0700154 int oldStarredState = mCursor.getInt(AGGREGATE_STARRED_COLUMN);
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800155 ContentValues values = new ContentValues(1);
Evan Millar66388be2009-05-28 15:41:07 -0700156 values.put(Aggregates.STARRED, oldStarredState == 1 ? 0 : 1);
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800157 getContentResolver().update(mUri, values, null, null);
158 break;
159 }
160 }
161 }
162
163 private TextView mNameView;
164 private TextView mPhoneticNameView; // may be null in some locales
165 private ImageView mPhotoView;
166 private int mNoPhotoResource;
167 private CheckBox mStarView;
168 private boolean mShowSmsLinksForAllPhones;
169
170 @Override
171 protected void onCreate(Bundle icicle) {
172 super.onCreate(icicle);
173
174 setContentView(R.layout.view_contact);
175 getListView().setOnCreateContextMenuListener(this);
176
177 mNameView = (TextView) findViewById(R.id.name);
178 mPhoneticNameView = (TextView) findViewById(R.id.phonetic_name);
179 mPhotoView = (ImageView) findViewById(R.id.photo);
180 mStarView = (CheckBox) findViewById(R.id.star);
181 mStarView.setOnClickListener(this);
182
183 // Set the photo with a random "no contact" image
184 long now = SystemClock.elapsedRealtime();
185 int num = (int) now & 0xf;
186 if (num < 9) {
187 // Leaning in from right, common
188 mNoPhotoResource = R.drawable.ic_contact_picture;
189 } else if (num < 14) {
190 // Leaning in from left uncommon
191 mNoPhotoResource = R.drawable.ic_contact_picture_2;
192 } else {
193 // Coming in from the top, rare
194 mNoPhotoResource = R.drawable.ic_contact_picture_3;
195 }
196
Evan Millar5c22c3b2009-05-29 11:37:54 -0700197 mUri = Uri.withAppendedPath(getIntent().getData(), "data");
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800198 mResolver = getContentResolver();
199
200 // Build the list of sections. The order they're added to mSections dictates the
201 // order they are displayed in the list.
202 mSections.add(mPhoneEntries);
203 mSections.add(mSmsEntries);
204 mSections.add(mEmailEntries);
205 mSections.add(mImEntries);
206 mSections.add(mPostalEntries);
207 mSections.add(mOrganizationEntries);
The Android Open Source Projectcac191e2009-03-18 22:20:27 -0700208 mSections.add(mGroupEntries);
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800209 mSections.add(mOtherEntries);
210
211 //TODO Read this value from a preference
212 mShowSmsLinksForAllPhones = true;
213
Evan Millar66388be2009-05-28 15:41:07 -0700214 mCursor = mResolver.query(mUri, AGGREGATE_PROJECTION, null, null, null);
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800215 }
216
217 @Override
218 protected void onResume() {
219 super.onResume();
220 mObserverRegistered = true;
221 mCursor.registerContentObserver(mObserver);
222 dataChanged();
223 }
224
225 @Override
226 protected void onPause() {
227 super.onPause();
228 if (mCursor != null) {
229 if (mObserverRegistered) {
230 mObserverRegistered = false;
231 mCursor.unregisterContentObserver(mObserver);
232 }
233 mCursor.deactivate();
234 }
235 }
236
237 @Override
238 protected void onDestroy() {
239 super.onDestroy();
240
241 if (mCursor != null) {
242 if (mObserverRegistered) {
243 mCursor.unregisterContentObserver(mObserver);
244 mObserverRegistered = false;
245 }
246 mCursor.close();
247 }
248 }
249
250 @Override
251 protected Dialog onCreateDialog(int id) {
252 switch (id) {
253 case DIALOG_CONFIRM_DELETE:
254 return new AlertDialog.Builder(this)
255 .setTitle(R.string.deleteConfirmation_title)
256 .setIcon(android.R.drawable.ic_dialog_alert)
257 .setMessage(R.string.deleteConfirmation)
258 .setNegativeButton(android.R.string.cancel, null)
259 .setPositiveButton(android.R.string.ok, this)
260 .setCancelable(false)
261 .create();
262 }
263 return null;
264 }
265
266 private void dataChanged() {
267 mCursor.requery();
268 if (mCursor.moveToFirst()) {
269 // Set the name
Evan Millar66388be2009-05-28 15:41:07 -0700270 String name = mCursor.getString(AGGREGATE_DISPLAY_NAME_COLUMN);
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800271 if (TextUtils.isEmpty(name)) {
272 mNameView.setText(getText(android.R.string.unknownName));
273 } else {
274 mNameView.setText(name);
275 }
276
Evan Millar66388be2009-05-28 15:41:07 -0700277 // TODO(emillar) Bring this back.
278 /*if (mPhoneticNameView != null) {
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800279 String phoneticName = mCursor.getString(CONTACT_PHONETIC_NAME_COLUMN);
280 mPhoneticNameView.setText(phoneticName);
281 }
282
283 // Load the photo
284 mPhotoView.setImageBitmap(People.loadContactPhoto(this, mUri, mNoPhotoResource,
Evan Millar66388be2009-05-28 15:41:07 -0700285 null)); */
Evan Millar5c22c3b2009-05-29 11:37:54 -0700286
Evan Millar66388be2009-05-28 15:41:07 -0700287 mPhotoView.setImageBitmap(BitmapFactory.decodeResource(getResources(),
288 mNoPhotoResource, null));
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800289
290 // Set the star
Evan Millar66388be2009-05-28 15:41:07 -0700291 mStarView.setChecked(mCursor.getInt(AGGREGATE_STARRED_COLUMN) == 1 ? true : false);
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800292
293 // Build up the contact entries
294 buildEntries(mCursor);
295 if (mAdapter == null) {
296 mAdapter = new ViewAdapter(this, mSections);
297 setListAdapter(mAdapter);
298 } else {
299 mAdapter.setSections(mSections, SHOW_SEPARATORS);
300 }
301 } else {
302 Toast.makeText(this, R.string.invalidContactMessage, Toast.LENGTH_SHORT).show();
303 Log.e(TAG, "invalid contact uri: " + mUri);
304 finish();
305 }
306 }
307
308 @Override
309 public boolean onCreateOptionsMenu(Menu menu) {
310 menu.add(0, 0, 0, R.string.menu_editContact)
311 .setIcon(android.R.drawable.ic_menu_edit)
312 .setIntent(new Intent(Intent.ACTION_EDIT, mUri))
313 .setAlphabeticShortcut('e');
314 menu.add(0, MENU_ITEM_DELETE, 0, R.string.menu_deleteContact)
315 .setIcon(android.R.drawable.ic_menu_delete);
316
317 return true;
318 }
319
320 @Override
321 public boolean onPrepareOptionsMenu(Menu menu) {
322 super.onPrepareOptionsMenu(menu);
323 // Perform this check each time the menu is about to be shown, because the Barcode Scanner
324 // could be installed or uninstalled at any time.
325 if (isBarcodeScannerInstalled()) {
326 if (menu.findItem(MENU_ITEM_SHOW_BARCODE) == null) {
327 menu.add(0, MENU_ITEM_SHOW_BARCODE, 0, R.string.menu_showBarcode)
328 .setIcon(R.drawable.ic_menu_show_barcode);
329 }
330 } else {
331 menu.removeItem(MENU_ITEM_SHOW_BARCODE);
332 }
333 return true;
334 }
335
336 private boolean isBarcodeScannerInstalled() {
337 final Intent intent = new Intent(SHOW_BARCODE_INTENT);
338 ResolveInfo ri = getPackageManager().resolveActivity(intent,
339 PackageManager.MATCH_DEFAULT_ONLY);
340 return ri != null;
341 }
342
343 @Override
344 public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfo) {
345 AdapterView.AdapterContextMenuInfo info;
346 try {
347 info = (AdapterView.AdapterContextMenuInfo) menuInfo;
348 } catch (ClassCastException e) {
349 Log.e(TAG, "bad menuInfo", e);
350 return;
351 }
352
353 // This can be null sometimes, don't crash...
354 if (info == null) {
355 Log.e(TAG, "bad menuInfo");
356 return;
357 }
Evan Millar5c22c3b2009-05-29 11:37:54 -0700358
Evan Millar66388be2009-05-28 15:41:07 -0700359 // TODO(emillar) Bring this back.
360 /*ViewEntry entry = ContactEntryAdapter.getEntry(mSections, info.position, SHOW_SEPARATORS);
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800361 switch (entry.kind) {
362 case Contacts.KIND_PHONE: {
363 menu.add(0, 0, 0, R.string.menu_call).setIntent(entry.intent);
364 menu.add(0, 0, 0, R.string.menu_sendSMS).setIntent(entry.auxIntent);
365 if (entry.primaryIcon == -1) {
366 menu.add(0, MENU_ITEM_MAKE_DEFAULT, 0, R.string.menu_makeDefaultNumber);
367 }
368 break;
369 }
370
371 case Contacts.KIND_EMAIL: {
372 menu.add(0, 0, 0, R.string.menu_sendEmail).setIntent(entry.intent);
373 break;
374 }
375
376 case Contacts.KIND_POSTAL: {
377 menu.add(0, 0, 0, R.string.menu_viewAddress).setIntent(entry.intent);
378 break;
379 }
Evan Millar5c22c3b2009-05-29 11:37:54 -0700380
Alex Kennbergbd7e1232009-03-27 10:28:23 -0700381 case ContactEntryAdapter.Entry.KIND_GROUP: {
382 menu.add(0, 0, 0, R.string.menu_viewGroup).setIntent(entry.intent);
383 break;
384 }
Evan Millar66388be2009-05-28 15:41:07 -0700385 } */
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800386 }
387
388 @Override
389 public boolean onOptionsItemSelected(MenuItem item) {
390 switch (item.getItemId()) {
391 case MENU_ITEM_DELETE: {
392 // Get confirmation
393 showDialog(DIALOG_CONFIRM_DELETE);
394 return true;
395 }
Evan Millar5c22c3b2009-05-29 11:37:54 -0700396
Evan Millar66388be2009-05-28 15:41:07 -0700397 // TODO(emillar) Bring this back.
398 /*case MENU_ITEM_SHOW_BARCODE:
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800399 if (mCursor.moveToFirst()) {
400 Intent intent = new Intent(SHOW_BARCODE_INTENT);
401 intent.putExtra("ENCODE_TYPE", "CONTACT_TYPE");
402 Bundle bundle = new Bundle();
Evan Millar66388be2009-05-28 15:41:07 -0700403 String name = mCursor.getString(AGGREGATE_DISPLAY_NAME_COLUMN);
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800404 if (!TextUtils.isEmpty(name)) {
Jeffrey Sharkey715b32a2009-03-27 18:56:05 -0700405 // Correctly handle when section headers are hidden
406 int sepAdjust = SHOW_SEPARATORS ? 1 : 0;
Evan Millar5c22c3b2009-05-29 11:37:54 -0700407
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800408 bundle.putString(Contacts.Intents.Insert.NAME, name);
409 // The 0th ViewEntry in each ArrayList below is a separator item
Jeffrey Sharkey715b32a2009-03-27 18:56:05 -0700410 int entriesToAdd = Math.min(mPhoneEntries.size() - sepAdjust, PHONE_KEYS.length);
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800411 for (int x = 0; x < entriesToAdd; x++) {
Jeffrey Sharkey715b32a2009-03-27 18:56:05 -0700412 ViewEntry entry = mPhoneEntries.get(x + sepAdjust);
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800413 bundle.putString(PHONE_KEYS[x], entry.data);
414 }
Jeffrey Sharkey715b32a2009-03-27 18:56:05 -0700415 entriesToAdd = Math.min(mEmailEntries.size() - sepAdjust, EMAIL_KEYS.length);
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800416 for (int x = 0; x < entriesToAdd; x++) {
Jeffrey Sharkey715b32a2009-03-27 18:56:05 -0700417 ViewEntry entry = mEmailEntries.get(x + sepAdjust);
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800418 bundle.putString(EMAIL_KEYS[x], entry.data);
419 }
Jeffrey Sharkey715b32a2009-03-27 18:56:05 -0700420 if (mPostalEntries.size() >= 1 + sepAdjust) {
421 ViewEntry entry = mPostalEntries.get(sepAdjust);
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800422 bundle.putString(Contacts.Intents.Insert.POSTAL, entry.data);
423 }
424 intent.putExtra("ENCODE_DATA", bundle);
425 try {
426 startActivity(intent);
427 } catch (ActivityNotFoundException e) {
428 // The check in onPrepareOptionsMenu() should make this impossible, but
429 // for safety I'm catching the exception rather than crashing. Ideally
430 // I'd call Menu.removeItem() here too, but I don't see a way to get
431 // the options menu.
432 Log.e(TAG, "Show barcode menu item was clicked but Barcode Scanner " +
433 "was not installed.");
434 }
435 return true;
436 }
437 }
Evan Millar66388be2009-05-28 15:41:07 -0700438 break; */
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800439 }
440 return super.onOptionsItemSelected(item);
441 }
Evan Millar5c22c3b2009-05-29 11:37:54 -0700442
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800443 @Override
444 public boolean onContextItemSelected(MenuItem item) {
445 switch (item.getItemId()) {
446 case MENU_ITEM_MAKE_DEFAULT: {
Evan Millar66388be2009-05-28 15:41:07 -0700447 //TODO(emillar) Bring this back.
448 /*AdapterView.AdapterContextMenuInfo info;
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800449 try {
450 info = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo();
451 } catch (ClassCastException e) {
452 Log.e(TAG, "bad menuInfo", e);
453 break;
454 }
455
456 ViewEntry entry = ContactEntryAdapter.getEntry(mSections, info.position,
457 SHOW_SEPARATORS);
458 ContentValues values = new ContentValues(1);
Evan Millar66388be2009-05-28 15:41:07 -0700459 values.put(PRIMARY_PHONE_ID, entry.id);
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800460 getContentResolver().update(mUri, values, null, null);
Evan Millar66388be2009-05-28 15:41:07 -0700461 dataChanged();*/
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800462 return true;
463 }
464 }
465 return super.onContextItemSelected(item);
466 }
467
468 @Override
469 public boolean onKeyDown(int keyCode, KeyEvent event) {
470 switch (keyCode) {
471 case KeyEvent.KEYCODE_CALL: {
472 try {
473 ITelephony phone = ITelephony.Stub.asInterface(
474 ServiceManager.checkService("phone"));
475 if (phone != null && !phone.isIdle()) {
476 // Skip out and let the key be handled at a higher level
477 break;
478 }
479 } catch (RemoteException re) {
480 // Fall through and try to call the contact
481 }
482
483 int index = getListView().getSelectedItemPosition();
484 if (index != -1) {
485 ViewEntry entry = ViewAdapter.getEntry(mSections, index, SHOW_SEPARATORS);
Evan Millar66388be2009-05-28 15:41:07 -0700486 if (entry.intent.getAction() == Intent.ACTION_CALL_PRIVILEGED) {
487 startActivity(entry.intent);
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800488 }
489 } else if (mNumPhoneNumbers != 0) {
490 // There isn't anything selected, call the default number
491 Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED, mUri);
492 startActivity(intent);
493 }
494 return true;
495 }
496
497 case KeyEvent.KEYCODE_DEL: {
498 showDialog(DIALOG_CONFIRM_DELETE);
499 return true;
500 }
501 }
502
503 return super.onKeyDown(keyCode, event);
504 }
505
506 @Override
507 protected void onListItemClick(ListView l, View v, int position, long id) {
508 ViewEntry entry = ViewAdapter.getEntry(mSections, position, SHOW_SEPARATORS);
509 if (entry != null) {
510 Intent intent = entry.intent;
511 if (intent != null) {
512 try {
513 startActivity(intent);
514 } catch (ActivityNotFoundException e) {
515 Log.e(TAG, "No activity found for intent: " + intent);
516 signalError();
517 }
518 } else {
519 signalError();
520 }
521 } else {
522 signalError();
523 }
524 }
525
526 /**
527 * Signal an error to the user via a beep, or some other method.
528 */
529 private void signalError() {
530 //TODO: implement this when we have the sonification APIs
531 }
532
533 /**
534 * Build separator entries for all of the sections.
535 */
536 private void buildSeparators() {
537 ViewEntry separator;
Evan Millar5c22c3b2009-05-29 11:37:54 -0700538
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800539 separator = new ViewEntry();
540 separator.kind = ViewEntry.KIND_SEPARATOR;
541 separator.data = getString(R.string.listSeparatorCallNumber);
542 mPhoneEntries.add(separator);
543
544 separator = new ViewEntry();
545 separator.kind = ViewEntry.KIND_SEPARATOR;
546 separator.data = getString(R.string.listSeparatorSendSmsMms);
547 mSmsEntries.add(separator);
548
549 separator = new ViewEntry();
550 separator.kind = ViewEntry.KIND_SEPARATOR;
551 separator.data = getString(R.string.listSeparatorSendEmail);
552 mEmailEntries.add(separator);
553
554 separator = new ViewEntry();
555 separator.kind = ViewEntry.KIND_SEPARATOR;
556 separator.data = getString(R.string.listSeparatorSendIm);
557 mImEntries.add(separator);
558
559 separator = new ViewEntry();
560 separator.kind = ViewEntry.KIND_SEPARATOR;
561 separator.data = getString(R.string.listSeparatorMapAddress);
562 mPostalEntries.add(separator);
563
564 separator = new ViewEntry();
565 separator.kind = ViewEntry.KIND_SEPARATOR;
566 separator.data = getString(R.string.listSeparatorOrganizations);
567 mOrganizationEntries.add(separator);
568
569 separator = new ViewEntry();
570 separator.kind = ViewEntry.KIND_SEPARATOR;
The Android Open Source Projectcac191e2009-03-18 22:20:27 -0700571 separator.data = getString(R.string.listSeparatorGroups);
572 mGroupEntries.add(separator);
573
574 separator = new ViewEntry();
575 separator.kind = ViewEntry.KIND_SEPARATOR;
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800576 separator.data = getString(R.string.listSeparatorOtherInformation);
577 mOtherEntries.add(separator);
578 }
579
580 private Uri constructImToUrl(String host, String data) {
581 // don't encode the url, because the Activity Manager can't find using the encoded url
582 StringBuilder buf = new StringBuilder("imto://");
583 buf.append(host);
584 buf.append('/');
585 buf.append(data);
586 return Uri.parse(buf.toString());
587 }
588
589 /**
590 * Build up the entries to display on the screen.
Evan Millar5c22c3b2009-05-29 11:37:54 -0700591 *
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800592 * @param personCursor the URI for the contact being displayed
593 */
Evan Millar66388be2009-05-28 15:41:07 -0700594 private final void buildEntries(Cursor aggCursor) {
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800595 // Clear out the old entries
596 final int numSections = mSections.size();
597 for (int i = 0; i < numSections; i++) {
598 mSections.get(i).clear();
599 }
600
601 if (SHOW_SEPARATORS) {
602 buildSeparators();
603 }
Evan Millar5c22c3b2009-05-29 11:37:54 -0700604
Evan Millar66388be2009-05-28 15:41:07 -0700605 // Build up method entries
606 if (mUri != null) {
607 while (aggCursor.moveToNext()) {
608 final String mimetype = aggCursor.getString(DATA_MIMETYPE_COLUMN);
609 final String pkg = aggCursor.getString(DATA_PACKAGE_COLUMN);
Evan Millar5c22c3b2009-05-29 11:37:54 -0700610
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800611 ViewEntry entry = new ViewEntry();
Evan Millar66388be2009-05-28 15:41:07 -0700612
613 final long id = aggCursor.getLong(DATA_ID_COLUMN);
614 final Uri uri = ContentUris.withAppendedId(Data.CONTENT_URI, id);
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800615 entry.id = id;
616 entry.uri = uri;
Evan Millar66388be2009-05-28 15:41:07 -0700617 //TODO(emillar) Can we get rid of kind?
618 entry.kind = 0;
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800619
Evan Millar66388be2009-05-28 15:41:07 -0700620 if (mimetype.equals(CommonDataKinds.Phone.CONTENT_ITEM_TYPE)
Evan Millar5c22c3b2009-05-29 11:37:54 -0700621 || mimetype.equals(CommonDataKinds.Email.CONTENT_ITEM_TYPE)
622 || mimetype.equals(CommonDataKinds.Postal.CONTENT_ITEM_TYPE)
Evan Millar66388be2009-05-28 15:41:07 -0700623 || mimetype.equals(CommonDataKinds.Im.CONTENT_ITEM_TYPE)) {
624 final int type = aggCursor.getInt(DATA_1_COLUMN);
625 final String label = aggCursor.getString(DATA_2_COLUMN);
626 final String data = aggCursor.getString(DATA_3_COLUMN);
627 final boolean isPrimary = "1".equals(aggCursor.getString(DATA_4_COLUMN));
628
629 // Don't crash if the data is bogus
630 if (TextUtils.isEmpty(data)) {
631 Log.w(TAG, "empty data for contact method " + id);
632 continue;
633 }
634
635 // Build phone entries
636 if (mimetype.equals(CommonDataKinds.Phone.CONTENT_ITEM_TYPE)) {
637 mNumPhoneNumbers++;
Evan Millar5c22c3b2009-05-29 11:37:54 -0700638
Evan Millar66388be2009-05-28 15:41:07 -0700639 final CharSequence displayLabel = ContactsUtils.getDisplayLabel(
640 this, mimetype, type, label);
641 entry.label = buildActionString(R.string.actionCall, displayLabel, true);
642 entry.data = data;
643 entry.intent = new Intent(Intent.ACTION_CALL_PRIVILEGED, entry.uri);
644 entry.auxIntent = new Intent(Intent.ACTION_SENDTO,
645 Uri.fromParts("sms", data, null));
646 if (isPrimary) {
647 entry.primaryIcon = R.drawable.ic_default_number;
648 }
649 entry.actionIcon = android.R.drawable.sym_action_call;
650 mPhoneEntries.add(entry);
651
Evan Millar5c22c3b2009-05-29 11:37:54 -0700652 if (type == CommonDataKinds.Phone.TYPE_MOBILE
Evan Millar66388be2009-05-28 15:41:07 -0700653 || mShowSmsLinksForAllPhones) {
654 // Add an SMS entry
655 ViewEntry smsEntry = new ViewEntry();
656 smsEntry.label = buildActionString(
657 R.string.actionText, displayLabel, true);
658 smsEntry.data = data;
659 smsEntry.id = id;
660 smsEntry.uri = uri;
661 smsEntry.intent = entry.auxIntent;
662 smsEntry.kind = ViewEntry.KIND_SMS;
663 smsEntry.actionIcon = R.drawable.sym_action_sms;
664 mSmsEntries.add(smsEntry);
665 }
666 // Build email entries
667 } else if (mimetype.equals(CommonDataKinds.Email.CONTENT_ITEM_TYPE)) {
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800668 entry.label = buildActionString(R.string.actionEmail,
Evan Millar66388be2009-05-28 15:41:07 -0700669 ContactsUtils.getDisplayLabel(this, mimetype, type, label), true);
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800670 entry.data = data;
671 entry.intent = new Intent(Intent.ACTION_SENDTO,
672 Uri.fromParts("mailto", data, null));
673 entry.actionIcon = android.R.drawable.sym_action_email;
Evan Millar66388be2009-05-28 15:41:07 -0700674 if (isPrimary) {
675 entry.primaryIcon = R.drawable.ic_default_number;
676 }
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800677 mEmailEntries.add(entry);
Evan Millar66388be2009-05-28 15:41:07 -0700678 // Build postal entries
679 } else if (mimetype.equals(CommonDataKinds.Postal.CONTENT_ITEM_TYPE)) {
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800680 entry.label = buildActionString(R.string.actionMap,
Evan Millar66388be2009-05-28 15:41:07 -0700681 ContactsUtils.getDisplayLabel(this, mimetype, type, label), true);
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800682 entry.data = data;
683 entry.maxLines = 4;
684 entry.intent = new Intent(Intent.ACTION_VIEW, uri);
685 entry.actionIcon = R.drawable.sym_action_map;
686 mPostalEntries.add(entry);
Evan Millar66388be2009-05-28 15:41:07 -0700687 // Build im entries
688 } else if (mimetype.equals(CommonDataKinds.Im.CONTENT_ITEM_TYPE)) {
689 String[] protocolStrings = getResources().getStringArray(
690 android.R.array.imProtocols);
691 Object protocolObj = ContactsUtils.decodeImProtocol(
692 aggCursor.getString(DATA_5_COLUMN));
Alex Kennberg87fc3172009-03-28 06:43:06 -0700693 String host = null;
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800694 if (protocolObj instanceof Number) {
695 int protocol = ((Number) protocolObj).intValue();
696 entry.label = buildActionString(R.string.actionChat,
697 protocolStrings[protocol], false);
Evan Millar66388be2009-05-28 15:41:07 -0700698 host = ContactsUtils.lookupProviderNameFromId(
699 protocol).toLowerCase();
700 if (protocol == CommonDataKinds.Im.PROTOCOL_GOOGLE_TALK
701 || protocol == CommonDataKinds.Im.PROTOCOL_MSN) {
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800702 entry.maxLabelLines = 2;
703 }
Alex Kennberg87fc3172009-03-28 06:43:06 -0700704 } else if (protocolObj != null) {
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800705 String providerName = (String) protocolObj;
706 entry.label = buildActionString(R.string.actionChat,
707 providerName, false);
708 host = providerName.toLowerCase();
709 }
710
711 // Only add the intent if there is a valid host
712 if (!TextUtils.isEmpty(host)) {
713 entry.intent = new Intent(Intent.ACTION_SENDTO,
714 constructImToUrl(host, data));
715 }
716 entry.data = data;
Evan Millar66388be2009-05-28 15:41:07 -0700717 //TODO(emillar) Do we want presence info?
718 /*if (!aggCursor.isNull(METHODS_STATUS_COLUMN)) {
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800719 entry.presenceIcon = Presence.getPresenceIconResourceId(
Evan Millar66388be2009-05-28 15:41:07 -0700720 aggCursor.getInt(METHODS_STATUS_COLUMN));
721 }*/
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800722 entry.actionIcon = android.R.drawable.sym_action_chat;
723 mImEntries.add(entry);
Evan Millar5c22c3b2009-05-29 11:37:54 -0700724 }
Evan Millar66388be2009-05-28 15:41:07 -0700725 // Build organization entries
726 } else if (mimetype.equals(CommonDataKinds.Organization.CONTENT_ITEM_TYPE)) {
727 final int type = aggCursor.getInt(DATA_1_COLUMN);
728 final String label = aggCursor.getString(DATA_2_COLUMN);
729 final String company = aggCursor.getString(DATA_3_COLUMN);
730 final String title = aggCursor.getString(DATA_4_COLUMN);
731 final boolean isPrimary = "1".equals(aggCursor.getString(DATA_5_COLUMN));
732
733 if (isPrimary) {
734 entry.primaryIcon = R.drawable.ic_default_number;
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800735 }
Evan Millar5c22c3b2009-05-29 11:37:54 -0700736
Evan Millar66388be2009-05-28 15:41:07 -0700737 // Don't crash if the data is bogus
738 if (TextUtils.isEmpty(company) && TextUtils.isEmpty(title)) {
739 Log.w(TAG, "empty data for contact method " + id);
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800740 continue;
741 }
Evan Millar5c22c3b2009-05-29 11:37:54 -0700742
Evan Millar66388be2009-05-28 15:41:07 -0700743 entry.data = title;
744 entry.actionIcon = R.drawable.sym_action_organization;
745 entry.label = company;
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800746
Evan Millar66388be2009-05-28 15:41:07 -0700747 mOrganizationEntries.add(entry);
748 // Build note entries
749 } else if (mimetype.equals(CommonDataKinds.Note.CONTENT_ITEM_TYPE)) {
750 entry.label = getString(R.string.label_notes);
751 entry.data = aggCursor.getString(DATA_1_COLUMN);
752 entry.id = 0;
753 entry.uri = null;
754 entry.intent = null;
755 entry.maxLines = 10;
756 entry.actionIcon = R.drawable.sym_note;
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800757
Evan Millar66388be2009-05-28 15:41:07 -0700758 if (TextUtils.isEmpty(entry.data)) {
759 Log.w(TAG, "empty data for contact method " + id);
Alex Kennberg87fc3172009-03-28 06:43:06 -0700760 continue;
761 }
Evan Millar5c22c3b2009-05-29 11:37:54 -0700762
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800763 mOtherEntries.add(entry);
Evan Millar66388be2009-05-28 15:41:07 -0700764 // Build the ringtone and send to voicemail entries
765 } else if (mimetype.equals(CommonDataKinds.CustomRingtone.CONTENT_ITEM_TYPE)) {
766 final Boolean sendToVoicemail = "1".equals(aggCursor.getString(DATA_1_COLUMN));
767 final String ringtoneStr = aggCursor.getString(DATA_2_COLUMN);
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800768
Evan Millar5c22c3b2009-05-29 11:37:54 -0700769 // TODO(emillar) we need to enforce uniqueness of custom ringtone entries on a
Evan Millar66388be2009-05-28 15:41:07 -0700770 // single aggregate. This could be done by checking against the reference ID stored
771 // in the aggregate.
Evan Millar5c22c3b2009-05-29 11:37:54 -0700772
Evan Millar66388be2009-05-28 15:41:07 -0700773 // Build the send directly to voice mail entry
774 if (sendToVoicemail) {
775 entry.label = getString(R.string.actionIncomingCall);
776 entry.data = getString(R.string.detailIncomingCallsGoToVoicemail);
777 entry.actionIcon = R.drawable.sym_send_to_voicemail;
778 mOtherEntries.add(entry);
779 } else if (!TextUtils.isEmpty(ringtoneStr)) {
780 // Get the URI
781 Uri ringtoneUri = Uri.parse(ringtoneStr);
782 if (ringtoneUri != null) {
783 Ringtone ringtone = RingtoneManager.getRingtone(this, ringtoneUri);
784 if (ringtone != null) {
785 entry.label = getString(R.string.label_ringtone);
786 entry.data = ringtone.getTitle(this);
787 entry.uri = ringtoneUri;
788 entry.actionIcon = R.drawable.sym_ringtone;
789 mOtherEntries.add(entry);
790 }
791 }
792 }
793 }
Evan Millar5c22c3b2009-05-29 11:37:54 -0700794
Evan Millar66388be2009-05-28 15:41:07 -0700795 // TODO(emillar) Add group entries
796// // Build the group entries
797// final Uri groupsUri = Uri.withAppendedPath(mUri, GroupMembership.CONTENT_DIRECTORY);
798// Cursor groupCursor = mResolver.query(groupsUri, ContactsListActivity.GROUPS_PROJECTION,
799// null, null, Groups.DEFAULT_SORT_ORDER);
800// if (groupCursor != null) {
801// try {
802// StringBuilder sb = new StringBuilder();
803//
804// while (groupCursor.moveToNext()) {
805// String systemId = groupCursor.getString(
806// ContactsListActivity.GROUPS_COLUMN_INDEX_SYSTEM_ID);
807//
808// if (systemId != null || Groups.GROUP_MY_CONTACTS.equals(systemId)) {
809// continue;
810// }
811//
812// String name = groupCursor.getString(ContactsListActivity.GROUPS_COLUMN_INDEX_NAME);
813// if (!TextUtils.isEmpty(name)) {
814// if (sb.length() == 0) {
815// sb.append(name);
816// } else {
817// sb.append(getString(R.string.group_list, name));
818// }
819// }
820// }
821//
822// if (sb.length() > 0) {
823// ViewEntry entry = new ViewEntry();
824// entry.kind = ContactEntryAdapter.Entry.KIND_GROUP;
825// entry.label = getString(R.string.label_groups);
826// entry.data = sb.toString();
827// entry.intent = new Intent(Intent.ACTION_EDIT, mUri);
828//
829// // TODO: Add an icon for the groups item.
830//
831// mGroupEntries.add(entry);
832// }
833// } finally {
834// groupCursor.close();
835// }
836// }
Evan Millar5c22c3b2009-05-29 11:37:54 -0700837
Evan Millar66388be2009-05-28 15:41:07 -0700838 }
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800839 }
840 }
841
Evan Millar66388be2009-05-28 15:41:07 -0700842
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800843 String buildActionString(int actionResId, CharSequence type, boolean lowerCase) {
Jeff Hamilton8350e5b2009-03-24 21:01:34 -0700844 // If there is no type just display an empty string
845 if (type == null) {
846 type = "";
847 }
848
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800849 if (lowerCase) {
850 return getString(actionResId, type.toString().toLowerCase());
851 } else {
852 return getString(actionResId, type.toString());
853 }
854 }
Evan Millar5c22c3b2009-05-29 11:37:54 -0700855
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800856 /**
857 * A basic structure with the data for a contact entry in the list.
858 */
859 final static class ViewEntry extends ContactEntryAdapter.Entry {
860 public int primaryIcon = -1;
861 public Intent intent;
862 public Intent auxIntent = null;
863 public int presenceIcon = -1;
864 public int actionIcon = -1;
865 public int maxLabelLines = 1;
866 }
867
868 private static final class ViewAdapter extends ContactEntryAdapter<ViewEntry> {
869 /** Cache of the children views of a row */
870 static class ViewCache {
871 public TextView label;
872 public TextView data;
873 public ImageView actionIcon;
874 public ImageView presenceIcon;
Evan Millar5c22c3b2009-05-29 11:37:54 -0700875
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800876 // Need to keep track of this too
877 ViewEntry entry;
878 }
Evan Millar5c22c3b2009-05-29 11:37:54 -0700879
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800880 ViewAdapter(Context context, ArrayList<ArrayList<ViewEntry>> sections) {
881 super(context, sections, SHOW_SEPARATORS);
882 }
883
884 @Override
885 public View getView(int position, View convertView, ViewGroup parent) {
Evan Millar5c22c3b2009-05-29 11:37:54 -0700886 ViewEntry entry = getEntry(mSections, position, false);
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800887 View v;
888
889 // Handle separators specially
890 if (entry.kind == ViewEntry.KIND_SEPARATOR) {
891 TextView separator = (TextView) mInflater.inflate(
892 R.layout.list_separator, parent, SHOW_SEPARATORS);
893 separator.setText(entry.data);
894 return separator;
895 }
896
897 ViewCache views;
898
899 // Check to see if we can reuse convertView
900 if (convertView != null) {
901 v = convertView;
902 views = (ViewCache) v.getTag();
903 } else {
904 // Create a new view if needed
905 v = mInflater.inflate(R.layout.list_item_text_icons, parent, false);
906
907 // Cache the children
908 views = new ViewCache();
909 views.label = (TextView) v.findViewById(android.R.id.text1);
910 views.data = (TextView) v.findViewById(android.R.id.text2);
911 views.actionIcon = (ImageView) v.findViewById(R.id.icon1);
912 views.presenceIcon = (ImageView) v.findViewById(R.id.icon2);
913 v.setTag(views);
914 }
915
916 // Update the entry in the view cache
917 views.entry = entry;
918
919 // Bind the data to the view
920 bindView(v, entry);
921 return v;
922 }
923
924 @Override
925 protected View newView(int position, ViewGroup parent) {
926 // getView() handles this
927 throw new UnsupportedOperationException();
928 }
929
930 @Override
931 protected void bindView(View view, ViewEntry entry) {
932 final Resources resources = mContext.getResources();
933 ViewCache views = (ViewCache) view.getTag();
934
935 // Set the label
936 TextView label = views.label;
937 setMaxLines(label, entry.maxLabelLines);
938 label.setText(entry.label);
939
940 // Set the data
941 TextView data = views.data;
942 if (data != null) {
943 data.setText(entry.data);
944 setMaxLines(data, entry.maxLines);
945 }
946
947 // Set the action icon
948 ImageView action = views.actionIcon;
949 if (entry.actionIcon != -1) {
950 action.setImageDrawable(resources.getDrawable(entry.actionIcon));
951 action.setVisibility(View.VISIBLE);
952 } else {
953 // Things should still line up as if there was an icon, so make it invisible
954 action.setVisibility(View.INVISIBLE);
955 }
956
957 // Set the presence icon
958 Drawable presenceIcon = null;
959 if (entry.primaryIcon != -1) {
960 presenceIcon = resources.getDrawable(entry.primaryIcon);
961 } else if (entry.presenceIcon != -1) {
962 presenceIcon = resources.getDrawable(entry.presenceIcon);
963 }
964
965 ImageView presence = views.presenceIcon;
966 if (presenceIcon != null) {
967 presence.setImageDrawable(presenceIcon);
968 presence.setVisibility(View.VISIBLE);
969 } else {
970 presence.setVisibility(View.GONE);
971 }
972 }
973
974 private void setMaxLines(TextView textView, int maxLines) {
975 if (maxLines == 1) {
976 textView.setSingleLine(true);
977 textView.setEllipsize(TextUtils.TruncateAt.END);
978 } else {
979 textView.setSingleLine(false);
980 textView.setMaxLines(maxLines);
981 textView.setEllipsize(null);
982 }
983 }
984 }
985}