• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
ContactGridManager( View view, @Nullable ImageView avatarImageView, int avatarSize, boolean showAnonymousAvatar)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 
show()111   public void show() {
112     contactGridLayout.setVisibility(View.VISIBLE);
113   }
114 
hide()115   public void hide() {
116     contactGridLayout.setVisibility(View.GONE);
117   }
118 
setAvatarHidden(boolean hide)119   public void setAvatarHidden(boolean hide) {
120     if (hide != hideAvatar) {
121       hideAvatar = hide;
122       updatePrimaryNameAndPhoto();
123     }
124   }
125 
isAvatarHidden()126   public boolean isAvatarHidden() {
127     return hideAvatar;
128   }
129 
getContainerView()130   public View getContainerView() {
131     return contactGridLayout;
132   }
133 
setIsMiddleRowVisible(boolean isMiddleRowVisible)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 
setPrimary(PrimaryInfo primaryInfo)144   public void setPrimary(PrimaryInfo primaryInfo) {
145     this.primaryInfo = primaryInfo;
146     updatePrimaryNameAndPhoto();
147     updateBottomRow();
148   }
149 
setCallState(PrimaryCallState primaryCallState)150   public void setCallState(PrimaryCallState primaryCallState) {
151     this.primaryCallState = primaryCallState;
152     updatePrimaryNameAndPhoto();
153     updateBottomRow();
154     updateTopRow();
155   }
156 
dispatchPopulateAccessibilityEvent(AccessibilityEvent event)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 
setAvatarImageView( @ullable ImageView avatarImageView, int avatarSize, boolean showAnonymousAvatar)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 
dispatchPopulateAccessibilityEvent(AccessibilityEvent event, View view)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 
updateAvatarVisibility()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    */
updateTopRow()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    */
updatePrimaryNameAndPhoto()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    */
updateBottomRow()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