/* * 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.calldetails; import android.content.ActivityNotFoundException; import android.content.ContentUris; import android.content.Context; import android.content.Intent; import android.net.Uri; import android.provider.CallLog.Calls; import android.provider.MediaStore; import android.support.annotation.ColorInt; import android.support.annotation.NonNull; import android.support.v4.content.ContextCompat; import android.support.v4.content.FileProvider; import android.support.v4.os.BuildCompat; import android.support.v7.widget.RecyclerView.ViewHolder; import android.text.TextUtils; import android.text.format.DateFormat; import android.view.Menu; import android.view.View; import android.webkit.MimeTypeMap; import android.widget.ImageView; import android.widget.PopupMenu; import android.widget.TextView; import android.widget.Toast; import com.android.dialer.calldetails.CallDetailsEntries.CallDetailsEntry; import com.android.dialer.calllogutils.CallLogDates; import com.android.dialer.calllogutils.CallLogDurations; import com.android.dialer.calllogutils.CallTypeHelper; import com.android.dialer.calllogutils.CallTypeIconsView; import com.android.dialer.callrecord.CallRecording; import com.android.dialer.callrecord.CallRecordingDataStore; import com.android.dialer.callrecord.impl.CallRecorderService; import com.android.dialer.common.LogUtil; import com.android.dialer.enrichedcall.historyquery.proto.HistoryResult; import com.android.dialer.enrichedcall.historyquery.proto.HistoryResult.Type; import com.android.dialer.glidephotomanager.PhotoInfo; import com.android.dialer.oem.MotorolaUtils; import com.android.dialer.util.DialerUtils; import com.android.dialer.util.IntentUtil; import java.text.SimpleDateFormat; import java.util.Date; import java.util.List; import java.util.Locale; /** ViewHolder for call entries in {@link OldCallDetailsActivity} or {@link CallDetailsActivity}. */ public class CallDetailsEntryViewHolder extends ViewHolder { /** Listener for the call details header */ interface CallDetailsEntryListener { /** Shows RTT transcript. */ void showRttTranscript(String transcriptId, String primaryText, PhotoInfo photoInfo); } private final CallDetailsEntryListener callDetailsEntryListener; private final CallTypeIconsView callTypeIcon; private final TextView callTypeText; private final TextView callTime; private final TextView callDuration; private final View multimediaImageContainer; private final View multimediaDetailsContainer; private final View multimediaDivider; private final TextView multimediaDetails; private final TextView postCallNote; private final TextView rttTranscript; private final ImageView multimediaImage; private final TextView playbackButton; // TODO(maxwelb): Display this when location is stored - a bug @SuppressWarnings("unused") private final TextView multimediaAttachmentsNumber; private final Context context; public CallDetailsEntryViewHolder( View container, CallDetailsEntryListener callDetailsEntryListener) { super(container); context = container.getContext(); callTypeIcon = (CallTypeIconsView) container.findViewById(R.id.call_direction); callTypeText = (TextView) container.findViewById(R.id.call_type); callTime = (TextView) container.findViewById(R.id.call_time); callDuration = (TextView) container.findViewById(R.id.call_duration); playbackButton = (TextView) container.findViewById(R.id.play_recordings); multimediaImageContainer = container.findViewById(R.id.multimedia_image_container); multimediaDetailsContainer = container.findViewById(R.id.ec_container); multimediaDivider = container.findViewById(R.id.divider); multimediaDetails = (TextView) container.findViewById(R.id.multimedia_details); postCallNote = (TextView) container.findViewById(R.id.post_call_note); multimediaImage = (ImageView) container.findViewById(R.id.multimedia_image); multimediaAttachmentsNumber = (TextView) container.findViewById(R.id.multimedia_attachments_number); rttTranscript = container.findViewById(R.id.rtt_transcript); this.callDetailsEntryListener = callDetailsEntryListener; } void setCallDetails( String number, String primaryText, PhotoInfo photoInfo, CallDetailsEntry entry, CallTypeHelper callTypeHelper, CallRecordingDataStore callRecordingDataStore, boolean showMultimediaDivider) { int callType = entry.getCallType(); boolean isVideoCall = (entry.getFeatures() & Calls.FEATURES_VIDEO) == Calls.FEATURES_VIDEO; boolean isPulledCall = (entry.getFeatures() & Calls.FEATURES_PULLED_EXTERNALLY) == Calls.FEATURES_PULLED_EXTERNALLY; boolean isDuoCall = entry.getIsDuoCall(); boolean isRttCall = BuildCompat.isAtLeastP() && (entry.getFeatures() & Calls.FEATURES_RTT) == Calls.FEATURES_RTT; callTime.setTextColor(getColorForCallType(context, callType)); callTypeIcon.clear(); callTypeIcon.add(callType); callTypeIcon.setShowVideo(isVideoCall); callTypeIcon.setShowHd( (entry.getFeatures() & Calls.FEATURES_HD_CALL) == Calls.FEATURES_HD_CALL); callTypeIcon.setShowWifi( MotorolaUtils.shouldShowWifiIconInCallLog(context, entry.getFeatures())); if (BuildCompat.isAtLeastP()) { callTypeIcon.setShowRtt((entry.getFeatures() & Calls.FEATURES_RTT) == Calls.FEATURES_RTT); } callTypeText.setText( callTypeHelper.getCallTypeText(callType, isVideoCall, isPulledCall, isDuoCall)); callTime.setText(CallLogDates.formatDate(context, entry.getDate())); if (CallTypeHelper.isMissedCallType(callType)) { callDuration.setVisibility(View.GONE); } else { callDuration.setVisibility(View.VISIBLE); callDuration.setText( CallLogDurations.formatDurationAndDataUsage( context, entry.getDuration(), entry.getDataUsage())); callDuration.setContentDescription( CallLogDurations.formatDurationAndDataUsageA11y( context, entry.getDuration(), entry.getDataUsage())); } // do this synchronously to prevent recordings from "popping in" after detail item is displayed final List recordings; if (CallRecorderService.isEnabled(context)) { callRecordingDataStore.open(context); // opens unless already open recordings = callRecordingDataStore.getRecordings(number, entry.getDate()); } else { recordings = null; } int count = recordings != null ? recordings.size() : 0; playbackButton.setOnClickListener(v -> handleRecordingClick(v, recordings)); playbackButton.setText( context.getResources().getQuantityString(R.plurals.play_recordings, count, count)); playbackButton.setVisibility(count > 0 ? View.VISIBLE : View.GONE); setMultimediaDetails(number, entry, showMultimediaDivider); if (isRttCall) { if (entry.getHasRttTranscript()) { rttTranscript.setText(R.string.rtt_transcript_link); rttTranscript.setTextAppearance(R.style.RttTranscriptLink); rttTranscript.setClickable(true); rttTranscript.setOnClickListener( v -> callDetailsEntryListener.showRttTranscript( entry.getCallMappingId(), primaryText, photoInfo)); } else { rttTranscript.setText(R.string.rtt_transcript_not_available); rttTranscript.setTextAppearance(R.style.RttTranscriptMessage); rttTranscript.setClickable(false); } rttTranscript.setVisibility(View.VISIBLE); } else { rttTranscript.setVisibility(View.GONE); } } private void setMultimediaDetails(String number, CallDetailsEntry entry, boolean showDivider) { multimediaDivider.setVisibility(showDivider ? View.VISIBLE : View.GONE); if (entry.getHistoryResultsList().isEmpty()) { LogUtil.i("CallDetailsEntryViewHolder.setMultimediaDetails", "no data, hiding UI"); multimediaDetailsContainer.setVisibility(View.GONE); } else { HistoryResult historyResult = entry.getHistoryResults(0); multimediaDetailsContainer.setVisibility(View.VISIBLE); multimediaDetailsContainer.setOnClickListener((v) -> startSmsIntent(context, number)); multimediaImageContainer.setOnClickListener((v) -> startSmsIntent(context, number)); multimediaImageContainer.setClipToOutline(true); if (!TextUtils.isEmpty(historyResult.getImageUri())) { LogUtil.i("CallDetailsEntryViewHolder.setMultimediaDetails", "setting image"); multimediaImageContainer.setVisibility(View.VISIBLE); multimediaImage.setImageURI(Uri.parse(historyResult.getImageUri())); multimediaDetails.setText( isIncoming(historyResult) ? R.string.received_a_photo : R.string.sent_a_photo); } else { LogUtil.i("CallDetailsEntryViewHolder.setMultimediaDetails", "no image"); } // Set text after image to overwrite the received/sent a photo text if (!TextUtils.isEmpty(historyResult.getText())) { LogUtil.i("CallDetailsEntryViewHolder.setMultimediaDetails", "showing text"); multimediaDetails.setText( context.getString(R.string.message_in_quotes, historyResult.getText())); } else { LogUtil.i("CallDetailsEntryViewHolder.setMultimediaDetails", "no text"); } if (entry.getHistoryResultsList().size() > 1 && !TextUtils.isEmpty(entry.getHistoryResults(1).getText())) { LogUtil.i("CallDetailsEntryViewHolder.setMultimediaDetails", "showing post call note"); postCallNote.setVisibility(View.VISIBLE); postCallNote.setText( context.getString(R.string.message_in_quotes, entry.getHistoryResults(1).getText())); postCallNote.setOnClickListener((v) -> startSmsIntent(context, number)); } else { LogUtil.i("CallDetailsEntryViewHolder.setMultimediaDetails", "no post call note"); } } } private void startSmsIntent(Context context, String number) { DialerUtils.startActivityWithErrorToast(context, IntentUtil.getSendSmsIntent(number)); } private void handleRecordingClick(View v, List recordings) { final Context context = v.getContext(); if (recordings.size() == 1) { playRecording(context, recordings.get(0)); } else { PopupMenu menu = new PopupMenu(context, v); String pattern = DateFormat.getBestDateTimePattern(Locale.getDefault(), DateFormat.is24HourFormat(context) ? "Hmss" : "hmssa"); SimpleDateFormat format = new SimpleDateFormat(pattern); for (int i = 0; i < recordings.size(); i++) { final long startTime = recordings.get(i).startRecordingTime; final String formattedDate = format.format(new Date(startTime)); menu.getMenu().add(Menu.NONE, i, i, formattedDate); } menu.setOnMenuItemClickListener(item -> { playRecording(context, recordings.get(item.getItemId())); return true; }); menu.show(); } } private void playRecording(Context context, CallRecording recording) { Uri uri = ContentUris.withAppendedId( MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, recording.mediaId); String extension = MimeTypeMap.getFileExtensionFromUrl(recording.fileName); String mime = !TextUtils.isEmpty(extension) ? MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension) : "audio/*"; try { Intent intent = new Intent(Intent.ACTION_VIEW) .setDataAndType(uri, mime) .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); context.startActivity(intent); } catch (ActivityNotFoundException e) { Toast.makeText(context, R.string.call_playback_no_app_found_toast, Toast.LENGTH_LONG) .show(); } } private static boolean isIncoming(@NonNull HistoryResult historyResult) { return historyResult.getType() == Type.INCOMING_POST_CALL || historyResult.getType() == Type.INCOMING_CALL_COMPOSER; } private static @ColorInt int getColorForCallType(Context context, int callType) { switch (callType) { case Calls.OUTGOING_TYPE: case Calls.VOICEMAIL_TYPE: case Calls.BLOCKED_TYPE: case Calls.INCOMING_TYPE: case Calls.ANSWERED_EXTERNALLY_TYPE: case Calls.REJECTED_TYPE: return ContextCompat.getColor(context, R.color.dialer_secondary_text_color); case Calls.MISSED_TYPE: default: // It is possible for users to end up with calls with unknown call types in their // call history, possibly due to 3rd party call log implementations (e.g. to // distinguish between rejected and missed calls). Instead of crashing, just // assume that all unknown call types are missed calls. return ContextCompat.getColor(context, R.color.dialer_red); } } }