blob: ed533be58f5fe7685514206a967e32992b87b712 [file] [log] [blame]
/*
* Copyright (C) 2016 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.incallui.contactgrid;
import android.content.Context;
import android.graphics.drawable.Animatable;
import android.graphics.drawable.Drawable;
import android.os.SystemClock;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.view.ViewCompat;
import android.telecom.TelecomManager;
import android.text.TextUtils;
import android.view.View;
import android.view.accessibility.AccessibilityEvent;
import android.widget.Chronometer;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.ViewAnimator;
import com.android.contacts.common.compat.PhoneNumberUtilsCompat;
import com.android.contacts.common.lettertiles.LetterTileDrawable;
import com.android.dialer.common.Assert;
import com.android.dialer.util.DrawableConverter;
import com.android.incallui.incall.protocol.ContactPhotoType;
import com.android.incallui.incall.protocol.PrimaryCallState;
import com.android.incallui.incall.protocol.PrimaryInfo;
import java.util.List;
/** Utility to manage the Contact grid */
public class ContactGridManager {
private final Context context;
private final View contactGridLayout;
// Row 0: Captain Holt ON HOLD
// Row 0: Calling...
// Row 0: [Wi-Fi icon] Calling via Starbucks Wi-Fi
// Row 0: [Wi-Fi icon] Starbucks Wi-Fi
// Row 0: Hey Jake, pick up!
private final ImageView connectionIconImageView;
private final TextView statusTextView;
// Row 1: Jake Peralta [Contact photo]
// Row 1: Walgreens
// Row 1: +1 (650) 253-0000
private final TextView contactNameTextView;
@Nullable private ImageView avatarImageView;
// Row 2: Mobile +1 (650) 253-0000
// Row 2: [HD attempting icon]/[HD icon] 00:15
// Row 2: Call ended
// Row 2: Hanging up
// Row 2: [Alert sign] Suspected spam caller
// Row 2: Your emergency callback number: +1 (650) 253-0000
private final ImageView workIconImageView;
private final ImageView hdIconImageView;
private final ImageView forwardIconImageView;
private final TextView forwardedNumberView;
private final ImageView spamIconImageView;
private final ViewAnimator bottomTextSwitcher;
private final TextView bottomTextView;
private final Chronometer bottomTimerView;
private int avatarSize;
private boolean hideAvatar;
private boolean showAnonymousAvatar;
private boolean middleRowVisible = true;
private PrimaryInfo primaryInfo = PrimaryInfo.createEmptyPrimaryInfo();
private PrimaryCallState primaryCallState = PrimaryCallState.createEmptyPrimaryCallState();
private final LetterTileDrawable letterTile;
public ContactGridManager(
View view, @Nullable ImageView avatarImageView, int avatarSize, boolean showAnonymousAvatar) {
context = view.getContext();
Assert.isNotNull(context);
this.avatarImageView = avatarImageView;
this.avatarSize = avatarSize;
this.showAnonymousAvatar = showAnonymousAvatar;
connectionIconImageView = view.findViewById(R.id.contactgrid_connection_icon);
statusTextView = view.findViewById(R.id.contactgrid_status_text);
contactNameTextView = view.findViewById(R.id.contactgrid_contact_name);
workIconImageView = view.findViewById(R.id.contactgrid_workIcon);
hdIconImageView = view.findViewById(R.id.contactgrid_hdIcon);
forwardIconImageView = view.findViewById(R.id.contactgrid_forwardIcon);
forwardedNumberView = view.findViewById(R.id.contactgrid_forwardNumber);
spamIconImageView = view.findViewById(R.id.contactgrid_spamIcon);
bottomTextSwitcher = view.findViewById(R.id.contactgrid_bottom_text_switcher);
bottomTextView = view.findViewById(R.id.contactgrid_bottom_text);
bottomTimerView = view.findViewById(R.id.contactgrid_bottom_timer);
contactGridLayout = (View) contactNameTextView.getParent();
letterTile = new LetterTileDrawable(context.getResources());
}
public void show() {
contactGridLayout.setVisibility(View.VISIBLE);
}
public void hide() {
contactGridLayout.setVisibility(View.GONE);
}
public void setAvatarHidden(boolean hide) {
if (hide != hideAvatar) {
hideAvatar = hide;
updatePrimaryNameAndPhoto();
}
}
public boolean isAvatarHidden() {
return hideAvatar;
}
public View getContainerView() {
return contactGridLayout;
}
public void setIsMiddleRowVisible(boolean isMiddleRowVisible) {
if (middleRowVisible == isMiddleRowVisible) {
return;
}
middleRowVisible = isMiddleRowVisible;
contactNameTextView.setVisibility(isMiddleRowVisible ? View.VISIBLE : View.GONE);
updateAvatarVisibility();
}
public void setPrimary(PrimaryInfo primaryInfo) {
this.primaryInfo = primaryInfo;
updatePrimaryNameAndPhoto();
updateBottomRow();
}
public void setCallState(PrimaryCallState primaryCallState) {
this.primaryCallState = primaryCallState;
updatePrimaryNameAndPhoto();
updateBottomRow();
updateTopRow();
}
public void dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
dispatchPopulateAccessibilityEvent(event, statusTextView);
dispatchPopulateAccessibilityEvent(event, contactNameTextView);
BottomRow.Info info = BottomRow.getInfo(context, primaryCallState, primaryInfo);
if (info.shouldPopulateAccessibilityEvent) {
dispatchPopulateAccessibilityEvent(event, bottomTextView);
}
}
public void setAvatarImageView(
@Nullable ImageView avatarImageView, int avatarSize, boolean showAnonymousAvatar) {
this.avatarImageView = avatarImageView;
this.avatarSize = avatarSize;
this.showAnonymousAvatar = showAnonymousAvatar;
updatePrimaryNameAndPhoto();
}
private void dispatchPopulateAccessibilityEvent(AccessibilityEvent event, View view) {
final List<CharSequence> eventText = event.getText();
int size = eventText.size();
view.dispatchPopulateAccessibilityEvent(event);
// If no text added write null to keep relative position.
if (size == eventText.size()) {
eventText.add(null);
}
}
private boolean updateAvatarVisibility() {
if (avatarImageView == null) {
return false;
}
if (!middleRowVisible) {
avatarImageView.setVisibility(View.GONE);
return false;
}
boolean hasPhoto =
primaryInfo.photo != null && primaryInfo.photoType == ContactPhotoType.CONTACT;
if (!hasPhoto && !showAnonymousAvatar) {
avatarImageView.setVisibility(View.GONE);
return false;
}
avatarImageView.setVisibility(View.VISIBLE);
return true;
}
/**
* Updates row 0. For example:
*
* <ul>
* <li>Captain Holt ON HOLD
* <li>Calling...
* <li>[Wi-Fi icon] Calling via Starbucks Wi-Fi
* <li>[Wi-Fi icon] Starbucks Wi-Fi
* <li>Call from
* </ul>
*/
private void updateTopRow() {
TopRow.Info info = TopRow.getInfo(context, primaryCallState);
if (TextUtils.isEmpty(info.label)) {
// Use INVISIBLE here to prevent the rows below this one from moving up and down.
statusTextView.setVisibility(View.INVISIBLE);
statusTextView.setText(null);
} else {
statusTextView.setText(info.label);
statusTextView.setVisibility(View.VISIBLE);
statusTextView.setSingleLine(info.labelIsSingleLine);
}
if (info.icon == null) {
connectionIconImageView.setVisibility(View.GONE);
} else {
connectionIconImageView.setVisibility(View.VISIBLE);
connectionIconImageView.setImageDrawable(info.icon);
}
}
/**
* Returns the appropriate LetterTileDrawable.TYPE_ based on a given call state.
*
* <p>If no special state is detected, yields TYPE_DEFAULT.
*/
private static @LetterTileDrawable.ContactType int getContactTypeForPrimaryCallState(
@NonNull PrimaryCallState callState, @NonNull PrimaryInfo primaryInfo) {
if (callState.isVoiceMailNumber) {
return LetterTileDrawable.TYPE_VOICEMAIL;
} else if (callState.isBusinessNumber) {
return LetterTileDrawable.TYPE_BUSINESS;
} else if (primaryInfo.numberPresentation == TelecomManager.PRESENTATION_RESTRICTED) {
return LetterTileDrawable.TYPE_GENERIC_AVATAR;
} else {
return LetterTileDrawable.TYPE_DEFAULT;
}
}
/**
* Updates row 1. For example:
*
* <ul>
* <li>Jake Peralta [Contact photo]
* <li>Walgreens
* <li>+1 (650) 253-0000
* </ul>
*/
private void updatePrimaryNameAndPhoto() {
if (TextUtils.isEmpty(primaryInfo.name)) {
contactNameTextView.setText(null);
} else {
contactNameTextView.setText(
primaryInfo.nameIsNumber
? PhoneNumberUtilsCompat.createTtsSpannable(primaryInfo.name)
: primaryInfo.name);
// Set direction of the name field
int nameDirection = View.TEXT_DIRECTION_INHERIT;
if (primaryInfo.nameIsNumber) {
nameDirection = View.TEXT_DIRECTION_LTR;
}
contactNameTextView.setTextDirection(nameDirection);
}
if (avatarImageView != null) {
if (hideAvatar) {
avatarImageView.setVisibility(View.GONE);
} else if (avatarSize > 0 && updateAvatarVisibility()) {
boolean hasPhoto =
primaryInfo.photo != null && primaryInfo.photoType == ContactPhotoType.CONTACT;
// Contact has a photo, don't render a letter tile.
if (hasPhoto) {
avatarImageView.setBackground(
DrawableConverter.getRoundedDrawable(
context, primaryInfo.photo, avatarSize, avatarSize));
// Contact has a name, that isn't a number.
} else {
letterTile.setCanonicalDialerLetterTileDetails(
primaryInfo.name,
primaryInfo.contactInfoLookupKey,
LetterTileDrawable.SHAPE_CIRCLE,
getContactTypeForPrimaryCallState(primaryCallState, primaryInfo));
// By invalidating the avatarImageView we force a redraw of the letter tile.
// This is required to properly display the updated letter tile iconography based on the
// contact type, because the background drawable reference cached in the view, and the
// view is not aware of the mutations made to the background.
avatarImageView.invalidate();
avatarImageView.setBackground(letterTile);
}
}
}
}
/**
* Updates row 2. For example:
*
* <ul>
* <li>Mobile +1 (650) 253-0000
* <li>[HD attempting icon]/[HD icon] 00:15
* <li>Call ended
* <li>Hanging up
* </ul>
*/
private void updateBottomRow() {
BottomRow.Info info = BottomRow.getInfo(context, primaryCallState, primaryInfo);
bottomTextView.setText(info.label);
bottomTextView.setAllCaps(info.isSpamIconVisible);
workIconImageView.setVisibility(info.isWorkIconVisible ? View.VISIBLE : View.GONE);
if (hdIconImageView.getVisibility() == View.GONE) {
if (info.isHdAttemptingIconVisible) {
hdIconImageView.setVisibility(View.VISIBLE);
hdIconImageView.setActivated(false);
Drawable drawableCurrent = hdIconImageView.getDrawable().getCurrent();
if (drawableCurrent instanceof Animatable && !((Animatable) drawableCurrent).isRunning()) {
((Animatable) drawableCurrent).start();
}
} else if (info.isHdIconVisible) {
hdIconImageView.setVisibility(View.VISIBLE);
hdIconImageView.setActivated(true);
}
} else if (info.isHdIconVisible) {
hdIconImageView.setActivated(true);
} else if (!info.isHdAttemptingIconVisible) {
hdIconImageView.setVisibility(View.GONE);
}
spamIconImageView.setVisibility(info.isSpamIconVisible ? View.VISIBLE : View.GONE);
if (info.isForwardIconVisible) {
forwardIconImageView.setVisibility(View.VISIBLE);
forwardedNumberView.setVisibility(View.VISIBLE);
if (info.isTimerVisible) {
bottomTextSwitcher.setVisibility(View.VISIBLE);
if (ViewCompat.getLayoutDirection(contactGridLayout) == ViewCompat.LAYOUT_DIRECTION_LTR) {
forwardedNumberView.setText(TextUtils.concat(info.label, " • "));
} else {
forwardedNumberView.setText(TextUtils.concat(" • ", info.label));
}
} else {
bottomTextSwitcher.setVisibility(View.GONE);
forwardedNumberView.setText(info.label);
}
} else {
forwardIconImageView.setVisibility(View.GONE);
forwardedNumberView.setVisibility(View.GONE);
}
if (info.isTimerVisible) {
bottomTextSwitcher.setDisplayedChild(1);
bottomTimerView.setBase(
primaryCallState.connectTimeMillis
- System.currentTimeMillis()
+ SystemClock.elapsedRealtime());
bottomTimerView.start();
} else {
bottomTextSwitcher.setDisplayedChild(0);
bottomTimerView.stop();
}
}
}