2 * Copyright (C) 2007 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.contacts;
19 import android.app.ListActivity;
20 import android.content.ActivityNotFoundException;
21 import android.content.AsyncQueryHandler;
22 import android.content.ContentUris;
23 import android.content.ContentValues;
24 import android.content.Context;
25 import android.content.Intent;
26 import android.database.Cursor;
27 import android.graphics.drawable.Drawable;
28 import android.net.Uri;
29 import android.os.Bundle;
30 import android.os.Handler;
31 import android.os.Message;
32 import android.os.RemoteException;
33 import android.os.ServiceManager;
34 import android.os.SystemClock;
35 import android.provider.CallLog;
36 import android.provider.CallLog.Calls;
37 import android.provider.Contacts.People;
38 import android.provider.Contacts.Phones;
39 import android.provider.Contacts.Intents.Insert;
40 import android.telephony.PhoneNumberUtils;
41 import android.telephony.TelephonyManager;
42 import android.text.TextUtils;
43 import android.text.format.DateUtils;
44 import android.util.Log;
45 import android.view.ContextMenu;
46 import android.view.KeyEvent;
47 import android.view.Menu;
48 import android.view.MenuItem;
49 import android.view.View;
50 import android.view.ViewConfiguration;
51 import android.view.ViewGroup;
52 import android.view.ViewTreeObserver;
53 import android.view.ContextMenu.ContextMenuInfo;
54 import android.widget.AdapterView;
55 import android.widget.ImageView;
56 import android.widget.ListView;
57 import android.widget.ResourceCursorAdapter;
58 import android.widget.TextView;
60 import com.android.internal.telephony.CallerInfo;
61 import com.android.internal.telephony.ITelephony;
63 import java.util.HashMap;
64 import java.util.LinkedList;
65 import java.lang.ref.WeakReference;
68 * Displays a list of call log entries.
70 public class RecentCallsListActivity extends ListActivity
71 implements View.OnCreateContextMenuListener {
72 private static final String TAG = "RecentCallsList";
74 /** The projection to use when querying the call log table */
75 static final String[] CALL_LOG_PROJECTION = new String[] {
82 Calls.CACHED_NUMBER_TYPE,
83 Calls.CACHED_NUMBER_LABEL
86 static final int ID_COLUMN_INDEX = 0;
87 static final int NUMBER_COLUMN_INDEX = 1;
88 static final int DATE_COLUMN_INDEX = 2;
89 static final int DURATION_COLUMN_INDEX = 3;
90 static final int CALL_TYPE_COLUMN_INDEX = 4;
91 static final int CALLER_NAME_COLUMN_INDEX = 5;
92 static final int CALLER_NUMBERTYPE_COLUMN_INDEX = 6;
93 static final int CALLER_NUMBERLABEL_COLUMN_INDEX = 7;
95 /** The projection to use when querying the phones table */
96 static final String[] PHONES_PROJECTION = new String[] {
104 static final int PERSON_ID_COLUMN_INDEX = 0;
105 static final int NAME_COLUMN_INDEX = 1;
106 static final int PHONE_TYPE_COLUMN_INDEX = 2;
107 static final int LABEL_COLUMN_INDEX = 3;
108 static final int MATCHED_NUMBER_COLUMN_INDEX = 4;
110 private static final int MENU_ITEM_DELETE = 1;
111 private static final int MENU_ITEM_DELETE_ALL = 2;
112 private static final int MENU_ITEM_VIEW_CONTACTS = 3;
114 private static final int QUERY_TOKEN = 53;
115 private static final int UPDATE_TOKEN = 54;
117 private RecentCallsAdapter mAdapter;
118 private QueryHandler mQueryHandler;
119 private String mVoiceMailNumber;
121 private CharSequence[] mLabelArray;
123 private Drawable mDrawableIncoming;
124 private Drawable mDrawableOutgoing;
125 private Drawable mDrawableMissed;
127 private static final class ContactInfo {
128 public long personId;
132 public String number;
134 public static ContactInfo EMPTY = new ContactInfo();
137 public static final class RecentCallsListItemViews {
140 TextView durationView;
145 private static final class CallerInfoQuery {
153 /** Adapter class to fill in data for the Call Log */
154 private final class RecentCallsAdapter extends ResourceCursorAdapter
155 implements Runnable, ViewTreeObserver.OnPreDrawListener {
156 HashMap<String,ContactInfo> mContactInfo;
157 private final LinkedList<CallerInfoQuery> mRequests;
158 private volatile boolean mDone;
159 private boolean mLoading = true;
160 ViewTreeObserver.OnPreDrawListener mPreDrawListener;
161 private static final int REDRAW = 1;
162 private static final int START_THREAD = 2;
163 private boolean mFirst;
164 private Thread mCallerIdThread;
166 public boolean onPreDraw() {
168 mHandler.sendEmptyMessageDelayed(START_THREAD, 1000);
174 private Handler mHandler = new Handler() {
176 public void handleMessage(Message msg) {
179 notifyDataSetChanged();
182 startRequestProcessing();
188 public RecentCallsAdapter() {
189 super(RecentCallsListActivity.this, R.layout.recent_calls_list_item, null);
191 mContactInfo = new HashMap<String,ContactInfo>();
192 mRequests = new LinkedList<CallerInfoQuery>();
193 mPreDrawListener = null;
196 void setLoading(boolean loading) {
201 public boolean isEmpty() {
203 // We don't want the empty state to show when loading.
206 return super.isEmpty();
210 public ContactInfo getContactInfo(String number) {
211 return mContactInfo.get(number);
214 public void startRequestProcessing() {
216 mCallerIdThread = new Thread(this);
217 mCallerIdThread.setPriority(Thread.MIN_PRIORITY);
218 mCallerIdThread.start();
221 public void stopRequestProcessing() {
223 if (mCallerIdThread != null) mCallerIdThread.interrupt();
226 public void clearCache() {
227 synchronized (mContactInfo) {
228 mContactInfo.clear();
232 private void updateCallLog(CallerInfoQuery ciq, ContactInfo ci) {
233 // Check if they are different. If not, don't update.
234 if (TextUtils.equals(ciq.name, ci.name)
235 && TextUtils.equals(ciq.numberLabel, ci.label)
236 && ciq.numberType == ci.type) {
239 ContentValues values = new ContentValues(3);
240 values.put(Calls.CACHED_NAME, ci.name);
241 values.put(Calls.CACHED_NUMBER_TYPE, ci.type);
242 values.put(Calls.CACHED_NUMBER_LABEL, ci.label);
243 RecentCallsListActivity.this.getContentResolver().update(
245 values, Calls.NUMBER + "='" + ciq.number + "'", null);
248 private void enqueueRequest(String number, int position,
249 String name, int numberType, String numberLabel) {
250 CallerInfoQuery ciq = new CallerInfoQuery();
252 ciq.position = position;
254 ciq.numberType = numberType;
255 ciq.numberLabel = numberLabel;
256 synchronized (mRequests) {
258 mRequests.notifyAll();
262 private void queryContactInfo(CallerInfoQuery ciq) {
263 // First check if there was a prior request for the same number
264 // that was already satisfied
265 ContactInfo info = mContactInfo.get(ciq.number);
266 if (info != null && info != ContactInfo.EMPTY) {
267 synchronized (mRequests) {
268 if (mRequests.isEmpty()) {
269 mHandler.sendEmptyMessage(REDRAW);
273 Cursor phonesCursor =
274 RecentCallsListActivity.this.getContentResolver().query(
275 Uri.withAppendedPath(Phones.CONTENT_FILTER_URL,
276 Uri.encode(ciq.number)),
277 PHONES_PROJECTION, null, null, null);
278 if (phonesCursor != null) {
279 if (phonesCursor.moveToFirst()) {
280 info = new ContactInfo();
281 info.personId = phonesCursor.getLong(PERSON_ID_COLUMN_INDEX);
282 info.name = phonesCursor.getString(NAME_COLUMN_INDEX);
283 info.type = phonesCursor.getInt(PHONE_TYPE_COLUMN_INDEX);
284 info.label = phonesCursor.getString(LABEL_COLUMN_INDEX);
285 info.number = phonesCursor.getString(MATCHED_NUMBER_COLUMN_INDEX);
287 mContactInfo.put(ciq.number, info);
288 // Inform list to update this item, if in view
289 synchronized (mRequests) {
290 if (mRequests.isEmpty()) {
291 mHandler.sendEmptyMessage(REDRAW);
295 phonesCursor.close();
299 updateCallLog(ciq, info);
304 * Handles requests for contact name and number type
305 * @see java.lang.Runnable#run()
309 CallerInfoQuery ciq = null;
310 synchronized (mRequests) {
311 if (!mRequests.isEmpty()) {
312 ciq = mRequests.removeFirst();
315 mRequests.wait(1000);
316 } catch (InterruptedException ie) {
317 // Ignore and continue processing requests
322 queryContactInfo(ciq);
328 public View newView(Context context, Cursor cursor, ViewGroup parent) {
329 View view = super.newView(context, cursor, parent);
331 // Get the views to bind to
332 RecentCallsListItemViews views = new RecentCallsListItemViews();
333 views.line1View = (TextView) view.findViewById(R.id.line1);
334 views.line2View = (TextView) view.findViewById(R.id.line2);
335 views.durationView = (TextView) view.findViewById(R.id.duration);
336 views.dateView = (TextView) view.findViewById(R.id.date);
337 views.iconView = (ImageView) view.findViewById(R.id.call_type_icon);
346 public void bindView(View view, Context context, Cursor c) {
347 final RecentCallsListItemViews views = (RecentCallsListItemViews) view.getTag();
349 String number = c.getString(NUMBER_COLUMN_INDEX);
350 String callerName = c.getString(CALLER_NAME_COLUMN_INDEX);
351 int callerNumberType = c.getInt(CALLER_NUMBERTYPE_COLUMN_INDEX);
352 String callerNumberLabel = c.getString(CALLER_NUMBERLABEL_COLUMN_INDEX);
354 // Lookup contacts with this number
355 ContactInfo info = mContactInfo.get(number);
357 // Mark it as empty and queue up a request to find the name
358 // The db request should happen on a non-UI thread
359 info = ContactInfo.EMPTY;
360 mContactInfo.put(number, info);
361 enqueueRequest(number, c.getPosition(),
362 callerName, callerNumberType, callerNumberLabel);
363 } else if (info != ContactInfo.EMPTY) { // Has been queried
364 // Check if any data is different from the data cached in the
365 // calls db. If so, queue the request so that we can update
367 if (!TextUtils.equals(info.name, callerName)
368 || info.type != callerNumberType
369 || !TextUtils.equals(info.label, callerNumberLabel)) {
370 // Something is amiss, so sync up.
371 enqueueRequest(number, c.getPosition(),
372 callerName, callerNumberType, callerNumberLabel);
376 String name = info.name;
377 int ntype = info.type;
378 String label = info.label;
379 // If there's no name cached in our hashmap, but there's one in the
380 // calls db, use the one in the calls db. Otherwise the name in our
381 // hashmap is more recent, so it has precedence.
382 if (TextUtils.isEmpty(name) && !TextUtils.isEmpty(callerName)) {
384 ntype = callerNumberType;
385 label = callerNumberLabel;
387 // Set the text lines
388 if (!TextUtils.isEmpty(name)) {
389 views.line1View.setText(name);
390 CharSequence numberLabel = Phones.getDisplayLabel(context, ntype, label,
392 if (!TextUtils.isEmpty(numberLabel)) {
393 views.line2View.setText(numberLabel);
395 views.line2View.setText(number);
398 // Set the presence icon
401 if (!c.isNull(SERVER_STATUS_COLUMN_INDEX)) {
402 serverStatus = c.getInt(SERVER_STATUS_COLUMN_INDEX);
403 views.line2View.setCompoundDrawablesWithIntrinsicBounds(
404 getResources().getDrawable(
405 Presence.getPresenceIconResourceId(serverStatus)),
408 views.line2View.setCompoundDrawablesWithIntrinsicBounds(null, null, null, null);
412 if (number.equals(CallerInfo.UNKNOWN_NUMBER)) {
413 number = getString(R.string.unknown);
414 } else if (number.equals(CallerInfo.PRIVATE_NUMBER)) {
415 number = getString(R.string.private_num);
416 } else if (number.equals(mVoiceMailNumber)) {
417 number = getString(R.string.voicemail);
419 // Just a raw number, format it to look pretty
420 number = PhoneNumberUtils.formatNumber(number);
423 views.line1View.setText(number);
424 views.line2View.setText(null);
426 // Clear the presence icon
427 // views.line2View.setCompoundDrawablesWithIntrinsicBounds(null, null, null, null);
430 int type = c.getInt(CALL_TYPE_COLUMN_INDEX);
431 long date = c.getLong(DATE_COLUMN_INDEX);
434 if (type == Calls.MISSED_TYPE) {
435 views.durationView.setVisibility(View.GONE);
437 views.durationView.setVisibility(View.VISIBLE);
438 views.durationView.setText(DateUtils.formatElapsedTime(c.getLong(DURATION_COLUMN_INDEX)));
441 // Set the date/time field by mixing relative and absolute times.
442 int flags = DateUtils.FORMAT_ABBREV_RELATIVE | DateUtils.FORMAT_SHOW_DATE
443 | DateUtils.FORMAT_ABBREV_MONTH;
445 views.dateView.setText(DateUtils.getRelativeDateTimeString(context, date,
446 DateUtils.MINUTE_IN_MILLIS, DateUtils.DAY_IN_MILLIS * 2, flags));
450 case Calls.INCOMING_TYPE:
451 views.iconView.setImageDrawable(mDrawableIncoming);
454 case Calls.OUTGOING_TYPE:
455 views.iconView.setImageDrawable(mDrawableOutgoing);
458 case Calls.MISSED_TYPE:
459 views.iconView.setImageDrawable(mDrawableMissed);
462 // Listen for the first draw
463 if (mPreDrawListener == null) {
465 mPreDrawListener = this;
466 view.getViewTreeObserver().addOnPreDrawListener(this);
471 private static final class QueryHandler extends AsyncQueryHandler {
472 private final WeakReference<RecentCallsListActivity> mActivity;
474 public QueryHandler(Context context) {
475 super(context.getContentResolver());
476 mActivity = new WeakReference<RecentCallsListActivity>(
477 (RecentCallsListActivity) context);
481 protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
482 final RecentCallsListActivity activity = mActivity.get();
483 if (activity != null && !activity.isFinishing()) {
484 final RecentCallsListActivity.RecentCallsAdapter callsAdapter = activity.mAdapter;
485 callsAdapter.setLoading(false);
486 callsAdapter.changeCursor(cursor);
494 protected void onCreate(Bundle state) {
495 super.onCreate(state);
497 setContentView(R.layout.recent_calls);
499 mDrawableIncoming = getResources().getDrawable(android.R.drawable.sym_call_incoming);
500 mDrawableOutgoing = getResources().getDrawable(android.R.drawable.sym_call_outgoing);
501 mDrawableMissed = getResources().getDrawable(android.R.drawable.sym_call_missed);
502 mLabelArray = getResources().getTextArray(com.android.internal.R.array.phoneTypes);
504 // Typing here goes to the dialer
505 setDefaultKeyMode(DEFAULT_KEYS_DIALER);
507 mAdapter = new RecentCallsAdapter();
508 getListView().setOnCreateContextMenuListener(this);
509 setListAdapter(mAdapter);
511 mVoiceMailNumber = ((TelephonyManager)getSystemService(Context.TELEPHONY_SERVICE))
512 .getVoiceMailNumber();
513 mQueryHandler = new QueryHandler(this);
517 protected void onResume() {
518 // The adapter caches looked up numbers, clear it so they will get
520 if (mAdapter != null) {
521 mAdapter.clearCache();
529 mAdapter.mPreDrawListener = null; // Let it restart the thread after next draw
533 protected void onPause() {
536 // Kill the requests thread
537 mAdapter.stopRequestProcessing();
541 protected void onDestroy() {
543 mAdapter.stopRequestProcessing();
544 Cursor cursor = mAdapter.getCursor();
545 if (cursor != null && !cursor.isClosed()) {
551 public void onWindowFocusChanged(boolean hasFocus) {
552 super.onWindowFocusChanged(hasFocus);
554 // Clear notifications only when window gains focus. This activity won't
555 // immediately receive focus if the keyguard screen is above it.
558 ITelephony iTelephony =
559 ITelephony.Stub.asInterface(ServiceManager.getService("phone"));
560 if (iTelephony != null) {
561 iTelephony.cancelMissedCallsNotification();
563 Log.w(TAG, "Telephony service is null, can't call " +
564 "cancelMissedCallsNotification");
566 } catch (RemoteException e) {
567 Log.e(TAG, "Failed to clear missed calls notification due to remote exception");
572 private void resetNewCallsFlag() {
573 // Mark all "new" missed calls as not new anymore
574 StringBuilder where = new StringBuilder("type=");
575 where.append(Calls.MISSED_TYPE);
576 where.append(" AND new=1");
578 ContentValues values = new ContentValues(1);
579 values.put(Calls.NEW, "0");
580 mQueryHandler.startUpdate(UPDATE_TOKEN, null, Calls.CONTENT_URI,
581 values, where.toString(), null);
584 private void startQuery() {
585 mAdapter.setLoading(true);
587 // Cancel any pending queries
588 mQueryHandler.cancelOperation(QUERY_TOKEN);
589 mQueryHandler.startQuery(QUERY_TOKEN, null, Calls.CONTENT_URI,
590 CALL_LOG_PROJECTION, null, null, Calls.DEFAULT_SORT_ORDER);
594 public boolean onCreateOptionsMenu(Menu menu) {
595 menu.add(0, MENU_ITEM_DELETE_ALL, 0, R.string.recentCalls_deleteAll)
596 .setIcon(android.R.drawable.ic_menu_close_clear_cancel);
601 public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfoIn) {
602 AdapterView.AdapterContextMenuInfo menuInfo;
604 menuInfo = (AdapterView.AdapterContextMenuInfo) menuInfoIn;
605 } catch (ClassCastException e) {
606 Log.e(TAG, "bad menuInfoIn", e);
610 Cursor cursor = (Cursor) mAdapter.getItem(menuInfo.position);
612 String number = cursor.getString(NUMBER_COLUMN_INDEX);
613 Uri numberUri = null;
614 boolean isVoicemail = false;
615 if (number.equals(CallerInfo.UNKNOWN_NUMBER)) {
616 number = getString(R.string.unknown);
617 } else if (number.equals(CallerInfo.PRIVATE_NUMBER)) {
618 number = getString(R.string.private_num);
619 } else if (number.equals(mVoiceMailNumber)) {
620 number = getString(R.string.voicemail);
621 numberUri = Uri.parse("voicemail:x");
624 numberUri = Uri.fromParts("tel", number, null);
627 ContactInfo info = mAdapter.getContactInfo(number);
628 boolean contactInfoPresent = (info != null && info != ContactInfo.EMPTY);
629 if (contactInfoPresent) {
630 menu.setHeaderTitle(info.name);
632 menu.setHeaderTitle(number);
635 if (numberUri != null) {
636 Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED, numberUri);
637 menu.add(0, 0, 0, getResources().getString(R.string.recentCalls_callNumber, number))
641 if (contactInfoPresent) {
642 menu.add(0, 0, 0, R.string.menu_viewContact)
643 .setIntent(new Intent(Intent.ACTION_VIEW,
644 ContentUris.withAppendedId(People.CONTENT_URI, info.personId)));
647 if (numberUri != null && !isVoicemail) {
648 menu.add(0, 0, 0, R.string.recentCalls_editNumberBeforeCall)
649 .setIntent(new Intent(Intent.ACTION_DIAL, numberUri));
650 menu.add(0, 0, 0, R.string.menu_sendTextMessage)
651 .setIntent(new Intent(Intent.ACTION_SENDTO,
652 Uri.fromParts("sms", number, null)));
654 if (!contactInfoPresent && numberUri != null && !isVoicemail) {
655 Intent intent = new Intent(Intent.ACTION_INSERT_OR_EDIT);
656 intent.setType(People.CONTENT_ITEM_TYPE);
657 intent.putExtra(Insert.PHONE, number);
658 menu.add(0, 0, 0, R.string.recentCalls_addToContact)
661 menu.add(0, MENU_ITEM_DELETE, 0, R.string.recentCalls_removeFromRecentList);
665 public boolean onOptionsItemSelected(MenuItem item) {
666 switch (item.getItemId()) {
667 case MENU_ITEM_DELETE_ALL: {
668 getContentResolver().delete(Calls.CONTENT_URI, null, null);
669 //TODO The change notification should do this automatically, but it isn't working
670 // right now. Remove this when the change notification is working properly.
675 case MENU_ITEM_VIEW_CONTACTS: {
676 Intent intent = new Intent(Intent.ACTION_VIEW, People.CONTENT_URI);
677 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
678 startActivity(intent);
682 return super.onOptionsItemSelected(item);
686 public boolean onContextItemSelected(MenuItem item) {
687 // Convert the menu info to the proper type
688 AdapterView.AdapterContextMenuInfo menuInfo;
690 menuInfo = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo();
691 } catch (ClassCastException e) {
692 Log.e(TAG, "bad menuInfoIn", e);
696 switch (item.getItemId()) {
697 case MENU_ITEM_DELETE: {
698 Cursor cursor = mAdapter.getCursor();
699 if (cursor != null) {
700 cursor.moveToPosition(menuInfo.position);
706 return super.onContextItemSelected(item);
710 public boolean onKeyDown(int keyCode, KeyEvent event) {
712 case KeyEvent.KEYCODE_CALL: {
713 long callPressDiff = SystemClock.uptimeMillis() - event.getDownTime();
714 if (callPressDiff >= ViewConfiguration.getLongPressTimeout()) {
715 // Launch voice dialer
716 Intent intent = new Intent(Intent.ACTION_VOICE_COMMAND);
717 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
719 startActivity(intent);
720 } catch (ActivityNotFoundException e) {
726 return super.onKeyDown(keyCode, event);
730 public boolean onKeyUp(int keyCode, KeyEvent event) {
732 case KeyEvent.KEYCODE_CALL:
734 ITelephony phone = ITelephony.Stub.asInterface(
735 ServiceManager.checkService("phone"));
736 if (phone != null && !phone.isIdle()) {
737 // Let the super class handle it
740 } catch (RemoteException re) {
741 // Fall through and try to call the contact
744 callEntry(getListView().getSelectedItemPosition());
747 return super.onKeyUp(keyCode, event);
751 * Get the number from the Contacts, if available, since sometimes
752 * the number provided by caller id may not be formatted properly
753 * depending on the carrier (roaming) in use at the time of the
755 * Logic : If the caller-id number starts with a "+", use it
756 * Else if the number in the contacts starts with a "+", use that one
757 * Else if the number in the contacts is longer, use that one
759 private String getBetterNumberFromContacts(String number) {
760 String matchingNumber = null;
761 // Look in the cache first. If it's not found then query the Phones db
762 ContactInfo ci = mAdapter.mContactInfo.get(number);
763 if (ci != null && ci != ContactInfo.EMPTY) {
764 matchingNumber = ci.number;
767 Cursor phonesCursor =
768 RecentCallsListActivity.this.getContentResolver().query(
769 Uri.withAppendedPath(Phones.CONTENT_FILTER_URL,
771 PHONES_PROJECTION, null, null, null);
772 if (phonesCursor != null) {
773 if (phonesCursor.moveToFirst()) {
774 matchingNumber = phonesCursor.getString(MATCHED_NUMBER_COLUMN_INDEX);
776 phonesCursor.close();
778 } catch (Exception e) {
779 // Use the number from the call log
782 if (!TextUtils.isEmpty(matchingNumber) &&
783 (matchingNumber.startsWith("+")
784 || matchingNumber.length() > number.length())) {
785 number = matchingNumber;
790 private void callEntry(int position) {
792 // In touch mode you may often not have something selected, so
793 // just call the first entry to make sure that [send] [send] calls the
794 // most recent entry.
797 final Cursor cursor = mAdapter.getCursor();
798 if (cursor != null && cursor.moveToPosition(position)) {
799 String number = cursor.getString(NUMBER_COLUMN_INDEX);
800 if (TextUtils.isEmpty(number)
801 || number.equals(CallerInfo.UNKNOWN_NUMBER)
802 || number.equals(CallerInfo.PRIVATE_NUMBER)) {
803 // This number can't be called, do nothing
807 int callType = cursor.getInt(CALL_TYPE_COLUMN_INDEX);
808 if (!number.startsWith("+") &&
809 (callType == Calls.INCOMING_TYPE
810 || callType == Calls.MISSED_TYPE)) {
811 // If the caller-id matches a contact with a better qualified number, use it
812 number = getBetterNumberFromContacts(number);
814 Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED,
815 Uri.fromParts("tel", number, null));
817 Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
818 startActivity(intent);
823 protected void onListItemClick(ListView l, View v, int position, long id) {
824 Intent intent = new Intent(this, CallDetailActivity.class);
825 intent.setData(ContentUris.withAppendedId(CallLog.Calls.CONTENT_URI, id));
826 startActivity(intent);