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.Drawable; 21 import android.support.annotation.Nullable; 22 import android.telephony.PhoneNumberUtils; 23 import android.text.BidiFormatter; 24 import android.text.Spannable; 25 import android.text.SpannableString; 26 import android.text.Spanned; 27 import android.text.TextDirectionHeuristics; 28 import android.text.TextUtils; 29 import android.text.style.ForegroundColorSpan; 30 import com.android.dialer.common.Assert; 31 import com.android.dialer.common.LogUtil; 32 import com.android.incallui.call.state.DialerCallState; 33 import com.android.incallui.incall.protocol.PrimaryCallState; 34 import com.android.incallui.incall.protocol.PrimaryInfo; 35 import com.android.incallui.videotech.utils.SessionModificationState; 36 import com.android.incallui.videotech.utils.VideoUtils; 37 38 /** 39 * Gets the content of the top row. For example: 40 * 41 * <ul> 42 * <li>Captain Holt ON HOLD 43 * <li>Calling... 44 * <li>[Wi-Fi icon] Calling via Starbucks Wi-Fi 45 * <li>[Wi-Fi icon] Starbucks Wi-Fi 46 * <li>Call from 47 * </ul> 48 */ 49 public class TopRow { 50 51 /** Content of the top row. */ 52 public static class Info { 53 54 @Nullable public final CharSequence label; 55 @Nullable public final Drawable icon; 56 public final boolean labelIsSingleLine; 57 Info(@ullable CharSequence label, @Nullable Drawable icon, boolean labelIsSingleLine)58 public Info(@Nullable CharSequence label, @Nullable Drawable icon, boolean labelIsSingleLine) { 59 this.label = label; 60 this.icon = icon; 61 this.labelIsSingleLine = labelIsSingleLine; 62 } 63 } 64 TopRow()65 private TopRow() {} 66 getInfo(Context context, PrimaryCallState state, PrimaryInfo primaryInfo)67 public static Info getInfo(Context context, PrimaryCallState state, PrimaryInfo primaryInfo) { 68 CharSequence label = null; 69 Drawable icon = state.connectionIcon(); 70 boolean labelIsSingleLine = true; 71 72 if (state.isWifi() && icon == null) { 73 icon = context.getDrawable(R.drawable.quantum_ic_network_wifi_vd_theme_24); 74 } 75 76 if (state.state() == DialerCallState.INCOMING 77 || state.state() == DialerCallState.CALL_WAITING) { 78 // Call from 79 // [Wi-Fi icon] Video call from 80 // Hey Jake, pick up! 81 if (!TextUtils.isEmpty(state.callSubject())) { 82 label = state.callSubject(); 83 labelIsSingleLine = false; 84 } else { 85 label = getLabelForIncoming(context, state); 86 // Show phone number if it's not displayed in name (center row) or location field (bottom 87 // row). 88 if (shouldShowNumber(primaryInfo, true /* isIncoming */)) { 89 label = TextUtils.concat(label, " ", spanDisplayNumber(primaryInfo.number())); 90 } 91 } 92 } else if (VideoUtils.hasSentVideoUpgradeRequest(state.sessionModificationState()) 93 || VideoUtils.hasReceivedVideoUpgradeRequest(state.sessionModificationState())) { 94 label = getLabelForVideoRequest(context, state); 95 } else if (state.sessionModificationState() == SessionModificationState.REQUEST_FAILED) { 96 label = context.getString(R.string.incall_video_call_operation_failed); 97 } else if (state.state() == DialerCallState.PULLING) { 98 label = context.getString(R.string.incall_transferring); 99 } else if (state.state() == DialerCallState.DIALING 100 || state.state() == DialerCallState.CONNECTING) { 101 // [Wi-Fi icon] Calling via Google Guest 102 // Calling... 103 label = getLabelForDialing(context, state); 104 } else if (state.state() == DialerCallState.ACTIVE && state.isRemotelyHeld()) { 105 label = context.getString(R.string.incall_remotely_held); 106 } else if (state.state() == DialerCallState.ACTIVE 107 && shouldShowNumber(primaryInfo, false /* isIncoming */)) { 108 label = spanDisplayNumber(primaryInfo.number()); 109 } else if (state.state() == DialerCallState.CALL_PENDING 110 && !TextUtils.isEmpty(state.customLabel())) { 111 label = state.customLabel(); 112 } else { 113 // Video calling... 114 // [Wi-Fi icon] Starbucks Wi-Fi 115 label = getConnectionLabel(state); 116 } 117 118 return new Info(label, icon, labelIsSingleLine); 119 } 120 spanDisplayNumber(String displayNumber)121 private static CharSequence spanDisplayNumber(String displayNumber) { 122 return PhoneNumberUtils.createTtsSpannable( 123 BidiFormatter.getInstance().unicodeWrap(displayNumber, TextDirectionHeuristics.LTR)); 124 } 125 shouldShowNumber(PrimaryInfo primaryInfo, boolean isIncoming)126 private static boolean shouldShowNumber(PrimaryInfo primaryInfo, boolean isIncoming) { 127 if (primaryInfo.nameIsNumber()) { 128 return false; 129 } 130 // Don't show number since it's already shown in bottom row of incoming screen if there is no 131 // location info. 132 if (primaryInfo.location() == null && isIncoming) { 133 return false; 134 } 135 if (primaryInfo.isLocalContact() && !isIncoming) { 136 return false; 137 } 138 if (TextUtils.isEmpty(primaryInfo.number())) { 139 return false; 140 } 141 return true; 142 } 143 getLabelForIncoming(Context context, PrimaryCallState state)144 private static CharSequence getLabelForIncoming(Context context, PrimaryCallState state) { 145 if (state.isVideoCall()) { 146 return getLabelForIncomingVideo(context, state.sessionModificationState(), state.isWifi()); 147 } else if (state.isWifi() && !TextUtils.isEmpty(state.connectionLabel())) { 148 return state.connectionLabel(); 149 } else if (isAccount(state)) { 150 return getColoredConnectionLabel(context, state); 151 } else if (state.isWorkCall()) { 152 return context.getString(R.string.contact_grid_incoming_work_call); 153 } else { 154 return context.getString(R.string.contact_grid_incoming_voice_call); 155 } 156 } 157 getColoredConnectionLabel(Context context, PrimaryCallState state)158 private static Spannable getColoredConnectionLabel(Context context, PrimaryCallState state) { 159 Assert.isNotNull(state.connectionLabel()); 160 String label = 161 context.getString(R.string.contact_grid_incoming_via_template, state.connectionLabel()); 162 Spannable spannable = new SpannableString(label); 163 164 int start = label.indexOf(state.connectionLabel()); 165 int end = start + state.connectionLabel().length(); 166 spannable.setSpan( 167 new ForegroundColorSpan(state.primaryColor()), 168 start, 169 end, 170 Spanned.SPAN_INCLUSIVE_EXCLUSIVE); 171 return spannable; 172 } 173 getLabelForIncomingVideo( Context context, @SessionModificationState int sessionModificationState, boolean isWifi)174 private static CharSequence getLabelForIncomingVideo( 175 Context context, @SessionModificationState int sessionModificationState, boolean isWifi) { 176 if (sessionModificationState == SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST) { 177 if (isWifi) { 178 return context.getString(R.string.contact_grid_incoming_wifi_video_call); 179 } else { 180 return context.getString(R.string.contact_grid_incoming_video_call); 181 } 182 } else { 183 if (isWifi) { 184 return context.getString(R.string.contact_grid_incoming_wifi_video_call); 185 } else { 186 return context.getString(R.string.contact_grid_incoming_video_call); 187 } 188 } 189 } 190 getLabelForDialing(Context context, PrimaryCallState state)191 private static CharSequence getLabelForDialing(Context context, PrimaryCallState state) { 192 if (!TextUtils.isEmpty(state.connectionLabel()) && !state.isWifi()) { 193 CharSequence label = getCallingViaLabel(context, state); 194 195 if (state.isAssistedDialed() && state.assistedDialingExtras() != null) { 196 LogUtil.i("TopRow.getLabelForDialing", "using assisted dialing with via label."); 197 String countryCode = 198 String.valueOf(state.assistedDialingExtras().transformedNumberCountryCallingCode()); 199 label = 200 TextUtils.concat( 201 label, 202 " • ", 203 context.getString( 204 R.string.incall_connecting_assited_dialed_component, 205 countryCode, 206 state.assistedDialingExtras().userHomeCountryCode())); 207 } 208 return label; 209 } else { 210 if (state.isVideoCall()) { 211 if (state.isWifi()) { 212 return context.getString(R.string.incall_wifi_video_call_requesting); 213 } else { 214 return context.getString(R.string.incall_video_call_requesting); 215 } 216 } 217 218 if (state.isAssistedDialed() && state.assistedDialingExtras() != null) { 219 LogUtil.i("TopRow.getLabelForDialing", "using assisted dialing label."); 220 String countryCode = 221 String.valueOf(state.assistedDialingExtras().transformedNumberCountryCallingCode()); 222 return context.getString( 223 R.string.incall_connecting_assited_dialed, 224 countryCode, 225 state.assistedDialingExtras().userHomeCountryCode()); 226 } 227 return context.getString(R.string.incall_connecting); 228 } 229 } 230 getCallingViaLabel(Context context, PrimaryCallState state)231 private static CharSequence getCallingViaLabel(Context context, PrimaryCallState state) { 232 if (state.simSuggestionReason() != null) { 233 switch (state.simSuggestionReason()) { 234 case FREQUENT: 235 return context.getString( 236 R.string.incall_calling_on_recent_choice_template, state.connectionLabel()); 237 case INTRA_CARRIER: 238 return context.getString( 239 R.string.incall_calling_on_same_carrier_template, state.connectionLabel()); 240 default: 241 break; 242 } 243 } 244 return context.getString(R.string.incall_calling_via_template, state.connectionLabel()); 245 } 246 getConnectionLabel(PrimaryCallState state)247 private static CharSequence getConnectionLabel(PrimaryCallState state) { 248 if (!TextUtils.isEmpty(state.connectionLabel()) 249 && (isAccount(state) || state.isWifi() || state.isConference())) { 250 // We normally don't show a "call state label" at all when active 251 // (but we can use the call state label to display the provider name). 252 return state.connectionLabel(); 253 } else { 254 return null; 255 } 256 } 257 getLabelForVideoRequest(Context context, PrimaryCallState state)258 private static CharSequence getLabelForVideoRequest(Context context, PrimaryCallState state) { 259 switch (state.sessionModificationState()) { 260 case SessionModificationState.WAITING_FOR_UPGRADE_TO_VIDEO_RESPONSE: 261 return context.getString(R.string.incall_video_call_upgrade_request); 262 case SessionModificationState.REQUEST_FAILED: 263 case SessionModificationState.UPGRADE_TO_VIDEO_REQUEST_FAILED: 264 return context.getString(R.string.incall_video_call_request_failed); 265 case SessionModificationState.REQUEST_REJECTED: 266 return context.getString(R.string.incall_video_call_request_rejected); 267 case SessionModificationState.UPGRADE_TO_VIDEO_REQUEST_TIMED_OUT: 268 return context.getString(R.string.incall_video_call_request_timed_out); 269 case SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST: 270 return getLabelForIncomingVideo(context, state.sessionModificationState(), state.isWifi()); 271 case SessionModificationState.NO_REQUEST: 272 default: 273 Assert.fail(); 274 return null; 275 } 276 } 277 isAccount(PrimaryCallState state)278 private static boolean isAccount(PrimaryCallState state) { 279 return !TextUtils.isEmpty(state.connectionLabel()) && TextUtils.isEmpty(state.gatewayNumber()); 280 } 281 } 282