summaryrefslogtreecommitdiffstats
path: root/src/com/android/contacts/RecentCallsListActivity.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/contacts/RecentCallsListActivity.java')
-rw-r--r--src/com/android/contacts/RecentCallsListActivity.java828
1 files changed, 828 insertions, 0 deletions
diff --git a/src/com/android/contacts/RecentCallsListActivity.java b/src/com/android/contacts/RecentCallsListActivity.java
new file mode 100644
index 000000000..dbf287987
--- /dev/null
+++ b/src/com/android/contacts/RecentCallsListActivity.java
@@ -0,0 +1,828 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.contacts;
+
+import android.app.ListActivity;
+import android.content.ActivityNotFoundException;
+import android.content.AsyncQueryHandler;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.database.Cursor;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.provider.CallLog;
+import android.provider.CallLog.Calls;
+import android.provider.Contacts.People;
+import android.provider.Contacts.Phones;
+import android.provider.Contacts.Intents.Insert;
+import android.telephony.PhoneNumberUtils;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+import android.text.format.DateUtils;
+import android.util.Log;
+import android.view.ContextMenu;
+import android.view.KeyEvent;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
+import android.view.ContextMenu.ContextMenuInfo;
+import android.widget.AdapterView;
+import android.widget.ImageView;
+import android.widget.ListView;
+import android.widget.ResourceCursorAdapter;
+import android.widget.TextView;
+
+import com.android.internal.telephony.CallerInfo;
+import com.android.internal.telephony.ITelephony;
+
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.lang.ref.WeakReference;
+
+/**
+ * Displays a list of call log entries.
+ */
+public class RecentCallsListActivity extends ListActivity
+ implements View.OnCreateContextMenuListener {
+ private static final String TAG = "RecentCallsList";
+
+ /** The projection to use when querying the call log table */
+ static final String[] CALL_LOG_PROJECTION = new String[] {
+ Calls._ID,
+ Calls.NUMBER,
+ Calls.DATE,
+ Calls.DURATION,
+ Calls.TYPE,
+ Calls.CACHED_NAME,
+ Calls.CACHED_NUMBER_TYPE,
+ Calls.CACHED_NUMBER_LABEL
+ };
+
+ static final int ID_COLUMN_INDEX = 0;
+ static final int NUMBER_COLUMN_INDEX = 1;
+ static final int DATE_COLUMN_INDEX = 2;
+ static final int DURATION_COLUMN_INDEX = 3;
+ static final int CALL_TYPE_COLUMN_INDEX = 4;
+ static final int CALLER_NAME_COLUMN_INDEX = 5;
+ static final int CALLER_NUMBERTYPE_COLUMN_INDEX = 6;
+ static final int CALLER_NUMBERLABEL_COLUMN_INDEX = 7;
+
+ /** The projection to use when querying the phones table */
+ static final String[] PHONES_PROJECTION = new String[] {
+ Phones.PERSON_ID,
+ Phones.DISPLAY_NAME,
+ Phones.TYPE,
+ Phones.LABEL,
+ Phones.NUMBER
+ };
+
+ static final int PERSON_ID_COLUMN_INDEX = 0;
+ static final int NAME_COLUMN_INDEX = 1;
+ static final int PHONE_TYPE_COLUMN_INDEX = 2;
+ static final int LABEL_COLUMN_INDEX = 3;
+ static final int MATCHED_NUMBER_COLUMN_INDEX = 4;
+
+ private static final int MENU_ITEM_DELETE = 1;
+ private static final int MENU_ITEM_DELETE_ALL = 2;
+ private static final int MENU_ITEM_VIEW_CONTACTS = 3;
+
+ private static final int QUERY_TOKEN = 53;
+ private static final int UPDATE_TOKEN = 54;
+
+ private RecentCallsAdapter mAdapter;
+ private QueryHandler mQueryHandler;
+ private String mVoiceMailNumber;
+
+ private CharSequence[] mLabelArray;
+
+ private Drawable mDrawableIncoming;
+ private Drawable mDrawableOutgoing;
+ private Drawable mDrawableMissed;
+
+ private static final class ContactInfo {
+ public long personId;
+ public String name;
+ public int type;
+ public String label;
+ public String number;
+
+ public static ContactInfo EMPTY = new ContactInfo();
+ }
+
+ public static final class RecentCallsListItemViews {
+ TextView line1View;
+ TextView line2View;
+ TextView durationView;
+ TextView dateView;
+ ImageView iconView;
+ }
+
+ private static final class CallerInfoQuery {
+ String number;
+ int position;
+ String name;
+ int numberType;
+ String numberLabel;
+ }
+
+ /** Adapter class to fill in data for the Call Log */
+ private final class RecentCallsAdapter extends ResourceCursorAdapter
+ implements Runnable, ViewTreeObserver.OnPreDrawListener {
+ HashMap<String,ContactInfo> mContactInfo;
+ private final LinkedList<CallerInfoQuery> mRequests;
+ private volatile boolean mDone;
+ private boolean mLoading = true;
+ ViewTreeObserver.OnPreDrawListener mPreDrawListener;
+ private static final int REDRAW = 1;
+ private static final int START_THREAD = 2;
+ private boolean mFirst;
+ private Thread mCallerIdThread;
+
+ public boolean onPreDraw() {
+ if (mFirst) {
+ mHandler.sendEmptyMessageDelayed(START_THREAD, 1000);
+ mFirst = false;
+ }
+ return true;
+ }
+
+ private Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case REDRAW:
+ notifyDataSetChanged();
+ break;
+ case START_THREAD:
+ startRequestProcessing();
+ break;
+ }
+ }
+ };
+
+ public RecentCallsAdapter() {
+ super(RecentCallsListActivity.this, R.layout.recent_calls_list_item, null);
+
+ mContactInfo = new HashMap<String,ContactInfo>();
+ mRequests = new LinkedList<CallerInfoQuery>();
+ mPreDrawListener = null;
+ }
+
+ void setLoading(boolean loading) {
+ mLoading = loading;
+ }
+
+ @Override
+ public boolean isEmpty() {
+ if (mLoading) {
+ // We don't want the empty state to show when loading.
+ return false;
+ } else {
+ return super.isEmpty();
+ }
+ }
+
+ public ContactInfo getContactInfo(String number) {
+ return mContactInfo.get(number);
+ }
+
+ public void startRequestProcessing() {
+ mDone = false;
+ mCallerIdThread = new Thread(this);
+ mCallerIdThread.setPriority(Thread.MIN_PRIORITY);
+ mCallerIdThread.start();
+ }
+
+ public void stopRequestProcessing() {
+ mDone = true;
+ if (mCallerIdThread != null) mCallerIdThread.interrupt();
+ }
+
+ public void clearCache() {
+ synchronized (mContactInfo) {
+ mContactInfo.clear();
+ }
+ }
+
+ private void updateCallLog(CallerInfoQuery ciq, ContactInfo ci) {
+ // Check if they are different. If not, don't update.
+ if (TextUtils.equals(ciq.name, ci.name)
+ && TextUtils.equals(ciq.numberLabel, ci.label)
+ && ciq.numberType == ci.type) {
+ return;
+ }
+ ContentValues values = new ContentValues(3);
+ values.put(Calls.CACHED_NAME, ci.name);
+ values.put(Calls.CACHED_NUMBER_TYPE, ci.type);
+ values.put(Calls.CACHED_NUMBER_LABEL, ci.label);
+ RecentCallsListActivity.this.getContentResolver().update(
+ Calls.CONTENT_URI,
+ values, Calls.NUMBER + "='" + ciq.number + "'", null);
+ }
+
+ private void enqueueRequest(String number, int position,
+ String name, int numberType, String numberLabel) {
+ CallerInfoQuery ciq = new CallerInfoQuery();
+ ciq.number = number;
+ ciq.position = position;
+ ciq.name = name;
+ ciq.numberType = numberType;
+ ciq.numberLabel = numberLabel;
+ synchronized (mRequests) {
+ mRequests.add(ciq);
+ mRequests.notifyAll();
+ }
+ }
+
+ private void queryContactInfo(CallerInfoQuery ciq) {
+ // First check if there was a prior request for the same number
+ // that was already satisfied
+ ContactInfo info = mContactInfo.get(ciq.number);
+ if (info != null && info != ContactInfo.EMPTY) {
+ synchronized (mRequests) {
+ if (mRequests.isEmpty()) {
+ mHandler.sendEmptyMessage(REDRAW);
+ }
+ }
+ } else {
+ Cursor phonesCursor =
+ RecentCallsListActivity.this.getContentResolver().query(
+ Uri.withAppendedPath(Phones.CONTENT_FILTER_URL,
+ Uri.encode(ciq.number)),
+ PHONES_PROJECTION, null, null, null);
+ if (phonesCursor != null) {
+ if (phonesCursor.moveToFirst()) {
+ info = new ContactInfo();
+ info.personId = phonesCursor.getLong(PERSON_ID_COLUMN_INDEX);
+ info.name = phonesCursor.getString(NAME_COLUMN_INDEX);
+ info.type = phonesCursor.getInt(PHONE_TYPE_COLUMN_INDEX);
+ info.label = phonesCursor.getString(LABEL_COLUMN_INDEX);
+ info.number = phonesCursor.getString(MATCHED_NUMBER_COLUMN_INDEX);
+
+ mContactInfo.put(ciq.number, info);
+ // Inform list to update this item, if in view
+ synchronized (mRequests) {
+ if (mRequests.isEmpty()) {
+ mHandler.sendEmptyMessage(REDRAW);
+ }
+ }
+ }
+ phonesCursor.close();
+ }
+ }
+ if (info != null) {
+ updateCallLog(ciq, info);
+ }
+ }
+
+ /*
+ * Handles requests for contact name and number type
+ * @see java.lang.Runnable#run()
+ */
+ public void run() {
+ while (!mDone) {
+ CallerInfoQuery ciq = null;
+ synchronized (mRequests) {
+ if (!mRequests.isEmpty()) {
+ ciq = mRequests.removeFirst();
+ } else {
+ try {
+ mRequests.wait(1000);
+ } catch (InterruptedException ie) {
+ // Ignore and continue processing requests
+ }
+ }
+ }
+ if (ciq != null) {
+ queryContactInfo(ciq);
+ }
+ }
+ }
+
+ @Override
+ public View newView(Context context, Cursor cursor, ViewGroup parent) {
+ View view = super.newView(context, cursor, parent);
+
+ // Get the views to bind to
+ RecentCallsListItemViews views = new RecentCallsListItemViews();
+ views.line1View = (TextView) view.findViewById(R.id.line1);
+ views.line2View = (TextView) view.findViewById(R.id.line2);
+ views.durationView = (TextView) view.findViewById(R.id.duration);
+ views.dateView = (TextView) view.findViewById(R.id.date);
+ views.iconView = (ImageView) view.findViewById(R.id.call_type_icon);
+
+ view.setTag(views);
+
+ return view;
+ }
+
+
+ @Override
+ public void bindView(View view, Context context, Cursor c) {
+ final RecentCallsListItemViews views = (RecentCallsListItemViews) view.getTag();
+
+ String number = c.getString(NUMBER_COLUMN_INDEX);
+ String callerName = c.getString(CALLER_NAME_COLUMN_INDEX);
+ int callerNumberType = c.getInt(CALLER_NUMBERTYPE_COLUMN_INDEX);
+ String callerNumberLabel = c.getString(CALLER_NUMBERLABEL_COLUMN_INDEX);
+
+ // Lookup contacts with this number
+ ContactInfo info = mContactInfo.get(number);
+ if (info == null) {
+ // Mark it as empty and queue up a request to find the name
+ // The db request should happen on a non-UI thread
+ info = ContactInfo.EMPTY;
+ mContactInfo.put(number, info);
+ enqueueRequest(number, c.getPosition(),
+ callerName, callerNumberType, callerNumberLabel);
+ } else if (info != ContactInfo.EMPTY) { // Has been queried
+ // Check if any data is different from the data cached in the
+ // calls db. If so, queue the request so that we can update
+ // the calls db.
+ if (!TextUtils.equals(info.name, callerName)
+ || info.type != callerNumberType
+ || !TextUtils.equals(info.label, callerNumberLabel)) {
+ // Something is amiss, so sync up.
+ enqueueRequest(number, c.getPosition(),
+ callerName, callerNumberType, callerNumberLabel);
+ }
+ }
+
+ String name = info.name;
+ int ntype = info.type;
+ String label = info.label;
+ // If there's no name cached in our hashmap, but there's one in the
+ // calls db, use the one in the calls db. Otherwise the name in our
+ // hashmap is more recent, so it has precedence.
+ if (TextUtils.isEmpty(name) && !TextUtils.isEmpty(callerName)) {
+ name = callerName;
+ ntype = callerNumberType;
+ label = callerNumberLabel;
+ }
+ // Set the text lines
+ if (!TextUtils.isEmpty(name)) {
+ views.line1View.setText(name);
+ CharSequence numberLabel = Phones.getDisplayLabel(context, ntype, label,
+ mLabelArray);
+ if (!TextUtils.isEmpty(numberLabel)) {
+ views.line2View.setText(numberLabel);
+ } else {
+ views.line2View.setText(number);
+ }
+
+ // Set the presence icon
+/*
+ int serverStatus;
+ if (!c.isNull(SERVER_STATUS_COLUMN_INDEX)) {
+ serverStatus = c.getInt(SERVER_STATUS_COLUMN_INDEX);
+ views.line2View.setCompoundDrawablesWithIntrinsicBounds(
+ getResources().getDrawable(
+ Presence.getPresenceIconResourceId(serverStatus)),
+ null, null, null);
+ } else {
+ views.line2View.setCompoundDrawablesWithIntrinsicBounds(null, null, null, null);
+ }
+*/
+ } else {
+ if (number.equals(CallerInfo.UNKNOWN_NUMBER)) {
+ number = getString(R.string.unknown);
+ } else if (number.equals(CallerInfo.PRIVATE_NUMBER)) {
+ number = getString(R.string.private_num);
+ } else if (number.equals(mVoiceMailNumber)) {
+ number = getString(R.string.voicemail);
+ } else {
+ // Just a raw number, format it to look pretty
+ number = PhoneNumberUtils.formatNumber(number);
+ }
+
+ views.line1View.setText(number);
+ views.line2View.setText(null);
+
+ // Clear the presence icon
+// views.line2View.setCompoundDrawablesWithIntrinsicBounds(null, null, null, null);
+ }
+
+ int type = c.getInt(CALL_TYPE_COLUMN_INDEX);
+ long date = c.getLong(DATE_COLUMN_INDEX);
+
+ // Set the duration
+ if (type == Calls.MISSED_TYPE) {
+ views.durationView.setVisibility(View.GONE);
+ } else {
+ views.durationView.setVisibility(View.VISIBLE);
+ views.durationView.setText(DateUtils.formatElapsedTime(c.getLong(DURATION_COLUMN_INDEX)));
+ }
+
+ // Set the date/time field by mixing relative and absolute times.
+ int flags = DateUtils.FORMAT_ABBREV_RELATIVE | DateUtils.FORMAT_SHOW_DATE
+ | DateUtils.FORMAT_ABBREV_MONTH;
+
+ views.dateView.setText(DateUtils.getRelativeDateTimeString(context, date,
+ DateUtils.MINUTE_IN_MILLIS, DateUtils.DAY_IN_MILLIS * 2, flags));
+
+ // Set the icon
+ switch (type) {
+ case Calls.INCOMING_TYPE:
+ views.iconView.setImageDrawable(mDrawableIncoming);
+ break;
+
+ case Calls.OUTGOING_TYPE:
+ views.iconView.setImageDrawable(mDrawableOutgoing);
+ break;
+
+ case Calls.MISSED_TYPE:
+ views.iconView.setImageDrawable(mDrawableMissed);
+ break;
+ }
+ // Listen for the first draw
+ if (mPreDrawListener == null) {
+ mFirst = true;
+ mPreDrawListener = this;
+ view.getViewTreeObserver().addOnPreDrawListener(this);
+ }
+ }
+ }
+
+ private static final class QueryHandler extends AsyncQueryHandler {
+ private final WeakReference<RecentCallsListActivity> mActivity;
+
+ public QueryHandler(Context context) {
+ super(context.getContentResolver());
+ mActivity = new WeakReference<RecentCallsListActivity>(
+ (RecentCallsListActivity) context);
+ }
+
+ @Override
+ protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
+ final RecentCallsListActivity activity = mActivity.get();
+ if (activity != null && !activity.isFinishing()) {
+ final RecentCallsListActivity.RecentCallsAdapter callsAdapter = activity.mAdapter;
+ callsAdapter.setLoading(false);
+ callsAdapter.changeCursor(cursor);
+ } else {
+ cursor.close();
+ }
+ }
+ }
+
+ @Override
+ protected void onCreate(Bundle state) {
+ super.onCreate(state);
+
+ setContentView(R.layout.recent_calls);
+
+ mDrawableIncoming = getResources().getDrawable(android.R.drawable.sym_call_incoming);
+ mDrawableOutgoing = getResources().getDrawable(android.R.drawable.sym_call_outgoing);
+ mDrawableMissed = getResources().getDrawable(android.R.drawable.sym_call_missed);
+ mLabelArray = getResources().getTextArray(com.android.internal.R.array.phoneTypes);
+
+ // Typing here goes to the dialer
+ setDefaultKeyMode(DEFAULT_KEYS_DIALER);
+
+ mAdapter = new RecentCallsAdapter();
+ getListView().setOnCreateContextMenuListener(this);
+ setListAdapter(mAdapter);
+
+ mVoiceMailNumber = ((TelephonyManager)getSystemService(Context.TELEPHONY_SERVICE))
+ .getVoiceMailNumber();
+ mQueryHandler = new QueryHandler(this);
+ }
+
+ @Override
+ protected void onResume() {
+ // The adapter caches looked up numbers, clear it so they will get
+ // looked up again.
+ if (mAdapter != null) {
+ mAdapter.clearCache();
+ }
+
+ startQuery();
+ resetNewCallsFlag();
+
+ super.onResume();
+
+ mAdapter.mPreDrawListener = null; // Let it restart the thread after next draw
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+
+ // Kill the requests thread
+ mAdapter.stopRequestProcessing();
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ mAdapter.stopRequestProcessing();
+ Cursor cursor = mAdapter.getCursor();
+ if (cursor != null && !cursor.isClosed()) {
+ cursor.close();
+ }
+ }
+
+ @Override
+ public void onWindowFocusChanged(boolean hasFocus) {
+ super.onWindowFocusChanged(hasFocus);
+
+ // Clear notifications only when window gains focus. This activity won't
+ // immediately receive focus if the keyguard screen is above it.
+ if (hasFocus) {
+ try {
+ ITelephony iTelephony =
+ ITelephony.Stub.asInterface(ServiceManager.getService("phone"));
+ if (iTelephony != null) {
+ iTelephony.cancelMissedCallsNotification();
+ } else {
+ Log.w(TAG, "Telephony service is null, can't call " +
+ "cancelMissedCallsNotification");
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to clear missed calls notification due to remote exception");
+ }
+ }
+ }
+
+ private void resetNewCallsFlag() {
+ // Mark all "new" missed calls as not new anymore
+ StringBuilder where = new StringBuilder("type=");
+ where.append(Calls.MISSED_TYPE);
+ where.append(" AND new=1");
+
+ ContentValues values = new ContentValues(1);
+ values.put(Calls.NEW, "0");
+ mQueryHandler.startUpdate(UPDATE_TOKEN, null, Calls.CONTENT_URI,
+ values, where.toString(), null);
+ }
+
+ private void startQuery() {
+ mAdapter.setLoading(true);
+
+ // Cancel any pending queries
+ mQueryHandler.cancelOperation(QUERY_TOKEN);
+ mQueryHandler.startQuery(QUERY_TOKEN, null, Calls.CONTENT_URI,
+ CALL_LOG_PROJECTION, null, null, Calls.DEFAULT_SORT_ORDER);
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ menu.add(0, MENU_ITEM_DELETE_ALL, 0, R.string.recentCalls_deleteAll)
+ .setIcon(android.R.drawable.ic_menu_close_clear_cancel);
+ return true;
+ }
+
+ @Override
+ public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfoIn) {
+ AdapterView.AdapterContextMenuInfo menuInfo;
+ try {
+ menuInfo = (AdapterView.AdapterContextMenuInfo) menuInfoIn;
+ } catch (ClassCastException e) {
+ Log.e(TAG, "bad menuInfoIn", e);
+ return;
+ }
+
+ Cursor cursor = (Cursor) mAdapter.getItem(menuInfo.position);
+
+ String number = cursor.getString(NUMBER_COLUMN_INDEX);
+ Uri numberUri = null;
+ boolean isVoicemail = false;
+ if (number.equals(CallerInfo.UNKNOWN_NUMBER)) {
+ number = getString(R.string.unknown);
+ } else if (number.equals(CallerInfo.PRIVATE_NUMBER)) {
+ number = getString(R.string.private_num);
+ } else if (number.equals(mVoiceMailNumber)) {
+ number = getString(R.string.voicemail);
+ numberUri = Uri.parse("voicemail:x");
+ isVoicemail = true;
+ } else {
+ numberUri = Uri.fromParts("tel", number, null);
+ }
+
+ ContactInfo info = mAdapter.getContactInfo(number);
+ boolean contactInfoPresent = (info != null && info != ContactInfo.EMPTY);
+ if (contactInfoPresent) {
+ menu.setHeaderTitle(info.name);
+ } else {
+ menu.setHeaderTitle(number);
+ }
+
+ if (numberUri != null) {
+ Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED, numberUri);
+ menu.add(0, 0, 0, getResources().getString(R.string.recentCalls_callNumber, number))
+ .setIntent(intent);
+ }
+
+ if (contactInfoPresent) {
+ menu.add(0, 0, 0, R.string.menu_viewContact)
+ .setIntent(new Intent(Intent.ACTION_VIEW,
+ ContentUris.withAppendedId(People.CONTENT_URI, info.personId)));
+ }
+
+ if (numberUri != null && !isVoicemail) {
+ menu.add(0, 0, 0, R.string.recentCalls_editNumberBeforeCall)
+ .setIntent(new Intent(Intent.ACTION_DIAL, numberUri));
+ menu.add(0, 0, 0, R.string.menu_sendTextMessage)
+ .setIntent(new Intent(Intent.ACTION_SENDTO,
+ Uri.fromParts("sms", number, null)));
+ }
+ if (!contactInfoPresent && numberUri != null && !isVoicemail) {
+ Intent intent = new Intent(Intent.ACTION_INSERT_OR_EDIT);
+ intent.setType(People.CONTENT_ITEM_TYPE);
+ intent.putExtra(Insert.PHONE, number);
+ menu.add(0, 0, 0, R.string.recentCalls_addToContact)
+ .setIntent(intent);
+ }
+ menu.add(0, MENU_ITEM_DELETE, 0, R.string.recentCalls_removeFromRecentList);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case MENU_ITEM_DELETE_ALL: {
+ getContentResolver().delete(Calls.CONTENT_URI, null, null);
+ //TODO The change notification should do this automatically, but it isn't working
+ // right now. Remove this when the change notification is working properly.
+ startQuery();
+ return true;
+ }
+
+ case MENU_ITEM_VIEW_CONTACTS: {
+ Intent intent = new Intent(Intent.ACTION_VIEW, People.CONTENT_URI);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ startActivity(intent);
+ return true;
+ }
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ @Override
+ public boolean onContextItemSelected(MenuItem item) {
+ // Convert the menu info to the proper type
+ AdapterView.AdapterContextMenuInfo menuInfo;
+ try {
+ menuInfo = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo();
+ } catch (ClassCastException e) {
+ Log.e(TAG, "bad menuInfoIn", e);
+ return false;
+ }
+
+ switch (item.getItemId()) {
+ case MENU_ITEM_DELETE: {
+ Cursor cursor = mAdapter.getCursor();
+ if (cursor != null) {
+ cursor.moveToPosition(menuInfo.position);
+ cursor.deleteRow();
+ }
+ return true;
+ }
+ }
+ return super.onContextItemSelected(item);
+ }
+
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_CALL: {
+ long callPressDiff = SystemClock.uptimeMillis() - event.getDownTime();
+ if (callPressDiff >= ViewConfiguration.getLongPressTimeout()) {
+ // Launch voice dialer
+ Intent intent = new Intent(Intent.ACTION_VOICE_COMMAND);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ try {
+ startActivity(intent);
+ } catch (ActivityNotFoundException e) {
+ }
+ return true;
+ }
+ }
+ }
+ return super.onKeyDown(keyCode, event);
+ }
+
+ @Override
+ public boolean onKeyUp(int keyCode, KeyEvent event) {
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_CALL:
+ try {
+ ITelephony phone = ITelephony.Stub.asInterface(
+ ServiceManager.checkService("phone"));
+ if (phone != null && !phone.isIdle()) {
+ // Let the super class handle it
+ break;
+ }
+ } catch (RemoteException re) {
+ // Fall through and try to call the contact
+ }
+
+ callEntry(getListView().getSelectedItemPosition());
+ return true;
+ }
+ return super.onKeyUp(keyCode, event);
+ }
+
+ /*
+ * Get the number from the Contacts, if available, since sometimes
+ * the number provided by caller id may not be formatted properly
+ * depending on the carrier (roaming) in use at the time of the
+ * incoming call.
+ * Logic : If the caller-id number starts with a "+", use it
+ * Else if the number in the contacts starts with a "+", use that one
+ * Else if the number in the contacts is longer, use that one
+ */
+ private String getBetterNumberFromContacts(String number) {
+ String matchingNumber = null;
+ // Look in the cache first. If it's not found then query the Phones db
+ ContactInfo ci = mAdapter.mContactInfo.get(number);
+ if (ci != null && ci != ContactInfo.EMPTY) {
+ matchingNumber = ci.number;
+ } else {
+ try {
+ Cursor phonesCursor =
+ RecentCallsListActivity.this.getContentResolver().query(
+ Uri.withAppendedPath(Phones.CONTENT_FILTER_URL,
+ number),
+ PHONES_PROJECTION, null, null, null);
+ if (phonesCursor != null) {
+ if (phonesCursor.moveToFirst()) {
+ matchingNumber = phonesCursor.getString(MATCHED_NUMBER_COLUMN_INDEX);
+ }
+ phonesCursor.close();
+ }
+ } catch (Exception e) {
+ // Use the number from the call log
+ }
+ }
+ if (!TextUtils.isEmpty(matchingNumber) &&
+ (matchingNumber.startsWith("+")
+ || matchingNumber.length() > number.length())) {
+ number = matchingNumber;
+ }
+ return number;
+ }
+
+ private void callEntry(int position) {
+ if (position < 0) {
+ // In touch mode you may often not have something selected, so
+ // just call the first entry to make sure that [send] [send] calls the
+ // most recent entry.
+ position = 0;
+ }
+ final Cursor cursor = mAdapter.getCursor();
+ if (cursor != null && cursor.moveToPosition(position)) {
+ String number = cursor.getString(NUMBER_COLUMN_INDEX);
+ if (TextUtils.isEmpty(number)
+ || number.equals(CallerInfo.UNKNOWN_NUMBER)
+ || number.equals(CallerInfo.PRIVATE_NUMBER)) {
+ // This number can't be called, do nothing
+ return;
+ }
+
+ int callType = cursor.getInt(CALL_TYPE_COLUMN_INDEX);
+ if (!number.startsWith("+") &&
+ (callType == Calls.INCOMING_TYPE
+ || callType == Calls.MISSED_TYPE)) {
+ // If the caller-id matches a contact with a better qualified number, use it
+ number = getBetterNumberFromContacts(number);
+ }
+ Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED,
+ Uri.fromParts("tel", number, null));
+ intent.setFlags(
+ Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+ startActivity(intent);
+ }
+ }
+
+ @Override
+ protected void onListItemClick(ListView l, View v, int position, long id) {
+ Intent intent = new Intent(this, CallDetailActivity.class);
+ intent.setData(ContentUris.withAppendedId(CallLog.Calls.CONTENT_URI, id));
+ startActivity(intent);
+ }
+}