OSDN Git Service

auto import from //depot/cupcake/@132589
[android-x86/packages-apps-Contacts.git] / src / com / android / contacts / RecentCallsListActivity.java
1 /*
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
17 package com.android.contacts;
18
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;
59
60 import com.android.internal.telephony.CallerInfo;
61 import com.android.internal.telephony.ITelephony;
62
63 import java.util.HashMap;
64 import java.util.LinkedList;
65 import java.lang.ref.WeakReference;
66
67 /**
68  * Displays a list of call log entries.
69  */
70 public class RecentCallsListActivity extends ListActivity
71         implements View.OnCreateContextMenuListener {
72     private static final String TAG = "RecentCallsList";
73
74     /** The projection to use when querying the call log table */
75     static final String[] CALL_LOG_PROJECTION = new String[] {
76             Calls._ID,
77             Calls.NUMBER,
78             Calls.DATE,
79             Calls.DURATION,
80             Calls.TYPE,
81             Calls.CACHED_NAME,
82             Calls.CACHED_NUMBER_TYPE,
83             Calls.CACHED_NUMBER_LABEL
84     };
85
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;
94
95     /** The projection to use when querying the phones table */
96     static final String[] PHONES_PROJECTION = new String[] {
97             Phones.PERSON_ID,
98             Phones.NAME,
99             Phones.TYPE,
100             Phones.LABEL,
101             Phones.NUMBER
102     };
103
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;
109
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;
113
114     private static final int QUERY_TOKEN = 53;
115     private static final int UPDATE_TOKEN = 54;
116
117     private RecentCallsAdapter mAdapter;
118     private QueryHandler mQueryHandler;
119     private String mVoiceMailNumber;
120
121     private CharSequence[] mLabelArray;
122
123     private Drawable mDrawableIncoming;
124     private Drawable mDrawableOutgoing;
125     private Drawable mDrawableMissed;
126
127     private static final class ContactInfo {
128         public long personId;
129         public String name;
130         public int type;
131         public String label;
132         public String number;
133
134         public static ContactInfo EMPTY = new ContactInfo();
135     }
136
137     public static final class RecentCallsListItemViews {
138         TextView line1View;
139         TextView line2View;
140         TextView durationView;
141         TextView dateView;
142         ImageView iconView;
143     }
144
145     private static final class CallerInfoQuery {
146         String number;
147         int position;
148         String name;
149         int numberType;
150         String numberLabel;
151     }
152
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;
165
166         public boolean onPreDraw() {
167             if (mFirst) {
168                 mHandler.sendEmptyMessageDelayed(START_THREAD, 1000);
169                 mFirst = false;
170             }
171             return true;
172         }
173
174         private Handler mHandler = new Handler() {
175             @Override
176             public void handleMessage(Message msg) {
177                 switch (msg.what) {
178                     case REDRAW:
179                         notifyDataSetChanged();
180                         break;
181                     case START_THREAD:
182                         startRequestProcessing();
183                         break;
184                 }
185             }
186         };
187
188         public RecentCallsAdapter() {
189             super(RecentCallsListActivity.this, R.layout.recent_calls_list_item, null);
190
191             mContactInfo = new HashMap<String,ContactInfo>();
192             mRequests = new LinkedList<CallerInfoQuery>();
193             mPreDrawListener = null;
194         }
195
196         void setLoading(boolean loading) {
197             mLoading = loading;
198         }
199
200         @Override
201         public boolean isEmpty() {
202             if (mLoading) {
203                 // We don't want the empty state to show when loading.
204                 return false;
205             } else {
206                 return super.isEmpty();
207             }
208         }
209
210         public ContactInfo getContactInfo(String number) {
211             return mContactInfo.get(number);
212         }
213
214         public void startRequestProcessing() {
215             mDone = false;
216             mCallerIdThread = new Thread(this);
217             mCallerIdThread.setPriority(Thread.MIN_PRIORITY);
218             mCallerIdThread.start();
219         }
220
221         public void stopRequestProcessing() {
222             mDone = true;
223             if (mCallerIdThread != null) mCallerIdThread.interrupt();
224         }
225
226         public void clearCache() {
227             synchronized (mContactInfo) {
228                 mContactInfo.clear();
229             }
230         }
231
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) {
237                 return;
238             }
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(
244                     Calls.CONTENT_URI,
245                     values, Calls.NUMBER + "='" + ciq.number + "'", null);
246         }
247
248         private void enqueueRequest(String number, int position,
249                 String name, int numberType, String numberLabel) {
250             CallerInfoQuery ciq = new CallerInfoQuery();
251             ciq.number = number;
252             ciq.position = position;
253             ciq.name = name;
254             ciq.numberType = numberType;
255             ciq.numberLabel = numberLabel;
256             synchronized (mRequests) {
257                 mRequests.add(ciq);
258                 mRequests.notifyAll();
259             }
260         }
261
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);
270                     }
271                 }
272             } else {
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);
286
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);
292                             }
293                         }
294                     }
295                     phonesCursor.close();
296                 }
297             }
298             if (info != null) {
299                 updateCallLog(ciq, info);
300             }
301         }
302
303         /*
304          * Handles requests for contact name and number type
305          * @see java.lang.Runnable#run()
306          */
307         public void run() {
308             while (!mDone) {
309                 CallerInfoQuery ciq = null;
310                 synchronized (mRequests) {
311                     if (!mRequests.isEmpty()) {
312                         ciq = mRequests.removeFirst();
313                     } else {
314                         try {
315                             mRequests.wait(1000);
316                         } catch (InterruptedException ie) {
317                             // Ignore and continue processing requests
318                         }
319                     }
320                 }
321                 if (ciq != null) {
322                     queryContactInfo(ciq);
323                 }
324             }
325         }
326
327         @Override
328         public View newView(Context context, Cursor cursor, ViewGroup parent) {
329             View view = super.newView(context, cursor, parent);
330
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);
338
339             view.setTag(views);
340
341             return view;
342         }
343
344
345         @Override
346         public void bindView(View view, Context context, Cursor c) {
347             final RecentCallsListItemViews views = (RecentCallsListItemViews) view.getTag();
348
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);
353
354             // Lookup contacts with this number
355             ContactInfo info = mContactInfo.get(number);
356             if (info == null) {
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
366                 // the calls db.
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);
373                 }
374             }
375
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)) {
383                 name = callerName;
384                 ntype = callerNumberType;
385                 label = callerNumberLabel;
386             }
387             // Set the text lines
388             if (!TextUtils.isEmpty(name)) {
389                 views.line1View.setText(name);
390                 CharSequence numberLabel = Phones.getDisplayLabel(context, ntype, label,
391                         mLabelArray);
392                 if (!TextUtils.isEmpty(numberLabel)) {
393                     views.line2View.setText(numberLabel);
394                 } else {
395                     views.line2View.setText(number);
396                 }
397
398                 // Set the presence icon
399 /*
400                 int serverStatus;
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)),
406                             null, null, null);
407                 } else {
408                     views.line2View.setCompoundDrawablesWithIntrinsicBounds(null, null, null, null);
409                 }
410 */
411             } else {
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);
418                 } else {
419                     // Just a raw number, format it to look pretty
420                     number = PhoneNumberUtils.formatNumber(number);
421                 }
422
423                 views.line1View.setText(number);
424                 views.line2View.setText(null);
425
426                 // Clear the presence icon
427 //                views.line2View.setCompoundDrawablesWithIntrinsicBounds(null, null, null, null);
428             }
429
430             int type = c.getInt(CALL_TYPE_COLUMN_INDEX);
431             long date = c.getLong(DATE_COLUMN_INDEX);
432
433             // Set the duration
434             if (type == Calls.MISSED_TYPE) {
435                 views.durationView.setVisibility(View.GONE);
436             } else {
437                 views.durationView.setVisibility(View.VISIBLE);
438                 views.durationView.setText(DateUtils.formatElapsedTime(c.getLong(DURATION_COLUMN_INDEX)));
439             }
440
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;
444             
445             views.dateView.setText(DateUtils.getRelativeDateTimeString(context, date,
446                     DateUtils.MINUTE_IN_MILLIS, DateUtils.DAY_IN_MILLIS * 2, flags));
447
448             // Set the icon
449             switch (type) {
450                 case Calls.INCOMING_TYPE:
451                     views.iconView.setImageDrawable(mDrawableIncoming);
452                     break;
453
454                 case Calls.OUTGOING_TYPE:
455                     views.iconView.setImageDrawable(mDrawableOutgoing);
456                     break;
457
458                 case Calls.MISSED_TYPE:
459                     views.iconView.setImageDrawable(mDrawableMissed);
460                     break;
461             }
462             // Listen for the first draw
463             if (mPreDrawListener == null) {
464                 mFirst = true;
465                 mPreDrawListener = this;
466                 view.getViewTreeObserver().addOnPreDrawListener(this);
467             }
468         }
469     }
470
471     private static final class QueryHandler extends AsyncQueryHandler {
472         private final WeakReference<RecentCallsListActivity> mActivity;
473
474         public QueryHandler(Context context) {
475             super(context.getContentResolver());
476             mActivity = new WeakReference<RecentCallsListActivity>(
477                     (RecentCallsListActivity) context);
478         }
479
480         @Override
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);
487             } else {
488                 cursor.close();
489             }
490         }
491     }
492
493     @Override
494     protected void onCreate(Bundle state) {
495         super.onCreate(state);
496
497         setContentView(R.layout.recent_calls);
498
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);
503
504         // Typing here goes to the dialer
505         setDefaultKeyMode(DEFAULT_KEYS_DIALER);
506
507         mAdapter = new RecentCallsAdapter();
508         getListView().setOnCreateContextMenuListener(this);
509         setListAdapter(mAdapter);
510
511         mVoiceMailNumber = ((TelephonyManager)getSystemService(Context.TELEPHONY_SERVICE))
512                 .getVoiceMailNumber();
513         mQueryHandler = new QueryHandler(this);
514     }
515     
516     @Override
517     protected void onResume() {
518         // The adapter caches looked up numbers, clear it so they will get
519         // looked up again.
520         if (mAdapter != null) {
521             mAdapter.clearCache();
522         }
523
524         startQuery();
525         resetNewCallsFlag();
526
527         super.onResume();
528
529         mAdapter.mPreDrawListener = null; // Let it restart the thread after next draw
530     }
531
532     @Override
533     protected void onPause() {
534         super.onPause();
535
536         // Kill the requests thread
537         mAdapter.stopRequestProcessing();
538     }
539
540     @Override
541     protected void onDestroy() {
542         super.onDestroy();
543         mAdapter.stopRequestProcessing();
544         Cursor cursor = mAdapter.getCursor();
545         if (cursor != null && !cursor.isClosed()) {
546             cursor.close();
547         }
548     }
549
550     @Override
551     public void onWindowFocusChanged(boolean hasFocus) {
552         super.onWindowFocusChanged(hasFocus);
553         
554         // Clear notifications only when window gains focus.  This activity won't
555         // immediately receive focus if the keyguard screen is above it.
556         if (hasFocus) {
557             try {
558                 ITelephony iTelephony =
559                         ITelephony.Stub.asInterface(ServiceManager.getService("phone"));
560                 if (iTelephony != null) {
561                     iTelephony.cancelMissedCallsNotification();
562                 } else {
563                     Log.w(TAG, "Telephony service is null, can't call " +
564                             "cancelMissedCallsNotification");
565                 }
566             } catch (RemoteException e) {
567                 Log.e(TAG, "Failed to clear missed calls notification due to remote exception");
568             }
569         }
570     }
571
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");
577
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);
582     }
583
584     private void startQuery() {
585         mAdapter.setLoading(true);
586
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);
591     }
592
593     @Override
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);
597         return true;
598     }
599
600     @Override
601     public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfoIn) {
602         AdapterView.AdapterContextMenuInfo menuInfo;
603         try {
604              menuInfo = (AdapterView.AdapterContextMenuInfo) menuInfoIn;
605         } catch (ClassCastException e) {
606             Log.e(TAG, "bad menuInfoIn", e);
607             return;
608         }
609
610         Cursor cursor = (Cursor) mAdapter.getItem(menuInfo.position);
611
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");
622             isVoicemail = true;
623         } else {
624             numberUri = Uri.fromParts("tel", number, null);
625         }
626
627         ContactInfo info = mAdapter.getContactInfo(number);
628         boolean contactInfoPresent = (info != null && info != ContactInfo.EMPTY);
629         if (contactInfoPresent) {
630             menu.setHeaderTitle(info.name);
631         } else {
632             menu.setHeaderTitle(number);
633         }
634
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))
638                     .setIntent(intent);
639         }
640
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)));
645         }
646
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)));
653         }
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)
659                     .setIntent(intent);
660         }
661         menu.add(0, MENU_ITEM_DELETE, 0, R.string.recentCalls_removeFromRecentList);
662     }
663
664     @Override
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.
671                 startQuery();
672                 return true;
673             }
674
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);
679                 return true;
680             }
681         }
682         return super.onOptionsItemSelected(item);
683     }
684
685     @Override
686     public boolean onContextItemSelected(MenuItem item) {
687         // Convert the menu info to the proper type
688         AdapterView.AdapterContextMenuInfo menuInfo;
689         try {
690              menuInfo = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo();
691         } catch (ClassCastException e) {
692             Log.e(TAG, "bad menuInfoIn", e);
693             return false;
694         }
695
696         switch (item.getItemId()) {
697             case MENU_ITEM_DELETE: {
698                 Cursor cursor = mAdapter.getCursor();
699                 if (cursor != null) {
700                     cursor.moveToPosition(menuInfo.position);
701                     cursor.deleteRow();
702                 }
703                 return true;
704             }
705         }
706         return super.onContextItemSelected(item);
707     }
708
709     @Override
710     public boolean onKeyDown(int keyCode, KeyEvent event) {
711         switch (keyCode) {
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);
718                     try {
719                         startActivity(intent);
720                     } catch (ActivityNotFoundException e) {
721                     }
722                     return true;
723                 }
724             }
725         }
726         return super.onKeyDown(keyCode, event);
727     }
728
729     @Override
730     public boolean onKeyUp(int keyCode, KeyEvent event) {
731         switch (keyCode) {
732             case KeyEvent.KEYCODE_CALL:
733                 try {
734                     ITelephony phone = ITelephony.Stub.asInterface(
735                             ServiceManager.checkService("phone"));
736                     if (phone != null && !phone.isIdle()) {
737                         // Let the super class handle it
738                         break;
739                     }
740                 } catch (RemoteException re) {
741                     // Fall through and try to call the contact
742                 }
743
744                 callEntry(getListView().getSelectedItemPosition());
745                 return true;
746         }
747         return super.onKeyUp(keyCode, event);
748     }
749
750     /*
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
754      * incoming call.
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
758      */
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;
765         } else {
766             try {
767                 Cursor phonesCursor =
768                     RecentCallsListActivity.this.getContentResolver().query(
769                             Uri.withAppendedPath(Phones.CONTENT_FILTER_URL,
770                                     number),
771                     PHONES_PROJECTION, null, null, null);
772                 if (phonesCursor != null) {
773                     if (phonesCursor.moveToFirst()) {
774                         matchingNumber = phonesCursor.getString(MATCHED_NUMBER_COLUMN_INDEX);
775                     }
776                     phonesCursor.close();
777                 }
778             } catch (Exception e) {
779                 // Use the number from the call log
780             }
781         }
782         if (!TextUtils.isEmpty(matchingNumber) &&
783                 (matchingNumber.startsWith("+")
784                         || matchingNumber.length() > number.length())) {
785             number = matchingNumber;
786         }
787         return number;
788     }
789
790     private void callEntry(int position) {
791         if (position < 0) {
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.
795             position = 0;
796         }
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
804                 return;
805             }
806
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);
813             }
814             Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED,
815                     Uri.fromParts("tel", number, null));
816             intent.setFlags(
817                     Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
818             startActivity(intent);
819         }
820     }
821
822     @Override
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);
827     }
828 }