OSDN Git Service

8949f6e27caa47ca4f16fbfdbef0a0e4a3e4f3cc
[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.DISPLAY_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     RecentCallsAdapter mAdapter;
118     private QueryHandler mQueryHandler;
119     String mVoiceMailNumber;
120
121     static final class ContactInfo {
122         public long personId;
123         public String name;
124         public int type;
125         public String label;
126         public String number;
127
128         public static ContactInfo EMPTY = new ContactInfo();
129     }
130
131     public static final class RecentCallsListItemViews {
132         TextView line1View;
133         TextView labelView;
134         TextView numberView;
135         TextView dateView;
136         ImageView iconView;
137         View callView;
138     }
139
140     static final class CallerInfoQuery {
141         String number;
142         int position;
143         String name;
144         int numberType;
145         String numberLabel;
146     }
147
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;
160
161         private CharSequence[] mLabelArray;
162
163         private Drawable mDrawableIncoming;
164         private Drawable mDrawableOutgoing;
165         private Drawable mDrawableMissed;
166
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));
172             }
173         }
174
175         public boolean onPreDraw() {
176             if (mFirst) {
177                 mHandler.sendEmptyMessageDelayed(START_THREAD, 1000);
178                 mFirst = false;
179             }
180             return true;
181         }
182
183         private Handler mHandler = new Handler() {
184             @Override
185             public void handleMessage(Message msg) {
186                 switch (msg.what) {
187                     case REDRAW:
188                         notifyDataSetChanged();
189                         break;
190                     case START_THREAD:
191                         startRequestProcessing();
192                         break;
193                 }
194             }
195         };
196
197         public RecentCallsAdapter() {
198             super(RecentCallsListActivity.this, R.layout.recent_calls_list_item, null);
199
200             mContactInfo = new HashMap<String,ContactInfo>();
201             mRequests = new LinkedList<CallerInfoQuery>();
202             mPreDrawListener = null;
203
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);
211         }
212
213         void setLoading(boolean loading) {
214             mLoading = loading;
215         }
216
217         @Override
218         public boolean isEmpty() {
219             if (mLoading) {
220                 // We don't want the empty state to show when loading.
221                 return false;
222             } else {
223                 return super.isEmpty();
224             }
225         }
226
227         public ContactInfo getContactInfo(String number) {
228             return mContactInfo.get(number);
229         }
230
231         public void startRequestProcessing() {
232             mDone = false;
233             mCallerIdThread = new Thread(this);
234             mCallerIdThread.setPriority(Thread.MIN_PRIORITY);
235             mCallerIdThread.start();
236         }
237
238         public void stopRequestProcessing() {
239             mDone = true;
240             if (mCallerIdThread != null) mCallerIdThread.interrupt();
241         }
242
243         public void clearCache() {
244             synchronized (mContactInfo) {
245                 mContactInfo.clear();
246             }
247         }
248
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) {
254                 return;
255             }
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(
261                     Calls.CONTENT_URI,
262                     values, Calls.NUMBER + "='" + ciq.number + "'", null);
263         }
264
265         private void enqueueRequest(String number, int position,
266                 String name, int numberType, String numberLabel) {
267             CallerInfoQuery ciq = new CallerInfoQuery();
268             ciq.number = number;
269             ciq.position = position;
270             ciq.name = name;
271             ciq.numberType = numberType;
272             ciq.numberLabel = numberLabel;
273             synchronized (mRequests) {
274                 mRequests.add(ciq);
275                 mRequests.notifyAll();
276             }
277         }
278
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);
287                     }
288                 }
289             } else {
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);
303
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);
309                             }
310                         }
311                     }
312                     phonesCursor.close();
313                 }
314             }
315             if (info != null) {
316                 updateCallLog(ciq, info);
317             }
318         }
319
320         /*
321          * Handles requests for contact name and number type
322          * @see java.lang.Runnable#run()
323          */
324         public void run() {
325             while (!mDone) {
326                 CallerInfoQuery ciq = null;
327                 synchronized (mRequests) {
328                     if (!mRequests.isEmpty()) {
329                         ciq = mRequests.removeFirst();
330                     } else {
331                         try {
332                             mRequests.wait(1000);
333                         } catch (InterruptedException ie) {
334                             // Ignore and continue processing requests
335                         }
336                     }
337                 }
338                 if (ciq != null) {
339                     queryContactInfo(ciq);
340                 }
341             }
342         }
343
344         @Override
345         public View newView(Context context, Cursor cursor, ViewGroup parent) {
346             View view = super.newView(context, cursor, parent);
347
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);
357             
358             view.setTag(views);
359
360             return view;
361         }
362
363
364         @Override
365         public void bindView(View view, Context context, Cursor c) {
366             final RecentCallsListItemViews views = (RecentCallsListItemViews) view.getTag();
367
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);
372             
373             // Store away the number so we can call it directly if you click on the call icon
374             views.callView.setTag(number);
375
376             // Lookup contacts with this number
377             ContactInfo info = mContactInfo.get(number);
378             if (info == null) {
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
388                 // the calls db.
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);
395                 }
396             }
397
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)) {
405                 name = callerName;
406                 ntype = callerNumberType;
407                 label = callerNumberLabel;
408             }
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,
414                         mLabelArray);
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);
420                 } else {
421                     views.labelView.setVisibility(View.GONE);
422                 }
423             } else {
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);
432                 } else {
433                     // Just a raw number, format it to look pretty
434                     number = PhoneNumberUtils.formatNumber(number);
435                 }
436
437                 views.line1View.setText(number);
438                 views.numberView.setVisibility(View.GONE);
439                 views.labelView.setVisibility(View.GONE);
440             }
441
442             int type = c.getInt(CALL_TYPE_COLUMN_INDEX);
443             long date = c.getLong(DATE_COLUMN_INDEX);
444
445             // Set the date/time field by mixing relative and absolute times.
446             int flags = DateUtils.FORMAT_ABBREV_RELATIVE;
447             
448             views.dateView.setText(DateUtils.getRelativeTimeSpanString(date,
449                     System.currentTimeMillis(), DateUtils.MINUTE_IN_MILLIS, flags));
450
451             // Set the icon
452             switch (type) {
453                 case Calls.INCOMING_TYPE:
454                     views.iconView.setImageDrawable(mDrawableIncoming);
455                     break;
456
457                 case Calls.OUTGOING_TYPE:
458                     views.iconView.setImageDrawable(mDrawableOutgoing);
459                     break;
460
461                 case Calls.MISSED_TYPE:
462                     views.iconView.setImageDrawable(mDrawableMissed);
463                     break;
464             }
465
466             // Listen for the first draw
467             if (mPreDrawListener == null) {
468                 mFirst = true;
469                 mPreDrawListener = this;
470                 view.getViewTreeObserver().addOnPreDrawListener(this);
471             }
472         }
473     }
474
475     private static final class QueryHandler extends AsyncQueryHandler {
476         private final WeakReference<RecentCallsListActivity> mActivity;
477
478         public QueryHandler(Context context) {
479             super(context.getContentResolver());
480             mActivity = new WeakReference<RecentCallsListActivity>(
481                     (RecentCallsListActivity) context);
482         }
483
484         @Override
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);
491             } else {
492                 cursor.close();
493             }
494         }
495     }
496
497     @Override
498     protected void onCreate(Bundle state) {
499         super.onCreate(state);
500
501         setContentView(R.layout.recent_calls);
502
503         // Typing here goes to the dialer
504         setDefaultKeyMode(DEFAULT_KEYS_DIALER);
505
506         mAdapter = new RecentCallsAdapter();
507         getListView().setOnCreateContextMenuListener(this);
508         setListAdapter(mAdapter);
509
510         mVoiceMailNumber = ((TelephonyManager)getSystemService(Context.TELEPHONY_SERVICE))
511                 .getVoiceMailNumber();
512         mQueryHandler = new QueryHandler(this);
513     }
514     
515     @Override
516     protected void onResume() {
517         // The adapter caches looked up numbers, clear it so they will get
518         // looked up again.
519         if (mAdapter != null) {
520             mAdapter.clearCache();
521         }
522
523         startQuery();
524         resetNewCallsFlag();
525
526         super.onResume();
527
528         mAdapter.mPreDrawListener = null; // Let it restart the thread after next draw
529     }
530
531     @Override
532     protected void onPause() {
533         super.onPause();
534
535         // Kill the requests thread
536         mAdapter.stopRequestProcessing();
537     }
538
539     @Override
540     protected void onDestroy() {
541         super.onDestroy();
542         mAdapter.stopRequestProcessing();
543         Cursor cursor = mAdapter.getCursor();
544         if (cursor != null && !cursor.isClosed()) {
545             cursor.close();
546         }
547     }
548
549     @Override
550     public void onWindowFocusChanged(boolean hasFocus) {
551         super.onWindowFocusChanged(hasFocus);
552         
553         // Clear notifications only when window gains focus.  This activity won't
554         // immediately receive focus if the keyguard screen is above it.
555         if (hasFocus) {
556             try {
557                 ITelephony iTelephony =
558                         ITelephony.Stub.asInterface(ServiceManager.getService("phone"));
559                 if (iTelephony != null) {
560                     iTelephony.cancelMissedCallsNotification();
561                 } else {
562                     Log.w(TAG, "Telephony service is null, can't call " +
563                             "cancelMissedCallsNotification");
564                 }
565             } catch (RemoteException e) {
566                 Log.e(TAG, "Failed to clear missed calls notification due to remote exception");
567             }
568         }
569     }
570
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");
576
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);
581     }
582
583     private void startQuery() {
584         mAdapter.setLoading(true);
585
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);
590     }
591
592     @Override
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);
596         return true;
597     }
598
599     @Override
600     public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfoIn) {
601         AdapterView.AdapterContextMenuInfo menuInfo;
602         try {
603              menuInfo = (AdapterView.AdapterContextMenuInfo) menuInfoIn;
604         } catch (ClassCastException e) {
605             Log.e(TAG, "bad menuInfoIn", e);
606             return;
607         }
608
609         Cursor cursor = (Cursor) mAdapter.getItem(menuInfo.position);
610
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");
623             isVoicemail = true;
624         } else {
625             numberUri = Uri.fromParts("tel", number, null);
626         }
627
628         ContactInfo info = mAdapter.getContactInfo(number);
629         boolean contactInfoPresent = (info != null && info != ContactInfo.EMPTY);
630         if (contactInfoPresent) {
631             menu.setHeaderTitle(info.name);
632         } else {
633             menu.setHeaderTitle(number);
634         }
635
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))
639                     .setIntent(intent);
640         }
641
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)));
646         }
647
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)));
654         }
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)
660                     .setIntent(intent);
661         }
662         menu.add(0, MENU_ITEM_DELETE, 0, R.string.recentCalls_removeFromRecentList);
663     }
664
665     @Override
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.
672                 startQuery();
673                 return true;
674             }
675
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);
680                 return true;
681             }
682         }
683         return super.onOptionsItemSelected(item);
684     }
685
686     @Override
687     public boolean onContextItemSelected(MenuItem item) {
688         // Convert the menu info to the proper type
689         AdapterView.AdapterContextMenuInfo menuInfo;
690         try {
691              menuInfo = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo();
692         } catch (ClassCastException e) {
693             Log.e(TAG, "bad menuInfoIn", e);
694             return false;
695         }
696
697         switch (item.getItemId()) {
698             case MENU_ITEM_DELETE: {
699                 Cursor cursor = mAdapter.getCursor();
700                 if (cursor != null) {
701                     cursor.moveToPosition(menuInfo.position);
702                     cursor.deleteRow();
703                 }
704                 return true;
705             }
706         }
707         return super.onContextItemSelected(item);
708     }
709
710     @Override
711     public boolean onKeyDown(int keyCode, KeyEvent event) {
712         switch (keyCode) {
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);
719                     try {
720                         startActivity(intent);
721                     } catch (ActivityNotFoundException e) {
722                     }
723                     return true;
724                 }
725             }
726         }
727         return super.onKeyDown(keyCode, event);
728     }
729
730     @Override
731     public boolean onKeyUp(int keyCode, KeyEvent event) {
732         switch (keyCode) {
733             case KeyEvent.KEYCODE_CALL:
734                 try {
735                     ITelephony phone = ITelephony.Stub.asInterface(
736                             ServiceManager.checkService("phone"));
737                     if (phone != null && !phone.isIdle()) {
738                         // Let the super class handle it
739                         break;
740                     }
741                 } catch (RemoteException re) {
742                     // Fall through and try to call the contact
743                 }
744
745                 callEntry(getListView().getSelectedItemPosition());
746                 return true;
747         }
748         return super.onKeyUp(keyCode, event);
749     }
750
751     /*
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
755      * incoming call.
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
759      */
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;
766         } else {
767             try {
768                 Cursor phonesCursor =
769                     RecentCallsListActivity.this.getContentResolver().query(
770                             Uri.withAppendedPath(Phones.CONTENT_FILTER_URL,
771                                     number),
772                     PHONES_PROJECTION, null, null, null);
773                 if (phonesCursor != null) {
774                     if (phonesCursor.moveToFirst()) {
775                         matchingNumber = phonesCursor.getString(MATCHED_NUMBER_COLUMN_INDEX);
776                     }
777                     phonesCursor.close();
778                 }
779             } catch (Exception e) {
780                 // Use the number from the call log
781             }
782         }
783         if (!TextUtils.isEmpty(matchingNumber) &&
784                 (matchingNumber.startsWith("+")
785                         || matchingNumber.length() > number.length())) {
786             number = matchingNumber;
787         }
788         return number;
789     }
790
791     private void callEntry(int position) {
792         if (position < 0) {
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.
796             position = 0;
797         }
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
806                 return;
807             }
808
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);
815             }
816             Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED,
817                     Uri.fromParts("tel", number, null));
818             intent.setFlags(
819                     Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
820             startActivity(intent);
821         }
822     }
823
824     @Override
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);
829     }
830 }