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 RecentCallsAdapter mAdapter;
118 private QueryHandler mQueryHandler;
119 String mVoiceMailNumber;
121 static final class ContactInfo {
122 public long personId;
126 public String number;
128 public static ContactInfo EMPTY = new ContactInfo();
131 public static final class RecentCallsListItemViews {
140 static final class CallerInfoQuery {
148 /** Adapter class to fill in data for the Call Log */
149 final class RecentCallsAdapter extends ResourceCursorAdapter
150 implements Runnable, ViewTreeObserver.OnPreDrawListener, View.OnClickListener {
151 HashMap<String,ContactInfo> mContactInfo;
152 private final LinkedList<CallerInfoQuery> mRequests;
153 private volatile boolean mDone;
154 private boolean mLoading = true;
155 ViewTreeObserver.OnPreDrawListener mPreDrawListener;
156 private static final int REDRAW = 1;
157 private static final int START_THREAD = 2;
158 private boolean mFirst;
159 private Thread mCallerIdThread;
161 private CharSequence[] mLabelArray;
163 private Drawable mDrawableIncoming;
164 private Drawable mDrawableOutgoing;
165 private Drawable mDrawableMissed;
167 public void onClick(View view) {
168 String number = (String) view.getTag();
169 if (!TextUtils.isEmpty(number)) {
170 Uri telUri = Uri.fromParts("tel", number, null);
171 startActivity(new Intent(Intent.ACTION_CALL_PRIVILEGED, telUri));
175 public boolean onPreDraw() {
177 mHandler.sendEmptyMessageDelayed(START_THREAD, 1000);
183 private Handler mHandler = new Handler() {
185 public void handleMessage(Message msg) {
188 notifyDataSetChanged();
191 startRequestProcessing();
197 public RecentCallsAdapter() {
198 super(RecentCallsListActivity.this, R.layout.recent_calls_list_item, null);
200 mContactInfo = new HashMap<String,ContactInfo>();
201 mRequests = new LinkedList<CallerInfoQuery>();
202 mPreDrawListener = null;
204 mDrawableIncoming = getResources().getDrawable(
205 R.drawable.ic_call_log_list_incoming_call);
206 mDrawableOutgoing = getResources().getDrawable(
207 R.drawable.ic_call_log_list_outgoing_call);
208 mDrawableMissed = getResources().getDrawable(
209 R.drawable.ic_call_log_list_missed_call);
210 mLabelArray = getResources().getTextArray(com.android.internal.R.array.phoneTypes);
213 void setLoading(boolean loading) {
218 public boolean isEmpty() {
220 // We don't want the empty state to show when loading.
223 return super.isEmpty();
227 public ContactInfo getContactInfo(String number) {
228 return mContactInfo.get(number);
231 public void startRequestProcessing() {
233 mCallerIdThread = new Thread(this);
234 mCallerIdThread.setPriority(Thread.MIN_PRIORITY);
235 mCallerIdThread.start();
238 public void stopRequestProcessing() {
240 if (mCallerIdThread != null) mCallerIdThread.interrupt();
243 public void clearCache() {
244 synchronized (mContactInfo) {
245 mContactInfo.clear();
249 private void updateCallLog(CallerInfoQuery ciq, ContactInfo ci) {
250 // Check if they are different. If not, don't update.
251 if (TextUtils.equals(ciq.name, ci.name)
252 && TextUtils.equals(ciq.numberLabel, ci.label)
253 && ciq.numberType == ci.type) {
256 ContentValues values = new ContentValues(3);
257 values.put(Calls.CACHED_NAME, ci.name);
258 values.put(Calls.CACHED_NUMBER_TYPE, ci.type);
259 values.put(Calls.CACHED_NUMBER_LABEL, ci.label);
260 RecentCallsListActivity.this.getContentResolver().update(
262 values, Calls.NUMBER + "='" + ciq.number + "'", null);
265 private void enqueueRequest(String number, int position,
266 String name, int numberType, String numberLabel) {
267 CallerInfoQuery ciq = new CallerInfoQuery();
269 ciq.position = position;
271 ciq.numberType = numberType;
272 ciq.numberLabel = numberLabel;
273 synchronized (mRequests) {
275 mRequests.notifyAll();
279 private void queryContactInfo(CallerInfoQuery ciq) {
280 // First check if there was a prior request for the same number
281 // that was already satisfied
282 ContactInfo info = mContactInfo.get(ciq.number);
283 if (info != null && info != ContactInfo.EMPTY) {
284 synchronized (mRequests) {
285 if (mRequests.isEmpty()) {
286 mHandler.sendEmptyMessage(REDRAW);
290 Cursor phonesCursor =
291 RecentCallsListActivity.this.getContentResolver().query(
292 Uri.withAppendedPath(Phones.CONTENT_FILTER_URL,
293 Uri.encode(ciq.number)),
294 PHONES_PROJECTION, null, null, null);
295 if (phonesCursor != null) {
296 if (phonesCursor.moveToFirst()) {
297 info = new ContactInfo();
298 info.personId = phonesCursor.getLong(PERSON_ID_COLUMN_INDEX);
299 info.name = phonesCursor.getString(NAME_COLUMN_INDEX);
300 info.type = phonesCursor.getInt(PHONE_TYPE_COLUMN_INDEX);
301 info.label = phonesCursor.getString(LABEL_COLUMN_INDEX);
302 info.number = phonesCursor.getString(MATCHED_NUMBER_COLUMN_INDEX);
304 mContactInfo.put(ciq.number, info);
305 // Inform list to update this item, if in view
306 synchronized (mRequests) {
307 if (mRequests.isEmpty()) {
308 mHandler.sendEmptyMessage(REDRAW);
312 phonesCursor.close();
316 updateCallLog(ciq, info);
321 * Handles requests for contact name and number type
322 * @see java.lang.Runnable#run()
326 CallerInfoQuery ciq = null;
327 synchronized (mRequests) {
328 if (!mRequests.isEmpty()) {
329 ciq = mRequests.removeFirst();
332 mRequests.wait(1000);
333 } catch (InterruptedException ie) {
334 // Ignore and continue processing requests
339 queryContactInfo(ciq);
345 public View newView(Context context, Cursor cursor, ViewGroup parent) {
346 View view = super.newView(context, cursor, parent);
348 // Get the views to bind to
349 RecentCallsListItemViews views = new RecentCallsListItemViews();
350 views.line1View = (TextView) view.findViewById(R.id.line1);
351 views.labelView = (TextView) view.findViewById(R.id.label);
352 views.numberView = (TextView) view.findViewById(R.id.number);
353 views.dateView = (TextView) view.findViewById(R.id.date);
354 views.iconView = (ImageView) view.findViewById(R.id.call_type_icon);
355 views.callView = view.findViewById(R.id.call_icon);
356 views.callView.setOnClickListener(this);
365 public void bindView(View view, Context context, Cursor c) {
366 final RecentCallsListItemViews views = (RecentCallsListItemViews) view.getTag();
368 String number = c.getString(NUMBER_COLUMN_INDEX);
369 String callerName = c.getString(CALLER_NAME_COLUMN_INDEX);
370 int callerNumberType = c.getInt(CALLER_NUMBERTYPE_COLUMN_INDEX);
371 String callerNumberLabel = c.getString(CALLER_NUMBERLABEL_COLUMN_INDEX);
373 // Store away the number so we can call it directly if you click on the call icon
374 views.callView.setTag(number);
376 // Lookup contacts with this number
377 ContactInfo info = mContactInfo.get(number);
379 // Mark it as empty and queue up a request to find the name
380 // The db request should happen on a non-UI thread
381 info = ContactInfo.EMPTY;
382 mContactInfo.put(number, info);
383 enqueueRequest(number, c.getPosition(),
384 callerName, callerNumberType, callerNumberLabel);
385 } else if (info != ContactInfo.EMPTY) { // Has been queried
386 // Check if any data is different from the data cached in the
387 // calls db. If so, queue the request so that we can update
389 if (!TextUtils.equals(info.name, callerName)
390 || info.type != callerNumberType
391 || !TextUtils.equals(info.label, callerNumberLabel)) {
392 // Something is amiss, so sync up.
393 enqueueRequest(number, c.getPosition(),
394 callerName, callerNumberType, callerNumberLabel);
398 String name = info.name;
399 int ntype = info.type;
400 String label = info.label;
401 // If there's no name cached in our hashmap, but there's one in the
402 // calls db, use the one in the calls db. Otherwise the name in our
403 // hashmap is more recent, so it has precedence.
404 if (TextUtils.isEmpty(name) && !TextUtils.isEmpty(callerName)) {
406 ntype = callerNumberType;
407 label = callerNumberLabel;
409 // Set the text lines
410 if (!TextUtils.isEmpty(name)) {
411 views.line1View.setText(name);
412 views.labelView.setVisibility(View.VISIBLE);
413 CharSequence numberLabel = Phones.getDisplayLabel(context, ntype, label,
415 views.numberView.setVisibility(View.VISIBLE);
416 views.numberView.setText(number);
417 if (!TextUtils.isEmpty(numberLabel)) {
418 views.labelView.setText(numberLabel);
419 views.labelView.setVisibility(View.VISIBLE);
421 views.labelView.setVisibility(View.GONE);
424 if (number.equals(CallerInfo.UNKNOWN_NUMBER)) {
425 number = getString(R.string.unknown);
426 } else if (number.equals(CallerInfo.PRIVATE_NUMBER)) {
427 number = getString(R.string.private_num);
428 } else if (number.equals(CallerInfo.PAYPHONE_NUMBER)) {
429 number = getString(R.string.payphone);
430 } else if (number.equals(mVoiceMailNumber)) {
431 number = getString(R.string.voicemail);
433 // Just a raw number, format it to look pretty
434 number = PhoneNumberUtils.formatNumber(number);
437 views.line1View.setText(number);
438 views.numberView.setVisibility(View.GONE);
439 views.labelView.setVisibility(View.GONE);
442 int type = c.getInt(CALL_TYPE_COLUMN_INDEX);
443 long date = c.getLong(DATE_COLUMN_INDEX);
445 // Set the date/time field by mixing relative and absolute times.
446 int flags = DateUtils.FORMAT_ABBREV_RELATIVE;
448 views.dateView.setText(DateUtils.getRelativeTimeSpanString(date,
449 System.currentTimeMillis(), DateUtils.MINUTE_IN_MILLIS, flags));
453 case Calls.INCOMING_TYPE:
454 views.iconView.setImageDrawable(mDrawableIncoming);
457 case Calls.OUTGOING_TYPE:
458 views.iconView.setImageDrawable(mDrawableOutgoing);
461 case Calls.MISSED_TYPE:
462 views.iconView.setImageDrawable(mDrawableMissed);
466 // Listen for the first draw
467 if (mPreDrawListener == null) {
469 mPreDrawListener = this;
470 view.getViewTreeObserver().addOnPreDrawListener(this);
475 private static final class QueryHandler extends AsyncQueryHandler {
476 private final WeakReference<RecentCallsListActivity> mActivity;
478 public QueryHandler(Context context) {
479 super(context.getContentResolver());
480 mActivity = new WeakReference<RecentCallsListActivity>(
481 (RecentCallsListActivity) context);
485 protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
486 final RecentCallsListActivity activity = mActivity.get();
487 if (activity != null && !activity.isFinishing()) {
488 final RecentCallsListActivity.RecentCallsAdapter callsAdapter = activity.mAdapter;
489 callsAdapter.setLoading(false);
490 callsAdapter.changeCursor(cursor);
498 protected void onCreate(Bundle state) {
499 super.onCreate(state);
501 setContentView(R.layout.recent_calls);
503 // Typing here goes to the dialer
504 setDefaultKeyMode(DEFAULT_KEYS_DIALER);
506 mAdapter = new RecentCallsAdapter();
507 getListView().setOnCreateContextMenuListener(this);
508 setListAdapter(mAdapter);
510 mVoiceMailNumber = ((TelephonyManager)getSystemService(Context.TELEPHONY_SERVICE))
511 .getVoiceMailNumber();
512 mQueryHandler = new QueryHandler(this);
516 protected void onResume() {
517 // The adapter caches looked up numbers, clear it so they will get
519 if (mAdapter != null) {
520 mAdapter.clearCache();
528 mAdapter.mPreDrawListener = null; // Let it restart the thread after next draw
532 protected void onPause() {
535 // Kill the requests thread
536 mAdapter.stopRequestProcessing();
540 protected void onDestroy() {
542 mAdapter.stopRequestProcessing();
543 Cursor cursor = mAdapter.getCursor();
544 if (cursor != null && !cursor.isClosed()) {
550 public void onWindowFocusChanged(boolean hasFocus) {
551 super.onWindowFocusChanged(hasFocus);
553 // Clear notifications only when window gains focus. This activity won't
554 // immediately receive focus if the keyguard screen is above it.
557 ITelephony iTelephony =
558 ITelephony.Stub.asInterface(ServiceManager.getService("phone"));
559 if (iTelephony != null) {
560 iTelephony.cancelMissedCallsNotification();
562 Log.w(TAG, "Telephony service is null, can't call " +
563 "cancelMissedCallsNotification");
565 } catch (RemoteException e) {
566 Log.e(TAG, "Failed to clear missed calls notification due to remote exception");
571 private void resetNewCallsFlag() {
572 // Mark all "new" missed calls as not new anymore
573 StringBuilder where = new StringBuilder("type=");
574 where.append(Calls.MISSED_TYPE);
575 where.append(" AND new=1");
577 ContentValues values = new ContentValues(1);
578 values.put(Calls.NEW, "0");
579 mQueryHandler.startUpdate(UPDATE_TOKEN, null, Calls.CONTENT_URI,
580 values, where.toString(), null);
583 private void startQuery() {
584 mAdapter.setLoading(true);
586 // Cancel any pending queries
587 mQueryHandler.cancelOperation(QUERY_TOKEN);
588 mQueryHandler.startQuery(QUERY_TOKEN, null, Calls.CONTENT_URI,
589 CALL_LOG_PROJECTION, null, null, Calls.DEFAULT_SORT_ORDER);
593 public boolean onCreateOptionsMenu(Menu menu) {
594 menu.add(0, MENU_ITEM_DELETE_ALL, 0, R.string.recentCalls_deleteAll)
595 .setIcon(android.R.drawable.ic_menu_close_clear_cancel);
600 public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfoIn) {
601 AdapterView.AdapterContextMenuInfo menuInfo;
603 menuInfo = (AdapterView.AdapterContextMenuInfo) menuInfoIn;
604 } catch (ClassCastException e) {
605 Log.e(TAG, "bad menuInfoIn", e);
609 Cursor cursor = (Cursor) mAdapter.getItem(menuInfo.position);
611 String number = cursor.getString(NUMBER_COLUMN_INDEX);
612 Uri numberUri = null;
613 boolean isVoicemail = false;
614 if (number.equals(CallerInfo.UNKNOWN_NUMBER)) {
615 number = getString(R.string.unknown);
616 } else if (number.equals(CallerInfo.PRIVATE_NUMBER)) {
617 number = getString(R.string.private_num);
618 } else if (number.equals(CallerInfo.PAYPHONE_NUMBER)) {
619 number = getString(R.string.payphone);
620 } else if (number.equals(mVoiceMailNumber)) {
621 number = getString(R.string.voicemail);
622 numberUri = Uri.parse("voicemail:x");
625 numberUri = Uri.fromParts("tel", number, null);
628 ContactInfo info = mAdapter.getContactInfo(number);
629 boolean contactInfoPresent = (info != null && info != ContactInfo.EMPTY);
630 if (contactInfoPresent) {
631 menu.setHeaderTitle(info.name);
633 menu.setHeaderTitle(number);
636 if (numberUri != null) {
637 Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED, numberUri);
638 menu.add(0, 0, 0, getResources().getString(R.string.recentCalls_callNumber, number))
642 if (contactInfoPresent) {
643 menu.add(0, 0, 0, R.string.menu_viewContact)
644 .setIntent(new Intent(Intent.ACTION_VIEW,
645 ContentUris.withAppendedId(People.CONTENT_URI, info.personId)));
648 if (numberUri != null && !isVoicemail) {
649 menu.add(0, 0, 0, R.string.recentCalls_editNumberBeforeCall)
650 .setIntent(new Intent(Intent.ACTION_DIAL, numberUri));
651 menu.add(0, 0, 0, R.string.menu_sendTextMessage)
652 .setIntent(new Intent(Intent.ACTION_SENDTO,
653 Uri.fromParts("sms", number, null)));
655 if (!contactInfoPresent && numberUri != null && !isVoicemail) {
656 Intent intent = new Intent(Intent.ACTION_INSERT_OR_EDIT);
657 intent.setType(People.CONTENT_ITEM_TYPE);
658 intent.putExtra(Insert.PHONE, number);
659 menu.add(0, 0, 0, R.string.recentCalls_addToContact)
662 menu.add(0, MENU_ITEM_DELETE, 0, R.string.recentCalls_removeFromRecentList);
666 public boolean onOptionsItemSelected(MenuItem item) {
667 switch (item.getItemId()) {
668 case MENU_ITEM_DELETE_ALL: {
669 getContentResolver().delete(Calls.CONTENT_URI, null, null);
670 //TODO The change notification should do this automatically, but it isn't working
671 // right now. Remove this when the change notification is working properly.
676 case MENU_ITEM_VIEW_CONTACTS: {
677 Intent intent = new Intent(Intent.ACTION_VIEW, People.CONTENT_URI);
678 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
679 startActivity(intent);
683 return super.onOptionsItemSelected(item);
687 public boolean onContextItemSelected(MenuItem item) {
688 // Convert the menu info to the proper type
689 AdapterView.AdapterContextMenuInfo menuInfo;
691 menuInfo = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo();
692 } catch (ClassCastException e) {
693 Log.e(TAG, "bad menuInfoIn", e);
697 switch (item.getItemId()) {
698 case MENU_ITEM_DELETE: {
699 Cursor cursor = mAdapter.getCursor();
700 if (cursor != null) {
701 cursor.moveToPosition(menuInfo.position);
707 return super.onContextItemSelected(item);
711 public boolean onKeyDown(int keyCode, KeyEvent event) {
713 case KeyEvent.KEYCODE_CALL: {
714 long callPressDiff = SystemClock.uptimeMillis() - event.getDownTime();
715 if (callPressDiff >= ViewConfiguration.getLongPressTimeout()) {
716 // Launch voice dialer
717 Intent intent = new Intent(Intent.ACTION_VOICE_COMMAND);
718 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
720 startActivity(intent);
721 } catch (ActivityNotFoundException e) {
727 return super.onKeyDown(keyCode, event);
731 public boolean onKeyUp(int keyCode, KeyEvent event) {
733 case KeyEvent.KEYCODE_CALL:
735 ITelephony phone = ITelephony.Stub.asInterface(
736 ServiceManager.checkService("phone"));
737 if (phone != null && !phone.isIdle()) {
738 // Let the super class handle it
741 } catch (RemoteException re) {
742 // Fall through and try to call the contact
745 callEntry(getListView().getSelectedItemPosition());
748 return super.onKeyUp(keyCode, event);
752 * Get the number from the Contacts, if available, since sometimes
753 * the number provided by caller id may not be formatted properly
754 * depending on the carrier (roaming) in use at the time of the
756 * Logic : If the caller-id number starts with a "+", use it
757 * Else if the number in the contacts starts with a "+", use that one
758 * Else if the number in the contacts is longer, use that one
760 private String getBetterNumberFromContacts(String number) {
761 String matchingNumber = null;
762 // Look in the cache first. If it's not found then query the Phones db
763 ContactInfo ci = mAdapter.mContactInfo.get(number);
764 if (ci != null && ci != ContactInfo.EMPTY) {
765 matchingNumber = ci.number;
768 Cursor phonesCursor =
769 RecentCallsListActivity.this.getContentResolver().query(
770 Uri.withAppendedPath(Phones.CONTENT_FILTER_URL,
772 PHONES_PROJECTION, null, null, null);
773 if (phonesCursor != null) {
774 if (phonesCursor.moveToFirst()) {
775 matchingNumber = phonesCursor.getString(MATCHED_NUMBER_COLUMN_INDEX);
777 phonesCursor.close();
779 } catch (Exception e) {
780 // Use the number from the call log
783 if (!TextUtils.isEmpty(matchingNumber) &&
784 (matchingNumber.startsWith("+")
785 || matchingNumber.length() > number.length())) {
786 number = matchingNumber;
791 private void callEntry(int position) {
793 // In touch mode you may often not have something selected, so
794 // just call the first entry to make sure that [send] [send] calls the
795 // most recent entry.
798 final Cursor cursor = mAdapter.getCursor();
799 if (cursor != null && cursor.moveToPosition(position)) {
800 String number = cursor.getString(NUMBER_COLUMN_INDEX);
801 if (TextUtils.isEmpty(number)
802 || number.equals(CallerInfo.UNKNOWN_NUMBER)
803 || number.equals(CallerInfo.PRIVATE_NUMBER)
804 || number.equals(CallerInfo.PAYPHONE_NUMBER)) {
805 // This number can't be called, do nothing
809 int callType = cursor.getInt(CALL_TYPE_COLUMN_INDEX);
810 if (!number.startsWith("+") &&
811 (callType == Calls.INCOMING_TYPE
812 || callType == Calls.MISSED_TYPE)) {
813 // If the caller-id matches a contact with a better qualified number, use it
814 number = getBetterNumberFromContacts(number);
816 Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED,
817 Uri.fromParts("tel", number, null));
819 Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
820 startActivity(intent);
825 protected void onListItemClick(ListView l, View v, int position, long id) {
826 Intent intent = new Intent(this, CallDetailActivity.class);
827 intent.setData(ContentUris.withAppendedId(CallLog.Calls.CONTENT_URI, id));
828 startActivity(intent);