Home | History | Annotate | Download | only in contactgrid
      1 /*
      2  * Copyright (C) 2016 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.incallui.contactgrid;
     18 
     19 import android.content.Context;
     20 import android.graphics.drawable.Animatable;
     21 import android.graphics.drawable.Drawable;
     22 import android.os.SystemClock;
     23 import android.support.annotation.Nullable;
     24 import android.support.v4.view.ViewCompat;
     25 import android.text.TextUtils;
     26 import android.view.View;
     27 import android.view.accessibility.AccessibilityEvent;
     28 import android.widget.Chronometer;
     29 import android.widget.ImageView;
     30 import android.widget.TextView;
     31 import android.widget.ViewAnimator;
     32 import com.android.contacts.common.compat.PhoneNumberUtilsCompat;
     33 import com.android.contacts.common.lettertiles.LetterTileDrawable;
     34 import com.android.dialer.common.Assert;
     35 import com.android.dialer.common.LogUtil;
     36 import com.android.dialer.util.DrawableConverter;
     37 import com.android.incallui.incall.protocol.ContactPhotoType;
     38 import com.android.incallui.incall.protocol.PrimaryCallState;
     39 import com.android.incallui.incall.protocol.PrimaryInfo;
     40 import java.util.List;
     41 
     42 /** Utility to manage the Contact grid */
     43 public class ContactGridManager {
     44 
     45   private final Context context;
     46   private final View contactGridLayout;
     47 
     48   // Row 0: Captain Holt        ON HOLD
     49   // Row 0: Calling...
     50   // Row 0: [Wi-Fi icon] Calling via Starbucks Wi-Fi
     51   // Row 0: [Wi-Fi icon] Starbucks Wi-Fi
     52   // Row 0: Hey Jake, pick up!
     53   private final ImageView connectionIconImageView;
     54   private final TextView statusTextView;
     55 
     56   // Row 1: Jake Peralta        [Contact photo]
     57   // Row 1: Walgreens
     58   // Row 1: +1 (650) 253-0000
     59   private final TextView contactNameTextView;
     60   @Nullable private ImageView avatarImageView;
     61 
     62   // Row 2: Mobile +1 (650) 253-0000
     63   // Row 2: [HD attempting icon]/[HD icon] 00:15
     64   // Row 2: Call ended
     65   // Row 2: Hanging up
     66   // Row 2: [Alert sign] Suspected spam caller
     67   // Row 2: Your emergency callback number: +1 (650) 253-0000
     68   private final ImageView workIconImageView;
     69   private final ImageView hdIconImageView;
     70   private final ImageView forwardIconImageView;
     71   private final TextView forwardedNumberView;
     72   private final ImageView spamIconImageView;
     73   private final ViewAnimator bottomTextSwitcher;
     74   private final TextView bottomTextView;
     75   private final Chronometer bottomTimerView;
     76   private int avatarSize;
     77   private boolean hideAvatar;
     78   private boolean showAnonymousAvatar;
     79   private boolean middleRowVisible = true;
     80   private boolean isTimerStarted;
     81 
     82   private PrimaryInfo primaryInfo = PrimaryInfo.createEmptyPrimaryInfo();
     83   private PrimaryCallState primaryCallState = PrimaryCallState.createEmptyPrimaryCallState();
     84   private final LetterTileDrawable letterTile;
     85 
     86   public ContactGridManager(
     87       View view, @Nullable ImageView avatarImageView, int avatarSize, boolean showAnonymousAvatar) {
     88     context = view.getContext();
     89     Assert.isNotNull(context);
     90 
     91     this.avatarImageView = avatarImageView;
     92     this.avatarSize = avatarSize;
     93     this.showAnonymousAvatar = showAnonymousAvatar;
     94     connectionIconImageView = view.findViewById(R.id.contactgrid_connection_icon);
     95     statusTextView = view.findViewById(R.id.contactgrid_status_text);
     96     contactNameTextView = view.findViewById(R.id.contactgrid_contact_name);
     97     workIconImageView = view.findViewById(R.id.contactgrid_workIcon);
     98     hdIconImageView = view.findViewById(R.id.contactgrid_hdIcon);
     99     forwardIconImageView = view.findViewById(R.id.contactgrid_forwardIcon);
    100     forwardedNumberView = view.findViewById(R.id.contactgrid_forwardNumber);
    101     spamIconImageView = view.findViewById(R.id.contactgrid_spamIcon);
    102     bottomTextSwitcher = view.findViewById(R.id.contactgrid_bottom_text_switcher);
    103     bottomTextView = view.findViewById(R.id.contactgrid_bottom_text);
    104     bottomTimerView = view.findViewById(R.id.contactgrid_bottom_timer);
    105 
    106     contactGridLayout = (View) contactNameTextView.getParent();
    107     letterTile = new LetterTileDrawable(context.getResources());
    108     isTimerStarted = false;
    109   }
    110 
    111   public void show() {
    112     contactGridLayout.setVisibility(View.VISIBLE);
    113   }
    114 
    115   public void hide() {
    116     contactGridLayout.setVisibility(View.GONE);
    117   }
    118 
    119   public void setAvatarHidden(boolean hide) {
    120     if (hide != hideAvatar) {
    121       hideAvatar = hide;
    122       updatePrimaryNameAndPhoto();
    123     }
    124   }
    125 
    126   public boolean isAvatarHidden() {
    127     return hideAvatar;
    128   }
    129 
    130   public View getContainerView() {
    131     return contactGridLayout;
    132   }
    133 
    134   public void setIsMiddleRowVisible(boolean isMiddleRowVisible) {
    135     if (middleRowVisible == isMiddleRowVisible) {
    136       return;
    137     }
    138     middleRowVisible = isMiddleRowVisible;
    139 
    140     contactNameTextView.setVisibility(isMiddleRowVisible ? View.VISIBLE : View.GONE);
    141     updateAvatarVisibility();
    142   }
    143 
    144   public void setPrimary(PrimaryInfo primaryInfo) {
    145     this.primaryInfo = primaryInfo;
    146     updatePrimaryNameAndPhoto();
    147     updateBottomRow();
    148   }
    149 
    150   public void setCallState(PrimaryCallState primaryCallState) {
    151     this.primaryCallState = primaryCallState;
    152     updatePrimaryNameAndPhoto();
    153     updateBottomRow();
    154     updateTopRow();
    155   }
    156 
    157   public void dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
    158     dispatchPopulateAccessibilityEvent(event, statusTextView);
    159     dispatchPopulateAccessibilityEvent(event, contactNameTextView);
    160     BottomRow.Info info = BottomRow.getInfo(context, primaryCallState, primaryInfo);
    161     if (info.shouldPopulateAccessibilityEvent) {
    162       dispatchPopulateAccessibilityEvent(event, bottomTextView);
    163     }
    164   }
    165 
    166   public void setAvatarImageView(
    167       @Nullable ImageView avatarImageView, int avatarSize, boolean showAnonymousAvatar) {
    168     this.avatarImageView = avatarImageView;
    169     this.avatarSize = avatarSize;
    170     this.showAnonymousAvatar = showAnonymousAvatar;
    171     updatePrimaryNameAndPhoto();
    172   }
    173 
    174   private void dispatchPopulateAccessibilityEvent(AccessibilityEvent event, View view) {
    175     final List<CharSequence> eventText = event.getText();
    176     int size = eventText.size();
    177     view.dispatchPopulateAccessibilityEvent(event);
    178     // If no text added write null to keep relative position.
    179     if (size == eventText.size()) {
    180       eventText.add(null);
    181     }
    182   }
    183 
    184   private boolean updateAvatarVisibility() {
    185     if (avatarImageView == null) {
    186       return false;
    187     }
    188 
    189     if (!middleRowVisible) {
    190       avatarImageView.setVisibility(View.GONE);
    191       return false;
    192     }
    193 
    194     boolean hasPhoto =
    195         primaryInfo.photo != null && primaryInfo.photoType == ContactPhotoType.CONTACT;
    196     if (!hasPhoto && !showAnonymousAvatar) {
    197       avatarImageView.setVisibility(View.GONE);
    198       return false;
    199     }
    200 
    201     avatarImageView.setVisibility(View.VISIBLE);
    202     return true;
    203   }
    204 
    205   /**
    206    * Updates row 0. For example:
    207    *
    208    * <ul>
    209    *   <li>Captain Holt ON HOLD
    210    *   <li>Calling...
    211    *   <li>[Wi-Fi icon] Calling via Starbucks Wi-Fi
    212    *   <li>[Wi-Fi icon] Starbucks Wi-Fi
    213    *   <li>Call from
    214    * </ul>
    215    */
    216   private void updateTopRow() {
    217     TopRow.Info info = TopRow.getInfo(context, primaryCallState, primaryInfo);
    218     if (TextUtils.isEmpty(info.label)) {
    219       // Use INVISIBLE here to prevent the rows below this one from moving up and down.
    220       statusTextView.setVisibility(View.INVISIBLE);
    221       statusTextView.setText(null);
    222     } else {
    223       statusTextView.setText(info.label);
    224       statusTextView.setVisibility(View.VISIBLE);
    225       statusTextView.setSingleLine(info.labelIsSingleLine);
    226     }
    227 
    228     if (info.icon == null) {
    229       connectionIconImageView.setVisibility(View.GONE);
    230     } else {
    231       connectionIconImageView.setVisibility(View.VISIBLE);
    232       connectionIconImageView.setImageDrawable(info.icon);
    233     }
    234   }
    235 
    236   /**
    237    * Updates row 1. For example:
    238    *
    239    * <ul>
    240    *   <li>Jake Peralta [Contact photo]
    241    *   <li>Walgreens
    242    *   <li>+1 (650) 253-0000
    243    * </ul>
    244    */
    245   private void updatePrimaryNameAndPhoto() {
    246     if (TextUtils.isEmpty(primaryInfo.name)) {
    247       contactNameTextView.setText(null);
    248     } else {
    249       contactNameTextView.setText(
    250           primaryInfo.nameIsNumber
    251               ? PhoneNumberUtilsCompat.createTtsSpannable(primaryInfo.name)
    252               : primaryInfo.name);
    253 
    254       // Set direction of the name field
    255       int nameDirection = View.TEXT_DIRECTION_INHERIT;
    256       if (primaryInfo.nameIsNumber) {
    257         nameDirection = View.TEXT_DIRECTION_LTR;
    258       }
    259       contactNameTextView.setTextDirection(nameDirection);
    260     }
    261 
    262     if (avatarImageView != null) {
    263       if (hideAvatar) {
    264         avatarImageView.setVisibility(View.GONE);
    265       } else if (avatarSize > 0 && updateAvatarVisibility()) {
    266         boolean hasPhoto =
    267             primaryInfo.photo != null && primaryInfo.photoType == ContactPhotoType.CONTACT;
    268         // Contact has a photo, don't render a letter tile.
    269         if (hasPhoto) {
    270           avatarImageView.setBackground(
    271               DrawableConverter.getRoundedDrawable(
    272                   context, primaryInfo.photo, avatarSize, avatarSize));
    273           // Contact has a name, that isn't a number.
    274         } else {
    275           letterTile.setCanonicalDialerLetterTileDetails(
    276               primaryInfo.name,
    277               primaryInfo.contactInfoLookupKey,
    278               LetterTileDrawable.SHAPE_CIRCLE,
    279               LetterTileDrawable.getContactTypeFromPrimitives(
    280                   primaryCallState.isVoiceMailNumber,
    281                   primaryInfo.isSpam,
    282                   primaryCallState.isBusinessNumber,
    283                   primaryInfo.numberPresentation,
    284                   primaryCallState.isConference));
    285           // By invalidating the avatarImageView we force a redraw of the letter tile.
    286           // This is required to properly display the updated letter tile iconography based on the
    287           // contact type, because the background drawable reference cached in the view, and the
    288           // view is not aware of the mutations made to the background.
    289           avatarImageView.invalidate();
    290           avatarImageView.setBackground(letterTile);
    291         }
    292       }
    293     }
    294   }
    295 
    296   /**
    297    * Updates row 2. For example:
    298    *
    299    * <ul>
    300    *   <li>Mobile +1 (650) 253-0000
    301    *   <li>[HD attempting icon]/[HD icon] 00:15
    302    *   <li>Call ended
    303    *   <li>Hanging up
    304    * </ul>
    305    */
    306   private void updateBottomRow() {
    307     BottomRow.Info info = BottomRow.getInfo(context, primaryCallState, primaryInfo);
    308 
    309     bottomTextView.setText(info.label);
    310     bottomTextView.setAllCaps(info.isSpamIconVisible);
    311     workIconImageView.setVisibility(info.isWorkIconVisible ? View.VISIBLE : View.GONE);
    312     if (hdIconImageView.getVisibility() == View.GONE) {
    313       if (info.isHdAttemptingIconVisible) {
    314         hdIconImageView.setVisibility(View.VISIBLE);
    315         hdIconImageView.setActivated(false);
    316         Drawable drawableCurrent = hdIconImageView.getDrawable().getCurrent();
    317         if (drawableCurrent instanceof Animatable && !((Animatable) drawableCurrent).isRunning()) {
    318           ((Animatable) drawableCurrent).start();
    319         }
    320       } else if (info.isHdIconVisible) {
    321         hdIconImageView.setVisibility(View.VISIBLE);
    322         hdIconImageView.setActivated(true);
    323       }
    324     } else if (info.isHdIconVisible) {
    325       hdIconImageView.setActivated(true);
    326     } else if (!info.isHdAttemptingIconVisible) {
    327       hdIconImageView.setVisibility(View.GONE);
    328     }
    329     spamIconImageView.setVisibility(info.isSpamIconVisible ? View.VISIBLE : View.GONE);
    330 
    331     if (info.isForwardIconVisible) {
    332       forwardIconImageView.setVisibility(View.VISIBLE);
    333       forwardedNumberView.setVisibility(View.VISIBLE);
    334       if (info.isTimerVisible) {
    335         bottomTextSwitcher.setVisibility(View.VISIBLE);
    336         if (ViewCompat.getLayoutDirection(contactGridLayout) == ViewCompat.LAYOUT_DIRECTION_LTR) {
    337           forwardedNumberView.setText(TextUtils.concat(info.label, "  "));
    338         } else {
    339           forwardedNumberView.setText(TextUtils.concat("  ", info.label));
    340         }
    341       } else {
    342         bottomTextSwitcher.setVisibility(View.GONE);
    343         forwardedNumberView.setText(info.label);
    344       }
    345     } else {
    346       forwardIconImageView.setVisibility(View.GONE);
    347       forwardedNumberView.setVisibility(View.GONE);
    348     }
    349 
    350     if (info.isTimerVisible) {
    351       bottomTextSwitcher.setDisplayedChild(1);
    352       bottomTimerView.setBase(
    353           primaryCallState.connectTimeMillis
    354               - System.currentTimeMillis()
    355               + SystemClock.elapsedRealtime());
    356       if (!isTimerStarted) {
    357         LogUtil.i(
    358             "ContactGridManager.updateBottomRow",
    359             "starting timer with base: %d",
    360             bottomTimerView.getBase());
    361         bottomTimerView.start();
    362         isTimerStarted = true;
    363       }
    364     } else {
    365       bottomTextSwitcher.setDisplayedChild(0);
    366       bottomTimerView.stop();
    367       isTimerStarted = false;
    368     }
    369   }
    370 }
    371