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