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