/* * Copyright (C) 2017 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.dialer.voicemail.listui; import static android.view.View.GONE; import static android.view.View.VISIBLE; import android.app.FragmentManager; import android.content.ContentValues; import android.content.Context; import android.content.Intent; import android.database.Cursor; import android.graphics.Typeface; import android.net.Uri; import android.provider.VoicemailContract.Voicemails; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.annotation.WorkerThread; import android.support.v7.widget.RecyclerView; import android.text.TextUtils; import android.util.Pair; import android.view.View; import android.view.View.OnClickListener; import android.widget.ImageView; import android.widget.QuickContactBadge; import android.widget.TextView; import com.android.dialer.calllog.database.contract.AnnotatedCallLogContract.AnnotatedCallLog; import com.android.dialer.common.Assert; import com.android.dialer.common.LogUtil; import com.android.dialer.common.concurrent.DialerExecutor.SuccessListener; import com.android.dialer.common.concurrent.DialerExecutor.Worker; import com.android.dialer.common.concurrent.DialerExecutorComponent; import com.android.dialer.contactphoto.ContactPhotoManager; import com.android.dialer.lettertile.LetterTileDrawable; import com.android.dialer.time.Clock; import com.android.dialer.voicemail.listui.menu.NewVoicemailMenu; import com.android.dialer.voicemail.model.VoicemailEntry; import com.android.voicemail.VoicemailClient; /** {@link RecyclerView.ViewHolder} for the new voicemail tab. */ final class NewVoicemailViewHolder extends RecyclerView.ViewHolder implements OnClickListener { private final Context context; private final TextView primaryTextView; private final TextView secondaryTextView; private final TextView transcriptionTextView; private final QuickContactBadge quickContactBadge; private final NewVoicemailMediaPlayerView mediaPlayerView; private final ImageView menuButton; private final Clock clock; private boolean isViewHolderExpanded; private int viewHolderId; private VoicemailEntry voicemailEntryOfViewHolder; @NonNull private Uri viewHolderVoicemailUri; private final NewVoicemailViewHolderListener voicemailViewHolderListener; NewVoicemailViewHolder( View view, Clock clock, NewVoicemailViewHolderListener newVoicemailViewHolderListener) { super(view); LogUtil.enterBlock("NewVoicemailViewHolder"); this.context = view.getContext(); primaryTextView = view.findViewById(R.id.primary_text); secondaryTextView = view.findViewById(R.id.secondary_text); transcriptionTextView = view.findViewById(R.id.transcription_text); quickContactBadge = view.findViewById(R.id.quick_contact_photo); mediaPlayerView = view.findViewById(R.id.new_voicemail_media_player); menuButton = view.findViewById(R.id.menu_button); this.clock = clock; voicemailViewHolderListener = newVoicemailViewHolderListener; viewHolderId = -1; isViewHolderExpanded = false; viewHolderVoicemailUri = null; } /** * When the {@link RecyclerView} displays voicemail entries, it might recycle the views upon * scrolling. In that case we need to ensure that the member variables of this {@link * NewVoicemailViewHolder} and its views are correctly set, especially when this {@link * NewVoicemailViewHolder} is recycled. * * @param cursor the voicemail data from {@link AnnotatedCallLog} generated by the {@link * VoicemailCursorLoader} related * @param fragmentManager FragmentManager retrieved from {@link * NewVoicemailFragment#getActivity()} * @param mediaPlayer * @param position the position of the item within the adapter's data set. * @param currentlyExpandedViewHolderId the value the adapter keeps track of which viewholder if */ void bindViewHolderValuesFromAdapter( Cursor cursor, FragmentManager fragmentManager, NewVoicemailMediaPlayer mediaPlayer, int position, int currentlyExpandedViewHolderId) { LogUtil.i( "NewVoicemailViewHolder.bindViewHolderValuesFromAdapter", "view holder at pos:%d, adapterPos:%d, cursorPos:%d, cursorSize:%d", position, getAdapterPosition(), cursor.getPosition(), cursor.getCount()); voicemailEntryOfViewHolder = VoicemailCursorLoader.toVoicemailEntry(cursor); viewHolderId = voicemailEntryOfViewHolder.id(); LogUtil.i( "NewVoicemailViewHolder.bindViewHolderValuesFromAdapter", "viewholderId:%d", viewHolderId); viewHolderVoicemailUri = Uri.parse(voicemailEntryOfViewHolder.voicemailUri()); primaryTextView.setText( VoicemailEntryText.buildPrimaryVoicemailText(context, voicemailEntryOfViewHolder)); secondaryTextView.setText( VoicemailEntryText.buildSecondaryVoicemailText(context, clock, voicemailEntryOfViewHolder)); String voicemailTranscription = voicemailEntryOfViewHolder.transcription(); if (TextUtils.isEmpty(voicemailTranscription)) { transcriptionTextView.setVisibility(GONE); transcriptionTextView.setText(null); } else { transcriptionTextView.setVisibility(View.VISIBLE); transcriptionTextView.setText(voicemailTranscription); } // Bold if voicemail is unread boldViewHolderIfUnread(); itemView.setOnClickListener(this); menuButton.setOnClickListener( NewVoicemailMenu.createOnClickListener(context, voicemailEntryOfViewHolder)); setPhoto(voicemailEntryOfViewHolder); // Update the expanded/collapsed state of this view holder // Only update the binding of the mediaPlayerView of the expanded view holder if (viewHolderId == currentlyExpandedViewHolderId) { LogUtil.i( "NewVoicemailViewHolder.bindViewHolderValuesFromAdapter", "viewHolderId:%d is expanded, update its mediaplayer view", viewHolderId); expandAndBindViewHolderAndMediaPlayerViewWithAdapterValues( voicemailEntryOfViewHolder, fragmentManager, mediaPlayer, voicemailViewHolderListener); LogUtil.i( "NewVoicemailViewHolder.bindViewHolderValuesFromAdapter", "After 2nd updating the MPPlayerView: viewHolderId:%d, uri:%s, MediaplayerView(after " + "updated):%s, adapter position passed down:%d, getAdapterPos:%d", viewHolderId, String.valueOf(viewHolderVoicemailUri), String.valueOf(mediaPlayerView.getVoicemailUri()), position, getAdapterPosition()); Assert.checkArgument( mediaPlayerView.getVisibility() == VISIBLE, "a expanded viewholder should have its media player view visible"); } else { LogUtil.i( "NewVoicemailViewHolder.bindViewHolderValuesFromAdapter", "viewHolderId:%d is not the expanded one, collapse it and don't update the MpView", viewHolderId); collapseViewHolder(); Assert.checkArgument( mediaPlayerView.getVisibility() == GONE, "a collapsed viewholder should not have its media player view visible"); } LogUtil.i( "NewVoicemailViewHolder.bindViewHolderValuesFromAdapter", "Final value after updating: viewHolderId:%d, uri:%s, MediaplayerView(not updated):%s," + " adapter position passed down:%d, getAdapterPos:%d, MPPlayerVisibility:%b", viewHolderId, String.valueOf(viewHolderVoicemailUri), String.valueOf(mediaPlayerView.getVoicemailUri()), position, getAdapterPosition(), mediaPlayerView.getVisibility() == VISIBLE); } private void boldViewHolderIfUnread() { LogUtil.v( "NewVoicemailViewHolder.boldViewHolderIfUnread", "id:%d, isRead:%d", voicemailEntryOfViewHolder.id(), voicemailEntryOfViewHolder.isRead()); if (voicemailEntryOfViewHolder.isRead() == 0) { primaryTextView.setTypeface(Typeface.DEFAULT, Typeface.BOLD); secondaryTextView.setTypeface(Typeface.DEFAULT, Typeface.BOLD); transcriptionTextView.setTypeface(Typeface.DEFAULT, Typeface.BOLD); } } // TODO(uabdullah): Consider/Implement TYPE (e.g Spam, TYPE_VOICEMAIL) private void setPhoto(VoicemailEntry voicemailEntry) { ContactPhotoManager.getInstance(context) .loadDialerThumbnailOrPhoto( quickContactBadge, parseUri(voicemailEntry.numberAttributes().getLookupUri()), voicemailEntry.numberAttributes().getPhotoId(), parseUri(voicemailEntry.numberAttributes().getPhotoUri()), VoicemailEntryText.buildPrimaryVoicemailText(context, voicemailEntry), LetterTileDrawable.TYPE_DEFAULT); } @Nullable private static Uri parseUri(@Nullable String string) { return TextUtils.isEmpty(string) ? null : Uri.parse(string); } void collapseViewHolder() { LogUtil.i( "NewVoicemailViewHolder.collapseViewHolder", "viewHolderId:%d is being collapsed, its MPViewUri:%s, its Uri is :%s", viewHolderId, String.valueOf(mediaPlayerView.getVoicemailUri()), String.valueOf(viewHolderVoicemailUri)); transcriptionTextView.setMaxLines(1); isViewHolderExpanded = false; mediaPlayerView.reset(); mediaPlayerView.setVisibility(GONE); } // When we are recycling the views ensure that we reset the viewHolder, as if its brand new public void reset() { LogUtil.i( "NewVoicemailViewHolder.reset()", "Reset the viewholder, currently viewHolderId:%d, uri:%s, isViewHolderExpanded:%b, " + "its MediaPlayerViewUri:%s", viewHolderId, String.valueOf(viewHolderVoicemailUri), isViewHolderExpanded, String.valueOf(mediaPlayerView.getVoicemailUri())); viewHolderId = -1; isViewHolderExpanded = false; viewHolderVoicemailUri = null; primaryTextView.setTypeface(null, Typeface.NORMAL); secondaryTextView.setTypeface(null, Typeface.NORMAL); transcriptionTextView.setTypeface(null, Typeface.NORMAL); mediaPlayerView.reset(); LogUtil.i( "NewVoicemailViewHolder.reset()", "Reset the viewholder, after resetting viewHolderId:%d, uri:%s, isViewHolderExpanded:%b", viewHolderId, String.valueOf(viewHolderVoicemailUri), isViewHolderExpanded); } /** * Is only called when a user either clicks a {@link NewVoicemailViewHolder} to expand it or if * the user had already expanded, then scrolled the {@link NewVoicemailViewHolder} out of view and * then scrolled it back into view, and during the binding (as the views are recyled in {@link * RecyclerView}) we restore the expanded state of the {@link NewVoicemailViewHolder}. * *
This function also tracks if the state of this viewholder is expanded.
*
* @param voicemailEntry are the voicemail related values from the {@link AnnotatedCallLog}
* @param fragmentManager FragmentManager retrieved from {@link
* NewVoicemailFragment#getActivity()}
* @param mediaPlayer there should only be one instance of this passed down from the {@link
* NewVoicemailAdapter}
* @param voicemailViewHolderListener
*/
void expandAndBindViewHolderAndMediaPlayerViewWithAdapterValues(
VoicemailEntry voicemailEntry,
FragmentManager fragmentManager,
NewVoicemailMediaPlayer mediaPlayer,
NewVoicemailViewHolderListener voicemailViewHolderListener) {
Assert.isNotNull(voicemailViewHolderListener);
Assert.checkArgument(
voicemailEntry.id() == viewHolderId, "ensure that the adapter binding has taken place");
Assert.checkArgument(
Uri.parse(voicemailEntry.voicemailUri()).equals(viewHolderVoicemailUri),
"ensure that the adapter binding has taken place");
LogUtil.i(
"NewVoicemailViewHolder.expandAndBindViewHolderAndMediaPlayerViewWithAdapterValues",
"voicemail id: %d, value of isViewHolderExpanded:%b, before setting it to be true, and"
+ " value of ViewholderUri:%s, MPView:%s, VoicemailRead:%d, before updating it",
viewHolderId,
isViewHolderExpanded,
String.valueOf(viewHolderVoicemailUri),
String.valueOf(mediaPlayerView.getVoicemailUri()),
voicemailEntry.isRead());
if (voicemailEntry.isRead() == 0) {
// update as read.
primaryTextView.setTypeface(Typeface.DEFAULT, Typeface.NORMAL);
secondaryTextView.setTypeface(Typeface.DEFAULT, Typeface.NORMAL);
transcriptionTextView.setTypeface(Typeface.DEFAULT, Typeface.NORMAL);
Uri uri = Uri.parse(voicemailEntry.voicemailUri());
Worker