Home | History | Annotate | Download | only in dialer
      1 /*
      2  * Copyright (C) 2009 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.dialer;
     18 
     19 import android.app.Activity;
     20 import android.app.LoaderManager.LoaderCallbacks;
     21 import android.content.ContentResolver;
     22 import android.content.ContentUris;
     23 import android.content.ContentValues;
     24 import android.content.Context;
     25 import android.content.Intent;
     26 import android.content.Loader;
     27 import android.content.res.Resources;
     28 import android.database.Cursor;
     29 import android.graphics.drawable.Drawable;
     30 import android.net.Uri;
     31 import android.os.AsyncTask;
     32 import android.os.Bundle;
     33 import android.provider.CallLog;
     34 import android.provider.CallLog.Calls;
     35 import android.provider.ContactsContract.CommonDataKinds.Phone;
     36 import android.provider.ContactsContract.Contacts;
     37 import android.provider.ContactsContract.DisplayNameSources;
     38 import android.provider.ContactsContract.Intents.Insert;
     39 import android.provider.VoicemailContract.Voicemails;
     40 import android.telephony.PhoneNumberUtils;
     41 import android.telephony.TelephonyManager;
     42 import android.text.TextUtils;
     43 import android.util.Log;
     44 import android.view.ActionMode;
     45 import android.view.KeyEvent;
     46 import android.view.LayoutInflater;
     47 import android.view.Menu;
     48 import android.view.MenuItem;
     49 import android.view.View;
     50 import android.widget.ImageButton;
     51 import android.widget.ImageView;
     52 import android.widget.ListView;
     53 import android.widget.TextView;
     54 import android.widget.Toast;
     55 
     56 import com.android.contacts.common.ContactPhotoManager;
     57 import com.android.contacts.common.CallUtil;
     58 import com.android.contacts.common.ClipboardUtils;
     59 import com.android.contacts.common.GeoUtil;
     60 import com.android.contacts.common.model.Contact;
     61 import com.android.contacts.common.model.ContactLoader;
     62 import com.android.contacts.common.util.UriUtils;
     63 import com.android.dialer.BackScrollManager.ScrollableHeader;
     64 import com.android.dialer.calllog.CallDetailHistoryAdapter;
     65 import com.android.dialer.calllog.CallTypeHelper;
     66 import com.android.dialer.calllog.ContactInfo;
     67 import com.android.dialer.calllog.ContactInfoHelper;
     68 import com.android.dialer.calllog.PhoneNumberHelper;
     69 import com.android.dialer.calllog.PhoneNumberUtilsWrapper;
     70 import com.android.dialer.util.AsyncTaskExecutor;
     71 import com.android.dialer.util.AsyncTaskExecutors;
     72 import com.android.dialer.voicemail.VoicemailPlaybackFragment;
     73 import com.android.dialer.voicemail.VoicemailStatusHelper;
     74 import com.android.dialer.voicemail.VoicemailStatusHelper.StatusMessage;
     75 import com.android.dialer.voicemail.VoicemailStatusHelperImpl;
     76 
     77 import java.util.List;
     78 
     79 /**
     80  * Displays the details of a specific call log entry.
     81  * <p>
     82  * This activity can be either started with the URI of a single call log entry, or with the
     83  * {@link #EXTRA_CALL_LOG_IDS} extra to specify a group of call log entries.
     84  */
     85 public class CallDetailActivity extends Activity implements ProximitySensorAware {
     86     private static final String TAG = "CallDetail";
     87 
     88     private static final int LOADER_ID = 0;
     89     private static final String BUNDLE_CONTACT_URI_EXTRA = "contact_uri_extra";
     90 
     91     private static final char LEFT_TO_RIGHT_EMBEDDING = '\u202A';
     92     private static final char POP_DIRECTIONAL_FORMATTING = '\u202C';
     93 
     94     /** The time to wait before enabling the blank the screen due to the proximity sensor. */
     95     private static final long PROXIMITY_BLANK_DELAY_MILLIS = 100;
     96     /** The time to wait before disabling the blank the screen due to the proximity sensor. */
     97     private static final long PROXIMITY_UNBLANK_DELAY_MILLIS = 500;
     98 
     99     /** The enumeration of {@link AsyncTask} objects used in this class. */
    100     public enum Tasks {
    101         MARK_VOICEMAIL_READ,
    102         DELETE_VOICEMAIL_AND_FINISH,
    103         REMOVE_FROM_CALL_LOG_AND_FINISH,
    104         UPDATE_PHONE_CALL_DETAILS,
    105     }
    106 
    107     /** A long array extra containing ids of call log entries to display. */
    108     public static final String EXTRA_CALL_LOG_IDS = "EXTRA_CALL_LOG_IDS";
    109     /** If we are started with a voicemail, we'll find the uri to play with this extra. */
    110     public static final String EXTRA_VOICEMAIL_URI = "EXTRA_VOICEMAIL_URI";
    111     /** If we should immediately start playback of the voicemail, this extra will be set to true. */
    112     public static final String EXTRA_VOICEMAIL_START_PLAYBACK = "EXTRA_VOICEMAIL_START_PLAYBACK";
    113     /** If the activity was triggered from a notification. */
    114     public static final String EXTRA_FROM_NOTIFICATION = "EXTRA_FROM_NOTIFICATION";
    115 
    116     private CallTypeHelper mCallTypeHelper;
    117     private PhoneNumberHelper mPhoneNumberHelper;
    118     private PhoneCallDetailsHelper mPhoneCallDetailsHelper;
    119     private TextView mHeaderTextView;
    120     private View mHeaderOverlayView;
    121     private ImageView mMainActionView;
    122     private ImageButton mMainActionPushLayerView;
    123     private ImageView mContactBackgroundView;
    124     private AsyncTaskExecutor mAsyncTaskExecutor;
    125     private ContactInfoHelper mContactInfoHelper;
    126 
    127     private String mNumber = null;
    128     private String mDefaultCountryIso;
    129 
    130     /* package */ LayoutInflater mInflater;
    131     /* package */ Resources mResources;
    132     /** Helper to load contact photos. */
    133     private ContactPhotoManager mContactPhotoManager;
    134     /** Helper to make async queries to content resolver. */
    135     private CallDetailActivityQueryHandler mAsyncQueryHandler;
    136     /** Helper to get voicemail status messages. */
    137     private VoicemailStatusHelper mVoicemailStatusHelper;
    138     // Views related to voicemail status message.
    139     private View mStatusMessageView;
    140     private TextView mStatusMessageText;
    141     private TextView mStatusMessageAction;
    142 
    143     /** Whether we should show "edit number before call" in the options menu. */
    144     private boolean mHasEditNumberBeforeCallOption;
    145     /** Whether we should show "trash" in the options menu. */
    146     private boolean mHasTrashOption;
    147     /** Whether we should show "remove from call log" in the options menu. */
    148     private boolean mHasRemoveFromCallLogOption;
    149 
    150     private ProximitySensorManager mProximitySensorManager;
    151     private final ProximitySensorListener mProximitySensorListener = new ProximitySensorListener();
    152 
    153     /**
    154      * The action mode used when the phone number is selected.  This will be non-null only when the
    155      * phone number is selected.
    156      */
    157     private ActionMode mPhoneNumberActionMode;
    158 
    159     private CharSequence mPhoneNumberLabelToCopy;
    160     private CharSequence mPhoneNumberToCopy;
    161 
    162     /** Listener to changes in the proximity sensor state. */
    163     private class ProximitySensorListener implements ProximitySensorManager.Listener {
    164         /** Used to show a blank view and hide the action bar. */
    165         private final Runnable mBlankRunnable = new Runnable() {
    166             @Override
    167             public void run() {
    168                 View blankView = findViewById(R.id.blank);
    169                 blankView.setVisibility(View.VISIBLE);
    170                 getActionBar().hide();
    171             }
    172         };
    173         /** Used to remove the blank view and show the action bar. */
    174         private final Runnable mUnblankRunnable = new Runnable() {
    175             @Override
    176             public void run() {
    177                 View blankView = findViewById(R.id.blank);
    178                 blankView.setVisibility(View.GONE);
    179                 getActionBar().show();
    180             }
    181         };
    182 
    183         @Override
    184         public synchronized void onNear() {
    185             clearPendingRequests();
    186             postDelayed(mBlankRunnable, PROXIMITY_BLANK_DELAY_MILLIS);
    187         }
    188 
    189         @Override
    190         public synchronized void onFar() {
    191             clearPendingRequests();
    192             postDelayed(mUnblankRunnable, PROXIMITY_UNBLANK_DELAY_MILLIS);
    193         }
    194 
    195         /** Removed any delayed requests that may be pending. */
    196         public synchronized void clearPendingRequests() {
    197             View blankView = findViewById(R.id.blank);
    198             blankView.removeCallbacks(mBlankRunnable);
    199             blankView.removeCallbacks(mUnblankRunnable);
    200         }
    201 
    202         /** Post a {@link Runnable} with a delay on the main thread. */
    203         private synchronized void postDelayed(Runnable runnable, long delayMillis) {
    204             // Post these instead of executing immediately so that:
    205             // - They are guaranteed to be executed on the main thread.
    206             // - If the sensor values changes rapidly for some time, the UI will not be
    207             //   updated immediately.
    208             View blankView = findViewById(R.id.blank);
    209             blankView.postDelayed(runnable, delayMillis);
    210         }
    211     }
    212 
    213     static final String[] CALL_LOG_PROJECTION = new String[] {
    214         CallLog.Calls.DATE,
    215         CallLog.Calls.DURATION,
    216         CallLog.Calls.NUMBER,
    217         CallLog.Calls.TYPE,
    218         CallLog.Calls.COUNTRY_ISO,
    219         CallLog.Calls.GEOCODED_LOCATION,
    220         CallLog.Calls.NUMBER_PRESENTATION,
    221     };
    222 
    223     static final int DATE_COLUMN_INDEX = 0;
    224     static final int DURATION_COLUMN_INDEX = 1;
    225     static final int NUMBER_COLUMN_INDEX = 2;
    226     static final int CALL_TYPE_COLUMN_INDEX = 3;
    227     static final int COUNTRY_ISO_COLUMN_INDEX = 4;
    228     static final int GEOCODED_LOCATION_COLUMN_INDEX = 5;
    229     static final int NUMBER_PRESENTATION_COLUMN_INDEX = 6;
    230 
    231     private final View.OnClickListener mPrimaryActionListener = new View.OnClickListener() {
    232         @Override
    233         public void onClick(View view) {
    234             if (finishPhoneNumerSelectedActionModeIfShown()) {
    235                 return;
    236             }
    237             startActivity(((ViewEntry) view.getTag()).primaryIntent);
    238         }
    239     };
    240 
    241     private final View.OnClickListener mSecondaryActionListener = new View.OnClickListener() {
    242         @Override
    243         public void onClick(View view) {
    244             if (finishPhoneNumerSelectedActionModeIfShown()) {
    245                 return;
    246             }
    247             startActivity(((ViewEntry) view.getTag()).secondaryIntent);
    248         }
    249     };
    250 
    251     private final View.OnLongClickListener mPrimaryLongClickListener =
    252             new View.OnLongClickListener() {
    253         @Override
    254         public boolean onLongClick(View v) {
    255             if (finishPhoneNumerSelectedActionModeIfShown()) {
    256                 return true;
    257             }
    258             startPhoneNumberSelectedActionMode(v);
    259             return true;
    260         }
    261     };
    262 
    263     private final LoaderCallbacks<Contact> mLoaderCallbacks = new LoaderCallbacks<Contact>() {
    264         @Override
    265         public void onLoaderReset(Loader<Contact> loader) {
    266         }
    267 
    268         @Override
    269         public void onLoadFinished(Loader<Contact> loader, Contact data) {
    270             final Intent intent = new Intent(Intent.ACTION_INSERT_OR_EDIT);
    271             intent.setType(Contacts.CONTENT_ITEM_TYPE);
    272             if (data.getDisplayNameSource() >= DisplayNameSources.ORGANIZATION) {
    273                 intent.putExtra(Insert.NAME, data.getDisplayName());
    274             }
    275             intent.putExtra(Insert.DATA, data.getContentValues());
    276             bindContactPhotoAction(intent, R.drawable.ic_add_contact_holo_dark,
    277                     getString(R.string.description_add_contact));
    278         }
    279 
    280         @Override
    281         public Loader<Contact> onCreateLoader(int id, Bundle args) {
    282             final Uri contactUri = args.getParcelable(BUNDLE_CONTACT_URI_EXTRA);
    283             if (contactUri == null) {
    284                 Log.wtf(TAG, "No contact lookup uri provided.");
    285             }
    286             return new ContactLoader(CallDetailActivity.this, contactUri,
    287                     false /* loadGroupMetaData */, false /* loadInvitableAccountTypes */,
    288                     false /* postViewNotification */, true /* computeFormattedPhoneNumber */);
    289         }
    290     };
    291 
    292     @Override
    293     protected void onCreate(Bundle icicle) {
    294         super.onCreate(icicle);
    295 
    296         setContentView(R.layout.call_detail);
    297 
    298         mAsyncTaskExecutor = AsyncTaskExecutors.createThreadPoolExecutor();
    299         mInflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE);
    300         mResources = getResources();
    301 
    302         mCallTypeHelper = new CallTypeHelper(getResources());
    303         mPhoneNumberHelper = new PhoneNumberHelper(mResources);
    304         mPhoneCallDetailsHelper = new PhoneCallDetailsHelper(mResources, mCallTypeHelper,
    305                 new PhoneNumberUtilsWrapper());
    306         mVoicemailStatusHelper = new VoicemailStatusHelperImpl();
    307         mAsyncQueryHandler = new CallDetailActivityQueryHandler(this);
    308         mHeaderTextView = (TextView) findViewById(R.id.header_text);
    309         mHeaderOverlayView = findViewById(R.id.photo_text_bar);
    310         mStatusMessageView = findViewById(R.id.voicemail_status);
    311         mStatusMessageText = (TextView) findViewById(R.id.voicemail_status_message);
    312         mStatusMessageAction = (TextView) findViewById(R.id.voicemail_status_action);
    313         mMainActionView = (ImageView) findViewById(R.id.main_action);
    314         mMainActionPushLayerView = (ImageButton) findViewById(R.id.main_action_push_layer);
    315         mContactBackgroundView = (ImageView) findViewById(R.id.contact_background);
    316         mDefaultCountryIso = GeoUtil.getCurrentCountryIso(this);
    317         mContactPhotoManager = ContactPhotoManager.getInstance(this);
    318         mProximitySensorManager = new ProximitySensorManager(this, mProximitySensorListener);
    319         mContactInfoHelper = new ContactInfoHelper(this, GeoUtil.getCurrentCountryIso(this));
    320         getActionBar().setDisplayHomeAsUpEnabled(true);
    321         optionallyHandleVoicemail();
    322         if (getIntent().getBooleanExtra(EXTRA_FROM_NOTIFICATION, false)) {
    323             closeSystemDialogs();
    324         }
    325     }
    326 
    327     @Override
    328     public void onResume() {
    329         super.onResume();
    330         updateData(getCallLogEntryUris());
    331     }
    332 
    333     /**
    334      * Handle voicemail playback or hide voicemail ui.
    335      * <p>
    336      * If the Intent used to start this Activity contains the suitable extras, then start voicemail
    337      * playback.  If it doesn't, then hide the voicemail ui.
    338      */
    339     private void optionallyHandleVoicemail() {
    340         View voicemailContainer = findViewById(R.id.voicemail_container);
    341         if (hasVoicemail()) {
    342             // Has voicemail: add the voicemail fragment.  Add suitable arguments to set the uri
    343             // to play and optionally start the playback.
    344             // Do a query to fetch the voicemail status messages.
    345             VoicemailPlaybackFragment playbackFragment = new VoicemailPlaybackFragment();
    346             Bundle fragmentArguments = new Bundle();
    347             fragmentArguments.putParcelable(EXTRA_VOICEMAIL_URI, getVoicemailUri());
    348             if (getIntent().getBooleanExtra(EXTRA_VOICEMAIL_START_PLAYBACK, false)) {
    349                 fragmentArguments.putBoolean(EXTRA_VOICEMAIL_START_PLAYBACK, true);
    350             }
    351             playbackFragment.setArguments(fragmentArguments);
    352             voicemailContainer.setVisibility(View.VISIBLE);
    353             getFragmentManager().beginTransaction()
    354                     .add(R.id.voicemail_container, playbackFragment)
    355                     .commitAllowingStateLoss();
    356             mAsyncQueryHandler.startVoicemailStatusQuery(getVoicemailUri());
    357             markVoicemailAsRead(getVoicemailUri());
    358         } else {
    359             // No voicemail uri: hide the status view.
    360             mStatusMessageView.setVisibility(View.GONE);
    361             voicemailContainer.setVisibility(View.GONE);
    362         }
    363     }
    364 
    365     private boolean hasVoicemail() {
    366         return getVoicemailUri() != null;
    367     }
    368 
    369     private Uri getVoicemailUri() {
    370         return getIntent().getParcelableExtra(EXTRA_VOICEMAIL_URI);
    371     }
    372 
    373     private void markVoicemailAsRead(final Uri voicemailUri) {
    374         mAsyncTaskExecutor.submit(Tasks.MARK_VOICEMAIL_READ, new AsyncTask<Void, Void, Void>() {
    375             @Override
    376             public Void doInBackground(Void... params) {
    377                 ContentValues values = new ContentValues();
    378                 values.put(Voicemails.IS_READ, true);
    379                 getContentResolver().update(voicemailUri, values,
    380                         Voicemails.IS_READ + " = 0", null);
    381                 return null;
    382             }
    383         });
    384     }
    385 
    386     /**
    387      * Returns the list of URIs to show.
    388      * <p>
    389      * There are two ways the URIs can be provided to the activity: as the data on the intent, or as
    390      * a list of ids in the call log added as an extra on the URI.
    391      * <p>
    392      * If both are available, the data on the intent takes precedence.
    393      */
    394     private Uri[] getCallLogEntryUris() {
    395         Uri uri = getIntent().getData();
    396         if (uri != null) {
    397             // If there is a data on the intent, it takes precedence over the extra.
    398             return new Uri[]{ uri };
    399         }
    400         long[] ids = getIntent().getLongArrayExtra(EXTRA_CALL_LOG_IDS);
    401         Uri[] uris = new Uri[ids.length];
    402         for (int index = 0; index < ids.length; ++index) {
    403             uris[index] = ContentUris.withAppendedId(Calls.CONTENT_URI_WITH_VOICEMAIL, ids[index]);
    404         }
    405         return uris;
    406     }
    407 
    408     @Override
    409     public boolean onKeyDown(int keyCode, KeyEvent event) {
    410         switch (keyCode) {
    411             case KeyEvent.KEYCODE_CALL: {
    412                 // Make sure phone isn't already busy before starting direct call
    413                 TelephonyManager tm = (TelephonyManager)
    414                         getSystemService(Context.TELEPHONY_SERVICE);
    415                 if (tm.getCallState() == TelephonyManager.CALL_STATE_IDLE) {
    416                     startActivity(CallUtil.getCallIntent(
    417                             Uri.fromParts(CallUtil.SCHEME_TEL, mNumber, null)));
    418                     return true;
    419                 }
    420             }
    421         }
    422 
    423         return super.onKeyDown(keyCode, event);
    424     }
    425 
    426     /**
    427      * Update user interface with details of given call.
    428      *
    429      * @param callUris URIs into {@link CallLog.Calls} of the calls to be displayed
    430      */
    431     private void updateData(final Uri... callUris) {
    432         class UpdateContactDetailsTask extends AsyncTask<Void, Void, PhoneCallDetails[]> {
    433             @Override
    434             public PhoneCallDetails[] doInBackground(Void... params) {
    435                 // TODO: All phone calls correspond to the same person, so we can make a single
    436                 // lookup.
    437                 final int numCalls = callUris.length;
    438                 PhoneCallDetails[] details = new PhoneCallDetails[numCalls];
    439                 try {
    440                     for (int index = 0; index < numCalls; ++index) {
    441                         details[index] = getPhoneCallDetailsForUri(callUris[index]);
    442                     }
    443                     return details;
    444                 } catch (IllegalArgumentException e) {
    445                     // Something went wrong reading in our primary data.
    446                     Log.w(TAG, "invalid URI starting call details", e);
    447                     return null;
    448                 }
    449             }
    450 
    451             @Override
    452             public void onPostExecute(PhoneCallDetails[] details) {
    453                 if (details == null) {
    454                     // Somewhere went wrong: we're going to bail out and show error to users.
    455                     Toast.makeText(CallDetailActivity.this, R.string.toast_call_detail_error,
    456                             Toast.LENGTH_SHORT).show();
    457                     finish();
    458                     return;
    459                 }
    460 
    461                 // We know that all calls are from the same number and the same contact, so pick the
    462                 // first.
    463                 PhoneCallDetails firstDetails = details[0];
    464                 mNumber = firstDetails.number.toString();
    465                 final int numberPresentation = firstDetails.numberPresentation;
    466                 final Uri contactUri = firstDetails.contactUri;
    467                 final Uri photoUri = firstDetails.photoUri;
    468 
    469                 // Set the details header, based on the first phone call.
    470                 mPhoneCallDetailsHelper.setCallDetailsHeader(mHeaderTextView, firstDetails);
    471 
    472                 // Cache the details about the phone number.
    473                 final boolean canPlaceCallsTo =
    474                     PhoneNumberUtilsWrapper.canPlaceCallsTo(mNumber, numberPresentation);
    475                 final PhoneNumberUtilsWrapper phoneUtils = new PhoneNumberUtilsWrapper();
    476                 final boolean isVoicemailNumber = phoneUtils.isVoicemailNumber(mNumber);
    477                 final boolean isSipNumber = phoneUtils.isSipNumber(mNumber);
    478 
    479                 // Let user view contact details if they exist, otherwise add option to create new
    480                 // contact from this number.
    481                 final Intent mainActionIntent;
    482                 final int mainActionIcon;
    483                 final String mainActionDescription;
    484 
    485                 final CharSequence nameOrNumber;
    486                 if (!TextUtils.isEmpty(firstDetails.name)) {
    487                     nameOrNumber = firstDetails.name;
    488                 } else {
    489                     nameOrNumber = firstDetails.number;
    490                 }
    491 
    492                 boolean skipBind = false;
    493 
    494                 if (contactUri != null && !UriUtils.isEncodedContactUri(contactUri)) {
    495                     mainActionIntent = new Intent(Intent.ACTION_VIEW, contactUri);
    496                     // This will launch People's detail contact screen, so we probably want to
    497                     // treat it as a separate People task.
    498                     mainActionIntent.setFlags(
    499                             Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
    500                     mainActionIcon = R.drawable.ic_contacts_holo_dark;
    501                     mainActionDescription =
    502                             getString(R.string.description_view_contact, nameOrNumber);
    503                 } else if (UriUtils.isEncodedContactUri(contactUri)) {
    504                     final Bundle bundle = new Bundle(1);
    505                     bundle.putParcelable(BUNDLE_CONTACT_URI_EXTRA, contactUri);
    506                     getLoaderManager().initLoader(LOADER_ID, bundle, mLoaderCallbacks);
    507                     mainActionIntent = null;
    508                     mainActionIcon = R.drawable.ic_add_contact_holo_dark;
    509                     mainActionDescription = getString(R.string.description_add_contact);
    510                     skipBind = true;
    511                 } else if (isVoicemailNumber) {
    512                     mainActionIntent = null;
    513                     mainActionIcon = 0;
    514                     mainActionDescription = null;
    515                 } else if (isSipNumber) {
    516                     // TODO: This item is currently disabled for SIP addresses, because
    517                     // the Insert.PHONE extra only works correctly for PSTN numbers.
    518                     //
    519                     // To fix this for SIP addresses, we need to:
    520                     // - define ContactsContract.Intents.Insert.SIP_ADDRESS, and use it here if
    521                     //   the current number is a SIP address
    522                     // - update the contacts UI code to handle Insert.SIP_ADDRESS by
    523                     //   updating the SipAddress field
    524                     // and then we can remove the "!isSipNumber" check above.
    525                     mainActionIntent = null;
    526                     mainActionIcon = 0;
    527                     mainActionDescription = null;
    528                 } else if (canPlaceCallsTo) {
    529                     mainActionIntent = new Intent(Intent.ACTION_INSERT_OR_EDIT);
    530                     mainActionIntent.setType(Contacts.CONTENT_ITEM_TYPE);
    531                     mainActionIntent.putExtra(Insert.PHONE, mNumber);
    532                     mainActionIcon = R.drawable.ic_add_contact_holo_dark;
    533                     mainActionDescription = getString(R.string.description_add_contact);
    534                 } else {
    535                     // If we cannot call the number, when we probably cannot add it as a contact
    536                     // either. This is usually the case of private, unknown, or payphone numbers.
    537                     mainActionIntent = null;
    538                     mainActionIcon = 0;
    539                     mainActionDescription = null;
    540                 }
    541 
    542                 if (!skipBind) {
    543                     bindContactPhotoAction(mainActionIntent, mainActionIcon,
    544                             mainActionDescription);
    545                 }
    546 
    547                 // This action allows to call the number that places the call.
    548                 if (canPlaceCallsTo) {
    549                     final CharSequence displayNumber =
    550                             mPhoneNumberHelper.getDisplayNumber(
    551                                     firstDetails.number,
    552                                     firstDetails.numberPresentation,
    553                                     firstDetails.formattedNumber);
    554 
    555                     ViewEntry entry = new ViewEntry(
    556                             getString(R.string.menu_callNumber,
    557                                     forceLeftToRight(displayNumber)),
    558                                     CallUtil.getCallIntent(mNumber),
    559                                     getString(R.string.description_call, nameOrNumber));
    560 
    561                     // Only show a label if the number is shown and it is not a SIP address.
    562                     if (!TextUtils.isEmpty(firstDetails.name)
    563                             && !TextUtils.isEmpty(firstDetails.number)
    564                             && !PhoneNumberUtils.isUriNumber(firstDetails.number.toString())) {
    565                         entry.label = Phone.getTypeLabel(mResources, firstDetails.numberType,
    566                                 firstDetails.numberLabel);
    567                     }
    568 
    569                     // The secondary action allows to send an SMS to the number that placed the
    570                     // call.
    571                     if (phoneUtils.canSendSmsTo(mNumber, numberPresentation)) {
    572                         entry.setSecondaryAction(
    573                                 R.drawable.ic_text_holo_light,
    574                                 new Intent(Intent.ACTION_SENDTO,
    575                                            Uri.fromParts("sms", mNumber, null)),
    576                                 getString(R.string.description_send_text_message, nameOrNumber));
    577                     }
    578 
    579                     configureCallButton(entry);
    580                     mPhoneNumberToCopy = displayNumber;
    581                     mPhoneNumberLabelToCopy = entry.label;
    582                 } else {
    583                     disableCallButton();
    584                     mPhoneNumberToCopy = null;
    585                     mPhoneNumberLabelToCopy = null;
    586                 }
    587 
    588                 mHasEditNumberBeforeCallOption =
    589                         canPlaceCallsTo && !isSipNumber && !isVoicemailNumber;
    590                 mHasTrashOption = hasVoicemail();
    591                 mHasRemoveFromCallLogOption = !hasVoicemail();
    592                 invalidateOptionsMenu();
    593 
    594                 ListView historyList = (ListView) findViewById(R.id.history);
    595                 historyList.setAdapter(
    596                         new CallDetailHistoryAdapter(CallDetailActivity.this, mInflater,
    597                                 mCallTypeHelper, details, hasVoicemail(), canPlaceCallsTo,
    598                                 findViewById(R.id.controls)));
    599                 BackScrollManager.bind(
    600                         new ScrollableHeader() {
    601                             private View mControls = findViewById(R.id.controls);
    602                             private View mPhoto = findViewById(R.id.contact_background_sizer);
    603                             private View mHeader = findViewById(R.id.photo_text_bar);
    604                         private View mSeparator = findViewById(R.id.separator);
    605 
    606                             @Override
    607                             public void setOffset(int offset) {
    608                                 mControls.setY(-offset);
    609                             }
    610 
    611                             @Override
    612                             public int getMaximumScrollableHeaderOffset() {
    613                                 // We can scroll the photo out, but we should keep the header if
    614                                 // present.
    615                                 if (mHeader.getVisibility() == View.VISIBLE) {
    616                                     return mPhoto.getHeight() - mHeader.getHeight();
    617                                 } else {
    618                                     // If the header is not present, we should also scroll out the
    619                                     // separator line.
    620                                     return mPhoto.getHeight() + mSeparator.getHeight();
    621                                 }
    622                             }
    623                         },
    624                         historyList);
    625                 loadContactPhotos(photoUri);
    626                 findViewById(R.id.call_detail).setVisibility(View.VISIBLE);
    627             }
    628         }
    629         mAsyncTaskExecutor.submit(Tasks.UPDATE_PHONE_CALL_DETAILS, new UpdateContactDetailsTask());
    630     }
    631 
    632     private void bindContactPhotoAction(final Intent actionIntent, int actionIcon,
    633             String actionDescription) {
    634         if (actionIntent == null) {
    635             mMainActionView.setVisibility(View.INVISIBLE);
    636             mMainActionPushLayerView.setVisibility(View.GONE);
    637             mHeaderTextView.setVisibility(View.INVISIBLE);
    638             mHeaderOverlayView.setVisibility(View.INVISIBLE);
    639         } else {
    640             mMainActionView.setVisibility(View.VISIBLE);
    641             mMainActionView.setImageResource(actionIcon);
    642             mMainActionPushLayerView.setVisibility(View.VISIBLE);
    643             mMainActionPushLayerView.setOnClickListener(new View.OnClickListener() {
    644                 @Override
    645                 public void onClick(View v) {
    646                     startActivity(actionIntent);
    647                 }
    648             });
    649             mMainActionPushLayerView.setContentDescription(actionDescription);
    650             mHeaderTextView.setVisibility(View.VISIBLE);
    651             mHeaderOverlayView.setVisibility(View.VISIBLE);
    652         }
    653     }
    654 
    655     /** Return the phone call details for a given call log URI. */
    656     private PhoneCallDetails getPhoneCallDetailsForUri(Uri callUri) {
    657         ContentResolver resolver = getContentResolver();
    658         Cursor callCursor = resolver.query(callUri, CALL_LOG_PROJECTION, null, null, null);
    659         try {
    660             if (callCursor == null || !callCursor.moveToFirst()) {
    661                 throw new IllegalArgumentException("Cannot find content: " + callUri);
    662             }
    663 
    664             // Read call log specifics.
    665             final String number = callCursor.getString(NUMBER_COLUMN_INDEX);
    666             final int numberPresentation = callCursor.getInt(
    667                     NUMBER_PRESENTATION_COLUMN_INDEX);
    668             final long date = callCursor.getLong(DATE_COLUMN_INDEX);
    669             final long duration = callCursor.getLong(DURATION_COLUMN_INDEX);
    670             final int callType = callCursor.getInt(CALL_TYPE_COLUMN_INDEX);
    671             String countryIso = callCursor.getString(COUNTRY_ISO_COLUMN_INDEX);
    672             final String geocode = callCursor.getString(GEOCODED_LOCATION_COLUMN_INDEX);
    673 
    674             if (TextUtils.isEmpty(countryIso)) {
    675                 countryIso = mDefaultCountryIso;
    676             }
    677 
    678             // Formatted phone number.
    679             final CharSequence formattedNumber;
    680             // Read contact specifics.
    681             final CharSequence nameText;
    682             final int numberType;
    683             final CharSequence numberLabel;
    684             final Uri photoUri;
    685             final Uri lookupUri;
    686             // If this is not a regular number, there is no point in looking it up in the contacts.
    687             ContactInfo info =
    688                     PhoneNumberUtilsWrapper.canPlaceCallsTo(number, numberPresentation)
    689                     && !new PhoneNumberUtilsWrapper().isVoicemailNumber(number)
    690                             ? mContactInfoHelper.lookupNumber(number, countryIso)
    691                             : null;
    692             if (info == null) {
    693                 formattedNumber = mPhoneNumberHelper.getDisplayNumber(number,
    694                         numberPresentation, null);
    695                 nameText = "";
    696                 numberType = 0;
    697                 numberLabel = "";
    698                 photoUri = null;
    699                 lookupUri = null;
    700             } else {
    701                 formattedNumber = info.formattedNumber;
    702                 nameText = info.name;
    703                 numberType = info.type;
    704                 numberLabel = info.label;
    705                 photoUri = info.photoUri;
    706                 lookupUri = info.lookupUri;
    707             }
    708             return new PhoneCallDetails(number, numberPresentation,
    709                     formattedNumber, countryIso, geocode,
    710                     new int[]{ callType }, date, duration,
    711                     nameText, numberType, numberLabel, lookupUri, photoUri);
    712         } finally {
    713             if (callCursor != null) {
    714                 callCursor.close();
    715             }
    716         }
    717     }
    718 
    719     /** Load the contact photos and places them in the corresponding views. */
    720     private void loadContactPhotos(Uri photoUri) {
    721         mContactPhotoManager.loadPhoto(mContactBackgroundView, photoUri,
    722                 mContactBackgroundView.getWidth(), true);
    723     }
    724 
    725     static final class ViewEntry {
    726         public final String text;
    727         public final Intent primaryIntent;
    728         /** The description for accessibility of the primary action. */
    729         public final String primaryDescription;
    730 
    731         public CharSequence label = null;
    732         /** Icon for the secondary action. */
    733         public int secondaryIcon = 0;
    734         /** Intent for the secondary action. If not null, an icon must be defined. */
    735         public Intent secondaryIntent = null;
    736         /** The description for accessibility of the secondary action. */
    737         public String secondaryDescription = null;
    738 
    739         public ViewEntry(String text, Intent intent, String description) {
    740             this.text = text;
    741             primaryIntent = intent;
    742             primaryDescription = description;
    743         }
    744 
    745         public void setSecondaryAction(int icon, Intent intent, String description) {
    746             secondaryIcon = icon;
    747             secondaryIntent = intent;
    748             secondaryDescription = description;
    749         }
    750     }
    751 
    752     /** Disables the call button area, e.g., for private numbers. */
    753     private void disableCallButton() {
    754         findViewById(R.id.call_and_sms).setVisibility(View.GONE);
    755     }
    756 
    757     /** Configures the call button area using the given entry. */
    758     private void configureCallButton(ViewEntry entry) {
    759         View convertView = findViewById(R.id.call_and_sms);
    760         convertView.setVisibility(View.VISIBLE);
    761 
    762         ImageView icon = (ImageView) convertView.findViewById(R.id.call_and_sms_icon);
    763         View divider = convertView.findViewById(R.id.call_and_sms_divider);
    764         TextView text = (TextView) convertView.findViewById(R.id.call_and_sms_text);
    765 
    766         View mainAction = convertView.findViewById(R.id.call_and_sms_main_action);
    767         mainAction.setOnClickListener(mPrimaryActionListener);
    768         mainAction.setTag(entry);
    769         mainAction.setContentDescription(entry.primaryDescription);
    770         mainAction.setOnLongClickListener(mPrimaryLongClickListener);
    771 
    772         if (entry.secondaryIntent != null) {
    773             icon.setOnClickListener(mSecondaryActionListener);
    774             icon.setImageResource(entry.secondaryIcon);
    775             icon.setVisibility(View.VISIBLE);
    776             icon.setTag(entry);
    777             icon.setContentDescription(entry.secondaryDescription);
    778             divider.setVisibility(View.VISIBLE);
    779         } else {
    780             icon.setVisibility(View.GONE);
    781             divider.setVisibility(View.GONE);
    782         }
    783         text.setText(entry.text);
    784 
    785         TextView label = (TextView) convertView.findViewById(R.id.call_and_sms_label);
    786         if (TextUtils.isEmpty(entry.label)) {
    787             label.setVisibility(View.GONE);
    788         } else {
    789             label.setText(entry.label);
    790             label.setVisibility(View.VISIBLE);
    791         }
    792     }
    793 
    794     protected void updateVoicemailStatusMessage(Cursor statusCursor) {
    795         if (statusCursor == null) {
    796             mStatusMessageView.setVisibility(View.GONE);
    797             return;
    798         }
    799         final StatusMessage message = getStatusMessage(statusCursor);
    800         if (message == null || !message.showInCallDetails()) {
    801             mStatusMessageView.setVisibility(View.GONE);
    802             return;
    803         }
    804 
    805         mStatusMessageView.setVisibility(View.VISIBLE);
    806         mStatusMessageText.setText(message.callDetailsMessageId);
    807         if (message.actionMessageId != -1) {
    808             mStatusMessageAction.setText(message.actionMessageId);
    809         }
    810         if (message.actionUri != null) {
    811             mStatusMessageAction.setClickable(true);
    812             mStatusMessageAction.setOnClickListener(new View.OnClickListener() {
    813                 @Override
    814                 public void onClick(View v) {
    815                     startActivity(new Intent(Intent.ACTION_VIEW, message.actionUri));
    816                 }
    817             });
    818         } else {
    819             mStatusMessageAction.setClickable(false);
    820         }
    821     }
    822 
    823     private StatusMessage getStatusMessage(Cursor statusCursor) {
    824         List<StatusMessage> messages = mVoicemailStatusHelper.getStatusMessages(statusCursor);
    825         if (messages.size() == 0) {
    826             return null;
    827         }
    828         // There can only be a single status message per source package, so num of messages can
    829         // at most be 1.
    830         if (messages.size() > 1) {
    831             Log.w(TAG, String.format("Expected 1, found (%d) num of status messages." +
    832                     " Will use the first one.", messages.size()));
    833         }
    834         return messages.get(0);
    835     }
    836 
    837     @Override
    838     public boolean onCreateOptionsMenu(Menu menu) {
    839         getMenuInflater().inflate(R.menu.call_details_options, menu);
    840         return super.onCreateOptionsMenu(menu);
    841     }
    842 
    843     @Override
    844     public boolean onPrepareOptionsMenu(Menu menu) {
    845         // This action deletes all elements in the group from the call log.
    846         // We don't have this action for voicemails, because you can just use the trash button.
    847         menu.findItem(R.id.menu_remove_from_call_log).setVisible(mHasRemoveFromCallLogOption);
    848         menu.findItem(R.id.menu_edit_number_before_call).setVisible(mHasEditNumberBeforeCallOption);
    849         menu.findItem(R.id.menu_trash).setVisible(mHasTrashOption);
    850         return super.onPrepareOptionsMenu(menu);
    851     }
    852 
    853     public void onMenuRemoveFromCallLog(MenuItem menuItem) {
    854         final StringBuilder callIds = new StringBuilder();
    855         for (Uri callUri : getCallLogEntryUris()) {
    856             if (callIds.length() != 0) {
    857                 callIds.append(",");
    858             }
    859             callIds.append(ContentUris.parseId(callUri));
    860         }
    861         mAsyncTaskExecutor.submit(Tasks.REMOVE_FROM_CALL_LOG_AND_FINISH,
    862                 new AsyncTask<Void, Void, Void>() {
    863                     @Override
    864                     public Void doInBackground(Void... params) {
    865                         getContentResolver().delete(Calls.CONTENT_URI_WITH_VOICEMAIL,
    866                                 Calls._ID + " IN (" + callIds + ")", null);
    867                         return null;
    868                     }
    869 
    870                     @Override
    871                     public void onPostExecute(Void result) {
    872                         finish();
    873                     }
    874                 });
    875     }
    876 
    877     public void onMenuEditNumberBeforeCall(MenuItem menuItem) {
    878         startActivity(new Intent(Intent.ACTION_DIAL, CallUtil.getCallUri(mNumber)));
    879     }
    880 
    881     public void onMenuTrashVoicemail(MenuItem menuItem) {
    882         final Uri voicemailUri = getVoicemailUri();
    883         mAsyncTaskExecutor.submit(Tasks.DELETE_VOICEMAIL_AND_FINISH,
    884                 new AsyncTask<Void, Void, Void>() {
    885                     @Override
    886                     public Void doInBackground(Void... params) {
    887                         getContentResolver().delete(voicemailUri, null, null);
    888                         return null;
    889                     }
    890                     @Override
    891                     public void onPostExecute(Void result) {
    892                         finish();
    893                     }
    894                 });
    895     }
    896 
    897     /** Invoked when the user presses the home button in the action bar. */
    898     private void onHomeSelected() {
    899         Intent intent = new Intent(Intent.ACTION_VIEW, Calls.CONTENT_URI);
    900         // This will open the call log even if the detail view has been opened directly.
    901         intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
    902         startActivity(intent);
    903         finish();
    904     }
    905 
    906     @Override
    907     protected void onPause() {
    908         // Immediately stop the proximity sensor.
    909         disableProximitySensor(false);
    910         mProximitySensorListener.clearPendingRequests();
    911         super.onPause();
    912     }
    913 
    914     @Override
    915     public void enableProximitySensor() {
    916         mProximitySensorManager.enable();
    917     }
    918 
    919     @Override
    920     public void disableProximitySensor(boolean waitForFarState) {
    921         mProximitySensorManager.disable(waitForFarState);
    922     }
    923 
    924     /**
    925      * If the phone number is selected, unselect it and return {@code true}.
    926      * Otherwise, just {@code false}.
    927      */
    928     private boolean finishPhoneNumerSelectedActionModeIfShown() {
    929         if (mPhoneNumberActionMode == null) return false;
    930         mPhoneNumberActionMode.finish();
    931         return true;
    932     }
    933 
    934     private void startPhoneNumberSelectedActionMode(View targetView) {
    935         mPhoneNumberActionMode = startActionMode(new PhoneNumberActionModeCallback(targetView));
    936     }
    937 
    938     private void closeSystemDialogs() {
    939         sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
    940     }
    941 
    942     private class PhoneNumberActionModeCallback implements ActionMode.Callback {
    943         private final View mTargetView;
    944         private final Drawable mOriginalViewBackground;
    945 
    946         public PhoneNumberActionModeCallback(View targetView) {
    947             mTargetView = targetView;
    948 
    949             // Highlight the phone number view.  Remember the old background, and put a new one.
    950             mOriginalViewBackground = mTargetView.getBackground();
    951             mTargetView.setBackgroundColor(getResources().getColor(R.color.item_selected));
    952         }
    953 
    954         @Override
    955         public boolean onCreateActionMode(ActionMode mode, Menu menu) {
    956             if (TextUtils.isEmpty(mPhoneNumberToCopy)) return false;
    957 
    958             getMenuInflater().inflate(R.menu.call_details_cab, menu);
    959             return true;
    960         }
    961 
    962         @Override
    963         public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
    964             return true;
    965         }
    966 
    967         @Override
    968         public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
    969             switch (item.getItemId()) {
    970                 case R.id.copy_phone_number:
    971                     ClipboardUtils.copyText(CallDetailActivity.this, mPhoneNumberLabelToCopy,
    972                             mPhoneNumberToCopy, true);
    973                     mode.finish(); // Close the CAB
    974                     return true;
    975             }
    976             return false;
    977         }
    978 
    979         @Override
    980         public void onDestroyActionMode(ActionMode mode) {
    981             mPhoneNumberActionMode = null;
    982 
    983             // Restore the view background.
    984             mTargetView.setBackground(mOriginalViewBackground);
    985         }
    986     }
    987 
    988     /** Returns the given text, forced to be left-to-right. */
    989     private static CharSequence forceLeftToRight(CharSequence text) {
    990         StringBuilder sb = new StringBuilder();
    991         sb.append(LEFT_TO_RIGHT_EMBEDDING);
    992         sb.append(text);
    993         sb.append(POP_DIRECTIONAL_FORMATTING);
    994         return sb.toString();
    995     }
    996 }
    997