Home | History | Annotate | Download | only in calllog
      1 /*
      2  * Copyright (C) 2011 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.calllog;
     18 
     19 import android.content.Context;
     20 import android.content.res.Resources;
     21 import android.content.Intent;
     22 import android.net.Uri;
     23 import android.provider.CallLog.Calls;
     24 import android.provider.ContactsContract.CommonDataKinds.Phone;
     25 import android.support.v7.widget.CardView;
     26 import android.support.v7.widget.RecyclerView;
     27 import android.telecom.PhoneAccountHandle;
     28 import android.text.TextUtils;
     29 import android.view.View;
     30 import android.view.ViewGroup;
     31 import android.view.ViewStub;
     32 import android.view.ViewTreeObserver;
     33 import android.widget.QuickContactBadge;
     34 import android.widget.ImageView;
     35 import android.widget.TextView;
     36 
     37 import com.android.contacts.common.CallUtil;
     38 import com.android.contacts.common.ContactPhotoManager;
     39 import com.android.contacts.common.ContactPhotoManager.DefaultImageRequest;
     40 import com.android.contacts.common.testing.NeededForTesting;
     41 import com.android.contacts.common.util.UriUtils;
     42 import com.android.dialer.R;
     43 import com.android.dialer.calllog.CallLogAsyncTaskUtil;
     44 import com.android.dialer.util.DialerUtils;
     45 import com.android.dialer.util.PhoneNumberUtil;
     46 import com.android.dialer.voicemail.VoicemailPlaybackPresenter;
     47 import com.android.dialer.voicemail.VoicemailPlaybackLayout;
     48 
     49 /**
     50  * This is an object containing references to views contained by the call log list item. This
     51  * improves performance by reducing the frequency with which we need to find views by IDs.
     52  *
     53  * This object also contains UI logic pertaining to the view, to isolate it from the CallLogAdapter.
     54  */
     55 public final class CallLogListItemViewHolder extends RecyclerView.ViewHolder
     56         implements View.OnClickListener {
     57 
     58     /** The root view of the call log list item */
     59     public final View rootView;
     60     /** The quick contact badge for the contact. */
     61     public final QuickContactBadge quickContactView;
     62     /** The primary action view of the entry. */
     63     public final View primaryActionView;
     64     /** The details of the phone call. */
     65     public final PhoneCallDetailsViews phoneCallDetailsViews;
     66     /** The text of the header for a day grouping. */
     67     public final TextView dayGroupHeader;
     68     /** The view containing the details for the call log row, including the action buttons. */
     69     public final CardView callLogEntryView;
     70     /** The actionable view which places a call to the number corresponding to the call log row. */
     71     public final ImageView primaryActionButtonView;
     72 
     73     /** The view containing call log item actions.  Null until the ViewStub is inflated. */
     74     public View actionsView;
     75     /** The button views below are assigned only when the action section is expanded. */
     76     public VoicemailPlaybackLayout voicemailPlaybackView;
     77     public View callButtonView;
     78     public View videoCallButtonView;
     79     public View createNewContactButtonView;
     80     public View addToExistingContactButtonView;
     81     public View sendMessageView;
     82     public View detailsButtonView;
     83 
     84     /**
     85      * The row Id for the first call associated with the call log entry.  Used as a key for the
     86      * map used to track which call log entries have the action button section expanded.
     87      */
     88     public long rowId;
     89 
     90     /**
     91      * The call Ids for the calls represented by the current call log entry.  Used when the user
     92      * deletes a call log entry.
     93      */
     94     public long[] callIds;
     95 
     96     /**
     97      * The callable phone number for the current call log entry.  Cached here as the call back
     98      * intent is set only when the actions ViewStub is inflated.
     99      */
    100     public String number;
    101 
    102     /**
    103      * The phone number presentation for the current call log entry.  Cached here as the call back
    104      * intent is set only when the actions ViewStub is inflated.
    105      */
    106     public int numberPresentation;
    107 
    108     /**
    109      * The type of call for the current call log entry.  Cached here as the call back
    110      * intent is set only when the actions ViewStub is inflated.
    111      */
    112     public int callType;
    113 
    114     /**
    115      * The account for the current call log entry.  Cached here as the call back
    116      * intent is set only when the actions ViewStub is inflated.
    117      */
    118     public PhoneAccountHandle accountHandle;
    119 
    120     /**
    121      * If the call has an associated voicemail message, the URI of the voicemail message for
    122      * playback.  Cached here as the voicemail intent is only set when the actions ViewStub is
    123      * inflated.
    124      */
    125     public String voicemailUri;
    126 
    127     /**
    128      * The name or number associated with the call.  Cached here for use when setting content
    129      * descriptions on buttons in the actions ViewStub when it is inflated.
    130      */
    131     public CharSequence nameOrNumber;
    132 
    133     /**
    134      * The contact info for the contact displayed in this list item.
    135      */
    136     public ContactInfo info;
    137 
    138     private static final int VOICEMAIL_TRANSCRIPTION_MAX_LINES = 10;
    139 
    140     private final Context mContext;
    141     private final TelecomCallLogCache mTelecomCallLogCache;
    142     private final CallLogListItemHelper mCallLogListItemHelper;
    143     private final VoicemailPlaybackPresenter mVoicemailPlaybackPresenter;
    144 
    145     private final int mPhotoSize;
    146 
    147     private View.OnClickListener mExpandCollapseListener;
    148     private boolean mVoicemailPrimaryActionButtonClicked;
    149 
    150     private CallLogListItemViewHolder(
    151             Context context,
    152             View.OnClickListener expandCollapseListener,
    153             TelecomCallLogCache telecomCallLogCache,
    154             CallLogListItemHelper callLogListItemHelper,
    155             VoicemailPlaybackPresenter voicemailPlaybackPresenter,
    156             View rootView,
    157             QuickContactBadge quickContactView,
    158             View primaryActionView,
    159             PhoneCallDetailsViews phoneCallDetailsViews,
    160             CardView callLogEntryView,
    161             TextView dayGroupHeader,
    162             ImageView primaryActionButtonView) {
    163         super(rootView);
    164 
    165         mContext = context;
    166         mExpandCollapseListener = expandCollapseListener;
    167         mTelecomCallLogCache = telecomCallLogCache;
    168         mCallLogListItemHelper = callLogListItemHelper;
    169         mVoicemailPlaybackPresenter = voicemailPlaybackPresenter;
    170 
    171         this.rootView = rootView;
    172         this.quickContactView = quickContactView;
    173         this.primaryActionView = primaryActionView;
    174         this.phoneCallDetailsViews = phoneCallDetailsViews;
    175         this.callLogEntryView = callLogEntryView;
    176         this.dayGroupHeader = dayGroupHeader;
    177         this.primaryActionButtonView = primaryActionButtonView;
    178 
    179         Resources resources = mContext.getResources();
    180         mPhotoSize = mContext.getResources().getDimensionPixelSize(R.dimen.contact_photo_size);
    181 
    182         // Set text height to false on the TextViews so they don't have extra padding.
    183         phoneCallDetailsViews.nameView.setElegantTextHeight(false);
    184         phoneCallDetailsViews.callLocationAndDate.setElegantTextHeight(false);
    185 
    186         quickContactView.setPrioritizedMimeType(Phone.CONTENT_ITEM_TYPE);
    187 
    188         primaryActionButtonView.setOnClickListener(this);
    189         primaryActionView.setOnClickListener(mExpandCollapseListener);
    190     }
    191 
    192     public static CallLogListItemViewHolder create(
    193             View view,
    194             Context context,
    195             View.OnClickListener expandCollapseListener,
    196             TelecomCallLogCache telecomCallLogCache,
    197             CallLogListItemHelper callLogListItemHelper,
    198             VoicemailPlaybackPresenter voicemailPlaybackPresenter) {
    199 
    200         return new CallLogListItemViewHolder(
    201                 context,
    202                 expandCollapseListener,
    203                 telecomCallLogCache,
    204                 callLogListItemHelper,
    205                 voicemailPlaybackPresenter,
    206                 view,
    207                 (QuickContactBadge) view.findViewById(R.id.quick_contact_photo),
    208                 view.findViewById(R.id.primary_action_view),
    209                 PhoneCallDetailsViews.fromView(view),
    210                 (CardView) view.findViewById(R.id.call_log_row),
    211                 (TextView) view.findViewById(R.id.call_log_day_group_label),
    212                 (ImageView) view.findViewById(R.id.primary_action_button));
    213     }
    214 
    215     /**
    216      * Configures the action buttons in the expandable actions ViewStub. The ViewStub is not
    217      * inflated during initial binding, so click handlers, tags and accessibility text must be set
    218      * here, if necessary.
    219      *
    220      * @param callLogItem The call log list item view.
    221      */
    222     public void inflateActionViewStub() {
    223         ViewStub stub = (ViewStub) rootView.findViewById(R.id.call_log_entry_actions_stub);
    224         if (stub != null) {
    225             actionsView = (ViewGroup) stub.inflate();
    226 
    227             voicemailPlaybackView = (VoicemailPlaybackLayout) actionsView
    228                     .findViewById(R.id.voicemail_playback_layout);
    229 
    230             callButtonView = actionsView.findViewById(R.id.call_action);
    231             callButtonView.setOnClickListener(this);
    232 
    233             videoCallButtonView = actionsView.findViewById(R.id.video_call_action);
    234             videoCallButtonView.setOnClickListener(this);
    235 
    236             createNewContactButtonView = actionsView.findViewById(R.id.create_new_contact_action);
    237             createNewContactButtonView.setOnClickListener(this);
    238 
    239             addToExistingContactButtonView =
    240                     actionsView.findViewById(R.id.add_to_existing_contact_action);
    241             addToExistingContactButtonView.setOnClickListener(this);
    242 
    243             sendMessageView = actionsView.findViewById(R.id.send_message_action);
    244             sendMessageView.setOnClickListener(this);
    245 
    246             detailsButtonView = actionsView.findViewById(R.id.details_action);
    247             detailsButtonView.setOnClickListener(this);
    248         }
    249 
    250         bindActionButtons();
    251     }
    252 
    253     private void updatePrimaryActionButton(boolean isExpanded) {
    254         if (!TextUtils.isEmpty(voicemailUri)) {
    255             // Treat as voicemail list item; show play button if not expanded.
    256             if (!isExpanded) {
    257                 primaryActionButtonView.setImageResource(R.drawable.ic_play_arrow_24dp);
    258                 primaryActionButtonView.setVisibility(View.VISIBLE);
    259             } else {
    260                 primaryActionButtonView.setVisibility(View.GONE);
    261             }
    262         } else {
    263             // Treat as normal list item; show call button, if possible.
    264             boolean canPlaceCallToNumber =
    265                     PhoneNumberUtil.canPlaceCallsTo(number, numberPresentation);
    266 
    267             if (canPlaceCallToNumber) {
    268                 boolean isVoicemailNumber =
    269                         mTelecomCallLogCache.isVoicemailNumber(accountHandle, number);
    270                 if (isVoicemailNumber) {
    271                     // Call to generic voicemail number, in case there are multiple accounts.
    272                     primaryActionButtonView.setTag(
    273                             IntentProvider.getReturnVoicemailCallIntentProvider());
    274                 } else {
    275                     primaryActionButtonView.setTag(
    276                             IntentProvider.getReturnCallIntentProvider(number));
    277                 }
    278 
    279                 primaryActionButtonView.setContentDescription(TextUtils.expandTemplate(
    280                         mContext.getString(R.string.description_call_action),
    281                         nameOrNumber));
    282                 primaryActionButtonView.setImageResource(R.drawable.ic_call_24dp);
    283                 primaryActionButtonView.setVisibility(View.VISIBLE);
    284             } else {
    285                 primaryActionButtonView.setTag(null);
    286                 primaryActionButtonView.setVisibility(View.GONE);
    287             }
    288         }
    289     }
    290 
    291     /**
    292      * Binds text titles, click handlers and intents to the voicemail, details and callback action
    293      * buttons.
    294      */
    295     private void bindActionButtons() {
    296         boolean canPlaceCallToNumber = PhoneNumberUtil.canPlaceCallsTo(number, numberPresentation);
    297 
    298         if (!TextUtils.isEmpty(voicemailUri) && canPlaceCallToNumber) {
    299             callButtonView.setTag(IntentProvider.getReturnCallIntentProvider(number));
    300             ((TextView) callButtonView.findViewById(R.id.call_action_text))
    301                     .setText(TextUtils.expandTemplate(
    302                             mContext.getString(R.string.call_log_action_call),
    303                             nameOrNumber));
    304             callButtonView.setVisibility(View.VISIBLE);
    305         } else {
    306             callButtonView.setVisibility(View.GONE);
    307         }
    308 
    309         // If one of the calls had video capabilities, show the video call button.
    310         if (mTelecomCallLogCache.isVideoEnabled() && canPlaceCallToNumber &&
    311                 phoneCallDetailsViews.callTypeIcons.isVideoShown()) {
    312             videoCallButtonView.setTag(IntentProvider.getReturnVideoCallIntentProvider(number));
    313             videoCallButtonView.setVisibility(View.VISIBLE);
    314         } else {
    315             videoCallButtonView.setVisibility(View.GONE);
    316         }
    317 
    318         // For voicemail calls, show the voicemail playback layout; hide otherwise.
    319         if (callType == Calls.VOICEMAIL_TYPE && mVoicemailPlaybackPresenter != null) {
    320             voicemailPlaybackView.setVisibility(View.VISIBLE);
    321 
    322             Uri uri = Uri.parse(voicemailUri);
    323             mVoicemailPlaybackPresenter.setPlaybackView(
    324                     voicemailPlaybackView, uri, mVoicemailPrimaryActionButtonClicked);
    325             mVoicemailPrimaryActionButtonClicked = false;
    326 
    327             CallLogAsyncTaskUtil.markVoicemailAsRead(mContext, uri);
    328         } else {
    329             voicemailPlaybackView.setVisibility(View.GONE);
    330         }
    331 
    332         detailsButtonView.setVisibility(View.VISIBLE);
    333         detailsButtonView.setTag(
    334                 IntentProvider.getCallDetailIntentProvider(rowId, callIds, null));
    335 
    336         if (info != null && UriUtils.isEncodedContactUri(info.lookupUri)) {
    337             createNewContactButtonView.setTag(IntentProvider.getAddContactIntentProvider(
    338                     info.lookupUri, info.name, info.number, info.type, true /* isNewContact */));
    339             createNewContactButtonView.setVisibility(View.VISIBLE);
    340 
    341             addToExistingContactButtonView.setTag(IntentProvider.getAddContactIntentProvider(
    342                     info.lookupUri, info.name, info.number, info.type, false /* isNewContact */));
    343             addToExistingContactButtonView.setVisibility(View.VISIBLE);
    344         } else {
    345             createNewContactButtonView.setVisibility(View.GONE);
    346             addToExistingContactButtonView.setVisibility(View.GONE);
    347         }
    348 
    349         sendMessageView.setTag(IntentProvider.getSendSmsIntentProvider(number));
    350 
    351         mCallLogListItemHelper.setActionContentDescriptions(this);
    352     }
    353 
    354     /**
    355      * Show or hide the action views, such as voicemail, details, and add contact.
    356      *
    357      * If the action views have never been shown yet for this view, inflate the view stub.
    358      */
    359     public void showActions(boolean show) {
    360         expandVoicemailTranscriptionView(show);
    361 
    362         if (show) {
    363             // Inflate the view stub if necessary, and wire up the event handlers.
    364             inflateActionViewStub();
    365 
    366             actionsView.setVisibility(View.VISIBLE);
    367             actionsView.setAlpha(1.0f);
    368         } else {
    369             // When recycling a view, it is possible the actionsView ViewStub was previously
    370             // inflated so we should hide it in this case.
    371             if (actionsView != null) {
    372                 actionsView.setVisibility(View.GONE);
    373             }
    374         }
    375 
    376         updatePrimaryActionButton(show);
    377     }
    378 
    379     public void expandVoicemailTranscriptionView(boolean isExpanded) {
    380         if (callType != Calls.VOICEMAIL_TYPE) {
    381             return;
    382         }
    383 
    384         final TextView view = phoneCallDetailsViews.voicemailTranscriptionView;
    385         if (TextUtils.isEmpty(view.getText())) {
    386             return;
    387         }
    388         view.setMaxLines(isExpanded ? VOICEMAIL_TRANSCRIPTION_MAX_LINES : 1);
    389         view.setSingleLine(!isExpanded);
    390     }
    391 
    392     public void setPhoto(long photoId, Uri photoUri, Uri contactUri, String displayName,
    393             boolean isVoicemail, boolean isBusiness) {
    394         quickContactView.assignContactUri(contactUri);
    395         quickContactView.setOverlay(null);
    396 
    397         int contactType = ContactPhotoManager.TYPE_DEFAULT;
    398         if (isVoicemail) {
    399             contactType = ContactPhotoManager.TYPE_VOICEMAIL;
    400         } else if (isBusiness) {
    401             contactType = ContactPhotoManager.TYPE_BUSINESS;
    402         }
    403 
    404         String lookupKey = null;
    405         if (contactUri != null) {
    406             lookupKey = ContactInfoHelper.getLookupKeyFromUri(contactUri);
    407         }
    408 
    409         DefaultImageRequest request = new DefaultImageRequest(
    410                 displayName, lookupKey, contactType, true /* isCircular */);
    411 
    412         if (photoId == 0 && photoUri != null) {
    413             ContactPhotoManager.getInstance(mContext).loadPhoto(quickContactView, photoUri,
    414                     mPhotoSize, false /* darkTheme */, true /* isCircular */, request);
    415         } else {
    416             ContactPhotoManager.getInstance(mContext).loadThumbnail(quickContactView, photoId,
    417                     false /* darkTheme */, true /* isCircular */, request);
    418         }
    419     }
    420 
    421     @Override
    422     public void onClick(View view) {
    423         if (view.getId() == R.id.primary_action_button && !TextUtils.isEmpty(voicemailUri)) {
    424             mVoicemailPrimaryActionButtonClicked = true;
    425             mExpandCollapseListener.onClick(primaryActionView);
    426         } else {
    427             final IntentProvider intentProvider = (IntentProvider) view.getTag();
    428             if (intentProvider != null) {
    429                 final Intent intent = intentProvider.getIntent(mContext);
    430                 // See IntentProvider.getCallDetailIntentProvider() for why this may be null.
    431                 if (intent != null) {
    432                     DialerUtils.startActivityWithErrorToast(mContext, intent);
    433                 }
    434             }
    435         }
    436     }
    437 
    438     @NeededForTesting
    439     public static CallLogListItemViewHolder createForTest(Context context) {
    440         Resources resources = context.getResources();
    441         TelecomCallLogCache telecomCallLogCache = new TelecomCallLogCache(context);
    442         PhoneCallDetailsHelper phoneCallDetailsHelper = new PhoneCallDetailsHelper(
    443                 context, resources, telecomCallLogCache);
    444 
    445         CallLogListItemViewHolder viewHolder = new CallLogListItemViewHolder(
    446                 context,
    447                 null /* expandCollapseListener */,
    448                 telecomCallLogCache,
    449                 new CallLogListItemHelper(phoneCallDetailsHelper, resources, telecomCallLogCache),
    450                 null /* voicemailPlaybackPresenter */,
    451                 new View(context),
    452                 new QuickContactBadge(context),
    453                 new View(context),
    454                 PhoneCallDetailsViews.createForTest(context),
    455                 new CardView(context),
    456                 new TextView(context),
    457                 new ImageView(context));
    458         viewHolder.detailsButtonView = new TextView(context);
    459         viewHolder.actionsView = new View(context);
    460         viewHolder.voicemailPlaybackView = new VoicemailPlaybackLayout(context);
    461 
    462         return viewHolder;
    463     }
    464 }
    465