blob: e5908f12e9a332307aec2e682c3d85e411b7f401 [file] [log] [blame]
Eric Erfanianccca3152017-02-22 16:32:36 -08001/*
2 * Copyright (C) 2013 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
17package com.android.incallui;
18
Eric Erfanian90508232017-03-24 09:31:16 -070019import static android.telecom.Call.Details.PROPERTY_HIGH_DEF_AUDIO;
Eric Erfanianccca3152017-02-22 16:32:36 -080020import static com.android.contacts.common.compat.CallCompat.Details.PROPERTY_ENTERPRISE_CALL;
21import static com.android.incallui.NotificationBroadcastReceiver.ACTION_ACCEPT_VIDEO_UPGRADE_REQUEST;
22import static com.android.incallui.NotificationBroadcastReceiver.ACTION_ANSWER_VIDEO_INCOMING_CALL;
23import static com.android.incallui.NotificationBroadcastReceiver.ACTION_ANSWER_VOICE_INCOMING_CALL;
24import static com.android.incallui.NotificationBroadcastReceiver.ACTION_DECLINE_INCOMING_CALL;
25import static com.android.incallui.NotificationBroadcastReceiver.ACTION_DECLINE_VIDEO_UPGRADE_REQUEST;
26import static com.android.incallui.NotificationBroadcastReceiver.ACTION_HANG_UP_ONGOING_CALL;
yuegb26c1ae2017-09-18 16:59:16 -070027import static com.android.incallui.NotificationBroadcastReceiver.ACTION_TURN_OFF_SPEAKER;
28import static com.android.incallui.NotificationBroadcastReceiver.ACTION_TURN_ON_SPEAKER;
Eric Erfanianccca3152017-02-22 16:32:36 -080029
Eric Erfaniand5e47f62017-03-15 14:41:07 -070030import android.Manifest;
Eric Erfanianccca3152017-02-22 16:32:36 -080031import android.app.Notification;
Eric Erfanianccca3152017-02-22 16:32:36 -080032import android.app.PendingIntent;
33import android.content.Context;
34import android.content.Intent;
Eric Erfanian83b20212017-05-31 08:53:10 -070035import android.content.res.Resources;
Eric Erfanianccca3152017-02-22 16:32:36 -080036import android.graphics.Bitmap;
Eric Erfanianccca3152017-02-22 16:32:36 -080037import android.graphics.drawable.BitmapDrawable;
38import android.graphics.drawable.Drawable;
Eric Erfaniand5e47f62017-03-15 14:41:07 -070039import android.graphics.drawable.Icon;
Eric Erfanianccca3152017-02-22 16:32:36 -080040import android.media.AudioAttributes;
41import android.net.Uri;
42import android.os.Build.VERSION;
43import android.os.Build.VERSION_CODES;
wangqicf61ca02017-08-31 15:32:55 -070044import android.os.Trace;
Eric Erfanianccca3152017-02-22 16:32:36 -080045import android.support.annotation.ColorRes;
46import android.support.annotation.NonNull;
47import android.support.annotation.Nullable;
Eric Erfaniand5e47f62017-03-15 14:41:07 -070048import android.support.annotation.RequiresPermission;
Eric Erfanianccca3152017-02-22 16:32:36 -080049import android.support.annotation.StringRes;
50import android.support.annotation.VisibleForTesting;
Eric Erfaniand5e47f62017-03-15 14:41:07 -070051import android.support.v4.os.BuildCompat;
Eric Erfanianccca3152017-02-22 16:32:36 -080052import android.telecom.Call.Details;
yuegb26c1ae2017-09-18 16:59:16 -070053import android.telecom.CallAudioState;
Eric Erfanianccca3152017-02-22 16:32:36 -080054import android.telecom.PhoneAccount;
Eric Erfaniand5e47f62017-03-15 14:41:07 -070055import android.telecom.PhoneAccountHandle;
Eric Erfanianccca3152017-02-22 16:32:36 -080056import android.telecom.TelecomManager;
57import android.text.BidiFormatter;
58import android.text.Spannable;
59import android.text.SpannableString;
60import android.text.TextDirectionHeuristics;
61import android.text.TextUtils;
62import android.text.style.ForegroundColorSpan;
63import com.android.contacts.common.ContactsUtils;
64import com.android.contacts.common.ContactsUtils.UserType;
65import com.android.contacts.common.preference.ContactsPreferences;
Eric Erfanianccca3152017-02-22 16:32:36 -080066import com.android.contacts.common.util.ContactDisplayUtils;
Eric Erfanian2ca43182017-08-31 06:57:16 -070067import com.android.dialer.common.Assert;
Eric Erfanianccca3152017-02-22 16:32:36 -080068import com.android.dialer.common.LogUtil;
Eric Erfanian2ca43182017-08-31 06:57:16 -070069import com.android.dialer.configprovider.ConfigProviderBindings;
70import com.android.dialer.contactphoto.BitmapUtil;
Eric Erfaniand8046e52017-04-06 09:41:50 -070071import com.android.dialer.enrichedcall.EnrichedCallManager;
72import com.android.dialer.enrichedcall.Session;
Eric Erfanian2ca43182017-08-31 06:57:16 -070073import com.android.dialer.lettertile.LetterTileDrawable;
74import com.android.dialer.lettertile.LetterTileDrawable.ContactType;
Eric Erfaniand8046e52017-04-06 09:41:50 -070075import com.android.dialer.multimedia.MultimediaData;
Eric Erfanian2ca43182017-08-31 06:57:16 -070076import com.android.dialer.notification.NotificationChannelId;
Eric Erfanian90508232017-03-24 09:31:16 -070077import com.android.dialer.oem.MotorolaUtils;
Eric Erfanianccca3152017-02-22 16:32:36 -080078import com.android.dialer.util.DrawableConverter;
79import com.android.incallui.ContactInfoCache.ContactCacheEntry;
80import com.android.incallui.ContactInfoCache.ContactInfoCacheCallback;
81import com.android.incallui.InCallPresenter.InCallState;
82import com.android.incallui.async.PausableExecutorImpl;
yuegb26c1ae2017-09-18 16:59:16 -070083import com.android.incallui.audiomode.AudioModeProvider;
Eric Erfanianccca3152017-02-22 16:32:36 -080084import com.android.incallui.call.CallList;
85import com.android.incallui.call.DialerCall;
Eric Erfanianccca3152017-02-22 16:32:36 -080086import com.android.incallui.call.DialerCallListener;
yueg01a964d2017-10-03 15:25:41 -070087import com.android.incallui.call.TelecomAdapter;
Eric Erfanianccca3152017-02-22 16:32:36 -080088import com.android.incallui.ringtone.DialerRingtoneManager;
89import com.android.incallui.ringtone.InCallTonePlayer;
90import com.android.incallui.ringtone.ToneGeneratorFactory;
Eric Erfanian90508232017-03-24 09:31:16 -070091import com.android.incallui.videotech.utils.SessionModificationState;
Eric Erfaniand5e47f62017-03-15 14:41:07 -070092import java.util.List;
Eric Erfanianccca3152017-02-22 16:32:36 -080093import java.util.Objects;
94
95/** This class adds Notifications to the status bar for the in-call experience. */
Eric Erfaniand8046e52017-04-06 09:41:50 -070096public class StatusBarNotifier
yuegb26c1ae2017-09-18 16:59:16 -070097 implements InCallPresenter.InCallStateListener,
98 EnrichedCallManager.StateChangedListener,
99 AudioModeProvider.AudioModeListener {
Eric Erfanianccca3152017-02-22 16:32:36 -0800100
Eric Erfanian2ca43182017-08-31 06:57:16 -0700101 private static final int NOTIFICATION_ID = 1;
102
Eric Erfanianccca3152017-02-22 16:32:36 -0800103 // Notification types
104 // Indicates that no notification is currently showing.
105 private static final int NOTIFICATION_NONE = 0;
106 // Notification for an active call. This is non-interruptive, but cannot be dismissed.
Eric Erfanian10b34a52017-05-04 08:23:17 -0700107 private static final int NOTIFICATION_IN_CALL = 1;
Eric Erfanianccca3152017-02-22 16:32:36 -0800108 // Notification for incoming calls. This is interruptive and will show up as a HUN.
Eric Erfanian10b34a52017-05-04 08:23:17 -0700109 private static final int NOTIFICATION_INCOMING_CALL = 2;
110 // Notification for incoming calls in the case where there is already an active call.
111 // This is non-interruptive, but otherwise behaves the same as NOTIFICATION_INCOMING_CALL
112 private static final int NOTIFICATION_INCOMING_CALL_QUIET = 3;
Eric Erfanianccca3152017-02-22 16:32:36 -0800113
Eric Erfanianccca3152017-02-22 16:32:36 -0800114 private static final long[] VIBRATE_PATTERN = new long[] {0, 1000, 1000};
115
116 private final Context mContext;
117 private final ContactInfoCache mContactInfoCache;
Eric Erfanianccca3152017-02-22 16:32:36 -0800118 private final DialerRingtoneManager mDialerRingtoneManager;
119 @Nullable private ContactsPreferences mContactsPreferences;
120 private int mCurrentNotification = NOTIFICATION_NONE;
121 private int mCallState = DialerCall.State.INVALID;
122 private int mSavedIcon = 0;
123 private String mSavedContent = null;
124 private Bitmap mSavedLargeIcon;
125 private String mSavedContentTitle;
yuegb26c1ae2017-09-18 16:59:16 -0700126 private CallAudioState savedCallAudioState;
Eric Erfanianccca3152017-02-22 16:32:36 -0800127 private Uri mRingtone;
128 private StatusBarCallListener mStatusBarCallListener;
129
Eric Erfaniand8046e52017-04-06 09:41:50 -0700130 public StatusBarNotifier(@NonNull Context context, @NonNull ContactInfoCache contactInfoCache) {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700131 mContext = Assert.isNotNull(context);
Eric Erfanianccca3152017-02-22 16:32:36 -0800132 mContactsPreferences = ContactsPreferencesFactory.newContactsPreferences(mContext);
133 mContactInfoCache = contactInfoCache;
Eric Erfanianccca3152017-02-22 16:32:36 -0800134 mDialerRingtoneManager =
135 new DialerRingtoneManager(
136 new InCallTonePlayer(new ToneGeneratorFactory(), new PausableExecutorImpl()),
137 CallList.getInstance());
138 mCurrentNotification = NOTIFICATION_NONE;
yuegb26c1ae2017-09-18 16:59:16 -0700139 AudioModeProvider.getInstance().addListener(this);
Eric Erfanianccca3152017-02-22 16:32:36 -0800140 }
141
142 /**
143 * Should only be called from a irrecoverable state where it is necessary to dismiss all
144 * notifications.
145 */
yueg01a964d2017-10-03 15:25:41 -0700146 static void clearAllCallNotifications() {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700147 LogUtil.e(
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700148 "StatusBarNotifier.clearAllCallNotifications",
149 "something terrible happened, clear all InCall notifications");
Eric Erfanianccca3152017-02-22 16:32:36 -0800150
yueg01a964d2017-10-03 15:25:41 -0700151 TelecomAdapter.getInstance().stopForegroundNotification();
Eric Erfanianccca3152017-02-22 16:32:36 -0800152 }
153
154 private static int getWorkStringFromPersonalString(int resId) {
155 if (resId == R.string.notification_ongoing_call) {
156 return R.string.notification_ongoing_work_call;
Eric Erfanianccca3152017-02-22 16:32:36 -0800157 } else if (resId == R.string.notification_incoming_call) {
158 return R.string.notification_incoming_work_call;
159 } else {
160 return resId;
161 }
162 }
163
164 /**
165 * Returns PendingIntent for answering a phone call. This will typically be used from Notification
166 * context.
167 */
168 private static PendingIntent createNotificationPendingIntent(Context context, String action) {
169 final Intent intent = new Intent(action, null, context, NotificationBroadcastReceiver.class);
170 return PendingIntent.getBroadcast(context, 0, intent, 0);
171 }
172
173 /** Creates notifications according to the state we receive from {@link InCallPresenter}. */
174 @Override
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700175 @RequiresPermission(Manifest.permission.READ_PHONE_STATE)
Eric Erfanianccca3152017-02-22 16:32:36 -0800176 public void onStateChange(InCallState oldState, InCallState newState, CallList callList) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700177 LogUtil.d("StatusBarNotifier.onStateChange", "%s->%s", oldState, newState);
Eric Erfanianccca3152017-02-22 16:32:36 -0800178 updateNotification(callList);
179 }
180
Eric Erfaniand8046e52017-04-06 09:41:50 -0700181 @Override
182 public void onEnrichedCallStateChanged() {
183 LogUtil.enterBlock("StatusBarNotifier.onEnrichedCallStateChanged");
184 updateNotification(CallList.getInstance());
185 }
186
Eric Erfanianccca3152017-02-22 16:32:36 -0800187 /**
188 * Updates the phone app's status bar notification *and* launches the incoming call UI in response
189 * to a new incoming call.
190 *
191 * <p>If an incoming call is ringing (or call-waiting), the notification will also include a
192 * "fullScreenIntent" that will cause the InCallScreen to be launched, unless the current
193 * foreground activity is marked as "immersive".
194 *
195 * <p>(This is the mechanism that actually brings up the incoming call UI when we receive a "new
196 * ringing connection" event from the telephony layer.)
197 *
198 * <p>Also note that this method is safe to call even if the phone isn't actually ringing (or,
199 * more likely, if an incoming call *was* ringing briefly but then disconnected). In that case,
200 * we'll simply update or cancel the in-call notification based on the current phone state.
201 *
202 * @see #updateInCallNotification(CallList)
203 */
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700204 @RequiresPermission(Manifest.permission.READ_PHONE_STATE)
Eric Erfaniand8046e52017-04-06 09:41:50 -0700205 public void updateNotification(CallList callList) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800206 updateInCallNotification(callList);
207 }
208
209 /**
210 * Take down the in-call notification.
211 *
212 * @see #updateInCallNotification(CallList)
213 */
214 private void cancelNotification() {
215 if (mStatusBarCallListener != null) {
216 setStatusBarCallListener(null);
217 }
218 if (mCurrentNotification != NOTIFICATION_NONE) {
yueg01a964d2017-10-03 15:25:41 -0700219 TelecomAdapter.getInstance().stopForegroundNotification();
220 mCurrentNotification = NOTIFICATION_NONE;
Eric Erfanianccca3152017-02-22 16:32:36 -0800221 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800222 }
223
224 /**
225 * Helper method for updateInCallNotification() and updateNotification(): Update the phone app's
226 * status bar notification based on the current telephony state, or cancels the notification if
227 * the phone is totally idle.
228 */
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700229 @RequiresPermission(Manifest.permission.READ_PHONE_STATE)
Eric Erfanianccca3152017-02-22 16:32:36 -0800230 private void updateInCallNotification(CallList callList) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700231 LogUtil.d("StatusBarNotifier.updateInCallNotification", "");
Eric Erfanianccca3152017-02-22 16:32:36 -0800232
233 final DialerCall call = getCallToShow(callList);
234
235 if (call != null) {
236 showNotification(callList, call);
237 } else {
238 cancelNotification();
239 }
240 }
241
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700242 @RequiresPermission(Manifest.permission.READ_PHONE_STATE)
Eric Erfanianccca3152017-02-22 16:32:36 -0800243 private void showNotification(final CallList callList, final DialerCall call) {
wangqicf61ca02017-08-31 15:32:55 -0700244 Trace.beginSection("StatusBarNotifier.showNotification");
Eric Erfanianccca3152017-02-22 16:32:36 -0800245 final boolean isIncoming =
246 (call.getState() == DialerCall.State.INCOMING
247 || call.getState() == DialerCall.State.CALL_WAITING);
248 setStatusBarCallListener(new StatusBarCallListener(call));
249
250 // we make a call to the contact info cache to query for supplemental data to what the
251 // call provides. This includes the contact name and photo.
252 // This callback will always get called immediately and synchronously with whatever data
253 // it has available, and may make a subsequent call later (same thread) if it had to
254 // call into the contacts provider for more data.
255 mContactInfoCache.findInfo(
256 call,
257 isIncoming,
258 new ContactInfoCacheCallback() {
259 @Override
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700260 @RequiresPermission(Manifest.permission.READ_PHONE_STATE)
Eric Erfanianccca3152017-02-22 16:32:36 -0800261 public void onContactInfoComplete(String callId, ContactCacheEntry entry) {
262 DialerCall call = callList.getCallById(callId);
263 if (call != null) {
264 call.getLogState().contactLookupResult = entry.contactLookupResult;
265 buildAndSendNotification(callList, call, entry);
266 }
267 }
268
269 @Override
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700270 @RequiresPermission(Manifest.permission.READ_PHONE_STATE)
Eric Erfanianccca3152017-02-22 16:32:36 -0800271 public void onImageLoadComplete(String callId, ContactCacheEntry entry) {
272 DialerCall call = callList.getCallById(callId);
273 if (call != null) {
274 buildAndSendNotification(callList, call, entry);
275 }
276 }
277 });
wangqicf61ca02017-08-31 15:32:55 -0700278 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800279 }
280
281 /** Sets up the main Ui for the notification */
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700282 @RequiresPermission(Manifest.permission.READ_PHONE_STATE)
Eric Erfanianccca3152017-02-22 16:32:36 -0800283 private void buildAndSendNotification(
284 CallList callList, DialerCall originalCall, ContactCacheEntry contactInfo) {
wangqicf61ca02017-08-31 15:32:55 -0700285 Trace.beginSection("StatusBarNotifier.buildAndSendNotification");
Eric Erfanianccca3152017-02-22 16:32:36 -0800286 // This can get called to update an existing notification after contact information has come
287 // back. However, it can happen much later. Before we continue, we need to make sure that
288 // the call being passed in is still the one we want to show in the notification.
289 final DialerCall call = getCallToShow(callList);
290 if (call == null || !call.getId().equals(originalCall.getId())) {
wangqicf61ca02017-08-31 15:32:55 -0700291 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800292 return;
293 }
294
wangqicf61ca02017-08-31 15:32:55 -0700295 Trace.beginSection("prepare work");
Eric Erfanianccca3152017-02-22 16:32:36 -0800296 final int callState = call.getState();
yuegb26c1ae2017-09-18 16:59:16 -0700297 final CallAudioState callAudioState = AudioModeProvider.getInstance().getAudioState();
Eric Erfanianccca3152017-02-22 16:32:36 -0800298
299 // Check if data has changed; if nothing is different, don't issue another notification.
300 final int iconResId = getIconToDisplay(call);
Eric Erfanian83b20212017-05-31 08:53:10 -0700301 Bitmap largeIcon = getLargeIconToDisplay(mContext, contactInfo, call);
twyend1d1d0c2017-10-05 17:34:43 -0700302 final CharSequence content = getContentString(call, contactInfo.userType);
Eric Erfanianccca3152017-02-22 16:32:36 -0800303 final String contentTitle = getContentTitle(contactInfo, call);
304
305 final boolean isVideoUpgradeRequest =
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700306 call.getVideoTech().getSessionModificationState()
Eric Erfanian90508232017-03-24 09:31:16 -0700307 == SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST;
Eric Erfanianccca3152017-02-22 16:32:36 -0800308 final int notificationType;
309 if (callState == DialerCall.State.INCOMING
310 || callState == DialerCall.State.CALL_WAITING
311 || isVideoUpgradeRequest) {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700312 if (ConfigProviderBindings.get(mContext)
313 .getBoolean("quiet_incoming_call_if_ui_showing", true)) {
314 notificationType =
315 InCallPresenter.getInstance().isShowingInCallUi()
316 ? NOTIFICATION_INCOMING_CALL_QUIET
317 : NOTIFICATION_INCOMING_CALL;
318 } else {
319 boolean alreadyActive =
320 callList.getActiveOrBackgroundCall() != null
321 && InCallPresenter.getInstance().isShowingInCallUi();
322 notificationType =
323 alreadyActive ? NOTIFICATION_INCOMING_CALL_QUIET : NOTIFICATION_INCOMING_CALL;
324 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800325 } else {
326 notificationType = NOTIFICATION_IN_CALL;
327 }
wangqicf61ca02017-08-31 15:32:55 -0700328 Trace.endSection(); // prepare work
Eric Erfanianccca3152017-02-22 16:32:36 -0800329
330 if (!checkForChangeAndSaveData(
331 iconResId,
twyend1d1d0c2017-10-05 17:34:43 -0700332 content.toString(),
Eric Erfanianccca3152017-02-22 16:32:36 -0800333 largeIcon,
334 contentTitle,
335 callState,
336 notificationType,
yuegb26c1ae2017-09-18 16:59:16 -0700337 contactInfo.contactRingtoneUri,
338 callAudioState)) {
wangqicf61ca02017-08-31 15:32:55 -0700339 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800340 return;
341 }
342
343 if (largeIcon != null) {
344 largeIcon = getRoundedIcon(largeIcon);
345 }
346
347 // This builder is used for the notification shown when the device is locked and the user
348 // has set their notification settings to 'hide sensitive content'
349 // {@see Notification.Builder#setPublicVersion}.
350 Notification.Builder publicBuilder = new Notification.Builder(mContext);
351 publicBuilder
352 .setSmallIcon(iconResId)
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700353 .setColor(mContext.getResources().getColor(R.color.dialer_theme_color, mContext.getTheme()))
Eric Erfanianccca3152017-02-22 16:32:36 -0800354 // Hide work call state for the lock screen notification
355 .setContentTitle(getContentString(call, ContactsUtils.USER_TYPE_CURRENT));
356 setNotificationWhen(call, callState, publicBuilder);
357
358 // Builder for the notification shown when the device is unlocked or the user has set their
359 // notification settings to 'show all notification content'.
360 final Notification.Builder builder = getNotificationBuilder();
361 builder.setPublicVersion(publicBuilder.build());
362
363 // Set up the main intent to send the user to the in-call screen
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700364 builder.setContentIntent(createLaunchPendingIntent(false /* isFullScreen */));
Eric Erfanianccca3152017-02-22 16:32:36 -0800365
366 // Set the intent as a full screen intent as well if a call is incoming
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700367 PhoneAccountHandle accountHandle = call.getAccountHandle();
368 if (accountHandle == null) {
369 accountHandle = getAnyPhoneAccount();
370 }
Eric Erfanian10b34a52017-05-04 08:23:17 -0700371
372 LogUtil.i("StatusBarNotifier.buildAndSendNotification", "notificationType=" + notificationType);
373 switch (notificationType) {
374 case NOTIFICATION_INCOMING_CALL:
Eric Erfanian2ca43182017-08-31 06:57:16 -0700375 if (BuildCompat.isAtLeastO()) {
376 builder.setChannelId(NotificationChannelId.INCOMING_CALL);
377 }
Eric Erfanian10b34a52017-05-04 08:23:17 -0700378 configureFullScreenIntent(builder, createLaunchPendingIntent(true /* isFullScreen */));
379 // Set the notification category and bump the priority for incoming calls
380 builder.setCategory(Notification.CATEGORY_CALL);
381 // This will be ignored on O+ and handled by the channel
Eric Erfanian10b34a52017-05-04 08:23:17 -0700382 builder.setPriority(Notification.PRIORITY_MAX);
383 if (mCurrentNotification != NOTIFICATION_INCOMING_CALL) {
384 LogUtil.i(
385 "StatusBarNotifier.buildAndSendNotification",
386 "Canceling old notification so this one can be noisy");
387 // Moving from a non-interuptive notification (or none) to a noisy one. Cancel the old
388 // notification (if there is one) so the fullScreenIntent or HUN will show
yueg01a964d2017-10-03 15:25:41 -0700389 TelecomAdapter.getInstance().stopForegroundNotification();
Eric Erfanian10b34a52017-05-04 08:23:17 -0700390 }
391 break;
392 case NOTIFICATION_INCOMING_CALL_QUIET:
Eric Erfanian2ca43182017-08-31 06:57:16 -0700393 if (BuildCompat.isAtLeastO()) {
394 builder.setChannelId(NotificationChannelId.ONGOING_CALL);
395 }
Eric Erfanian10b34a52017-05-04 08:23:17 -0700396 break;
397 case NOTIFICATION_IN_CALL:
Eric Erfanian2ca43182017-08-31 06:57:16 -0700398 if (BuildCompat.isAtLeastO()) {
399 publicBuilder.setColorized(true);
400 builder.setColorized(true);
401 builder.setChannelId(NotificationChannelId.ONGOING_CALL);
402 }
yueg01a964d2017-10-03 15:25:41 -0700403 // This will be ignored on O+ and handled by the channel
404 builder.setPriority(Notification.PRIORITY_MAX);
Eric Erfanian2ca43182017-08-31 06:57:16 -0700405 break;
406 default:
Eric Erfanian10b34a52017-05-04 08:23:17 -0700407 break;
Eric Erfanianccca3152017-02-22 16:32:36 -0800408 }
409
410 // Set the content
411 builder.setContentText(content);
412 builder.setSmallIcon(iconResId);
413 builder.setContentTitle(contentTitle);
414 builder.setLargeIcon(largeIcon);
yueg01a964d2017-10-03 15:25:41 -0700415 builder.setColor(InCallPresenter.getInstance().getThemeColorManager().getPrimaryColor());
Eric Erfanianccca3152017-02-22 16:32:36 -0800416
417 if (isVideoUpgradeRequest) {
418 builder.setUsesChronometer(false);
419 addDismissUpgradeRequestAction(builder);
420 addAcceptUpgradeRequestAction(builder);
421 } else {
yuegb26c1ae2017-09-18 16:59:16 -0700422 createIncomingCallNotification(call, callState, callAudioState, builder);
Eric Erfanianccca3152017-02-22 16:32:36 -0800423 }
424
425 addPersonReference(builder, contactInfo, call);
426
wangqicf61ca02017-08-31 15:32:55 -0700427 Trace.beginSection("fire notification");
Eric Erfanianccca3152017-02-22 16:32:36 -0800428 // Fire off the notification
429 Notification notification = builder.build();
430
431 if (mDialerRingtoneManager.shouldPlayRingtone(callState, contactInfo.contactRingtoneUri)) {
432 notification.flags |= Notification.FLAG_INSISTENT;
433 notification.sound = contactInfo.contactRingtoneUri;
434 AudioAttributes.Builder audioAttributes = new AudioAttributes.Builder();
435 audioAttributes.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC);
436 audioAttributes.setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE);
437 notification.audioAttributes = audioAttributes.build();
438 if (mDialerRingtoneManager.shouldVibrate(mContext.getContentResolver())) {
439 notification.vibrate = VIBRATE_PATTERN;
440 }
441 }
442 if (mDialerRingtoneManager.shouldPlayCallWaitingTone(callState)) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700443 LogUtil.v("StatusBarNotifier.buildAndSendNotification", "playing call waiting tone");
Eric Erfanianccca3152017-02-22 16:32:36 -0800444 mDialerRingtoneManager.playCallWaitingTone();
445 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800446
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700447 LogUtil.i(
448 "StatusBarNotifier.buildAndSendNotification",
449 "displaying notification for " + notificationType);
450
yueg01a964d2017-10-03 15:25:41 -0700451 // If a notification exists, this will only update it.
452 TelecomAdapter.getInstance().startForegroundNotification(NOTIFICATION_ID, notification);
453
wangqicf61ca02017-08-31 15:32:55 -0700454 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800455 call.getLatencyReport().onNotificationShown();
456 mCurrentNotification = notificationType;
wangqicf61ca02017-08-31 15:32:55 -0700457 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800458 }
459
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700460 @Nullable
461 @RequiresPermission(Manifest.permission.READ_PHONE_STATE)
462 private PhoneAccountHandle getAnyPhoneAccount() {
463 PhoneAccountHandle accountHandle;
464 TelecomManager telecomManager = mContext.getSystemService(TelecomManager.class);
465 accountHandle = telecomManager.getDefaultOutgoingPhoneAccount(PhoneAccount.SCHEME_TEL);
466 if (accountHandle == null) {
467 List<PhoneAccountHandle> accountHandles = telecomManager.getCallCapablePhoneAccounts();
468 if (!accountHandles.isEmpty()) {
469 accountHandle = accountHandles.get(0);
470 }
471 }
472 return accountHandle;
473 }
474
Eric Erfanianccca3152017-02-22 16:32:36 -0800475 private void createIncomingCallNotification(
yuegb26c1ae2017-09-18 16:59:16 -0700476 DialerCall call, int state, CallAudioState callAudioState, Notification.Builder builder) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800477 setNotificationWhen(call, state, builder);
478
479 // Add hang up option for any active calls (active | onhold), outgoing calls (dialing).
480 if (state == DialerCall.State.ACTIVE
481 || state == DialerCall.State.ONHOLD
482 || DialerCall.State.isDialing(state)) {
483 addHangupAction(builder);
yuegb26c1ae2017-09-18 16:59:16 -0700484 addSpeakerAction(builder, callAudioState);
Eric Erfanianccca3152017-02-22 16:32:36 -0800485 } else if (state == DialerCall.State.INCOMING || state == DialerCall.State.CALL_WAITING) {
486 addDismissAction(builder);
487 if (call.isVideoCall()) {
488 addVideoCallAction(builder);
489 } else {
490 addAnswerAction(builder);
491 }
492 }
493 }
494
495 /**
496 * Sets the notification's when section as needed. For active calls, this is explicitly set as the
497 * duration of the call. For all other states, the notification will automatically show the time
498 * at which the notification was created.
499 */
500 private void setNotificationWhen(DialerCall call, int state, Notification.Builder builder) {
501 if (state == DialerCall.State.ACTIVE) {
502 builder.setUsesChronometer(true);
503 builder.setWhen(call.getConnectTimeMillis());
504 } else {
505 builder.setUsesChronometer(false);
506 }
507 }
508
509 /**
510 * Checks the new notification data and compares it against any notification that we are already
511 * displaying. If the data is exactly the same, we return false so that we do not issue a new
512 * notification for the exact same data.
513 */
514 private boolean checkForChangeAndSaveData(
515 int icon,
516 String content,
517 Bitmap largeIcon,
518 String contentTitle,
519 int state,
520 int notificationType,
yuegb26c1ae2017-09-18 16:59:16 -0700521 Uri ringtone,
522 CallAudioState callAudioState) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800523
524 // The two are different:
525 // if new title is not null, it should be different from saved version OR
526 // if new title is null, the saved version should not be null
527 final boolean contentTitleChanged =
528 (contentTitle != null && !contentTitle.equals(mSavedContentTitle))
529 || (contentTitle == null && mSavedContentTitle != null);
530
Eric Erfanian8369df02017-05-03 10:27:13 -0700531 boolean largeIconChanged =
532 mSavedLargeIcon == null ? largeIcon != null : !mSavedLargeIcon.sameAs(largeIcon);
533
Eric Erfanianccca3152017-02-22 16:32:36 -0800534 // any change means we are definitely updating
535 boolean retval =
536 (mSavedIcon != icon)
537 || !Objects.equals(mSavedContent, content)
538 || (mCallState != state)
Eric Erfanian8369df02017-05-03 10:27:13 -0700539 || largeIconChanged
Eric Erfanianccca3152017-02-22 16:32:36 -0800540 || contentTitleChanged
yuegb26c1ae2017-09-18 16:59:16 -0700541 || !Objects.equals(mRingtone, ringtone)
542 || !Objects.equals(savedCallAudioState, callAudioState);
Eric Erfanianccca3152017-02-22 16:32:36 -0800543
wangqi9982f0d2017-10-11 17:46:07 -0700544 LogUtil.d(
545 "StatusBarNotifier.checkForChangeAndSaveData",
546 "data changed: icon: %b, content: %b, state: %b, largeIcon: %b, title: %b, ringtone: %b, "
547 + "audioState: %b, type: %b",
548 (mSavedIcon != icon),
549 !Objects.equals(mSavedContent, content),
550 (mCallState != state),
551 largeIconChanged,
552 contentTitleChanged,
553 !Objects.equals(mRingtone, ringtone),
554 !Objects.equals(savedCallAudioState, callAudioState),
555 mCurrentNotification != notificationType);
Eric Erfanianccca3152017-02-22 16:32:36 -0800556 // If we aren't showing a notification right now or the notification type is changing,
557 // definitely do an update.
558 if (mCurrentNotification != notificationType) {
559 if (mCurrentNotification == NOTIFICATION_NONE) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700560 LogUtil.d(
561 "StatusBarNotifier.checkForChangeAndSaveData", "showing notification for first time.");
Eric Erfanianccca3152017-02-22 16:32:36 -0800562 }
563 retval = true;
564 }
565
566 mSavedIcon = icon;
567 mSavedContent = content;
568 mCallState = state;
569 mSavedLargeIcon = largeIcon;
570 mSavedContentTitle = contentTitle;
571 mRingtone = ringtone;
yuegb26c1ae2017-09-18 16:59:16 -0700572 savedCallAudioState = callAudioState;
Eric Erfanianccca3152017-02-22 16:32:36 -0800573
574 if (retval) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700575 LogUtil.d(
576 "StatusBarNotifier.checkForChangeAndSaveData", "data changed. Showing notification");
Eric Erfanianccca3152017-02-22 16:32:36 -0800577 }
578
579 return retval;
580 }
581
582 /** Returns the main string to use in the notification. */
583 @VisibleForTesting
584 @Nullable
585 String getContentTitle(ContactCacheEntry contactInfo, DialerCall call) {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700586 if (call.isConferenceCall()) {
587 return CallerInfoUtils.getConferenceString(
588 mContext, call.hasProperty(Details.PROPERTY_GENERIC_CONFERENCE));
Eric Erfanianccca3152017-02-22 16:32:36 -0800589 }
590
591 String preferredName =
592 ContactDisplayUtils.getPreferredDisplayName(
593 contactInfo.namePrimary, contactInfo.nameAlternative, mContactsPreferences);
594 if (TextUtils.isEmpty(preferredName)) {
595 return TextUtils.isEmpty(contactInfo.number)
596 ? null
597 : BidiFormatter.getInstance()
598 .unicodeWrap(contactInfo.number, TextDirectionHeuristics.LTR);
599 }
600 return preferredName;
601 }
602
603 private void addPersonReference(
604 Notification.Builder builder, ContactCacheEntry contactInfo, DialerCall call) {
605 // Query {@link Contacts#CONTENT_LOOKUP_URI} directly with work lookup key is not allowed.
606 // So, do not pass {@link Contacts#CONTENT_LOOKUP_URI} to NotificationManager to avoid
607 // NotificationManager using it.
608 if (contactInfo.lookupUri != null && contactInfo.userType != ContactsUtils.USER_TYPE_WORK) {
609 builder.addPerson(contactInfo.lookupUri.toString());
610 } else if (!TextUtils.isEmpty(call.getNumber())) {
611 builder.addPerson(Uri.fromParts(PhoneAccount.SCHEME_TEL, call.getNumber(), null).toString());
612 }
613 }
614
615 /** Gets a large icon from the contact info object to display in the notification. */
Eric Erfanian83b20212017-05-31 08:53:10 -0700616 private static Bitmap getLargeIconToDisplay(
617 Context context, ContactCacheEntry contactInfo, DialerCall call) {
618 Resources resources = context.getResources();
Eric Erfanianccca3152017-02-22 16:32:36 -0800619 Bitmap largeIcon = null;
Eric Erfanianccca3152017-02-22 16:32:36 -0800620 if (contactInfo.photo != null && (contactInfo.photo instanceof BitmapDrawable)) {
621 largeIcon = ((BitmapDrawable) contactInfo.photo).getBitmap();
622 }
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700623 if (contactInfo.photo == null) {
Eric Erfanian83b20212017-05-31 08:53:10 -0700624 int width = (int) resources.getDimension(android.R.dimen.notification_large_icon_width);
625 int height = (int) resources.getDimension(android.R.dimen.notification_large_icon_height);
Eric Erfanian2ca43182017-08-31 06:57:16 -0700626 @ContactType
627 int contactType =
628 LetterTileDrawable.getContactTypeFromPrimitives(
wangqi9982f0d2017-10-11 17:46:07 -0700629 call.isVoiceMailNumber(),
Eric Erfanian2ca43182017-08-31 06:57:16 -0700630 call.isSpam(),
631 contactInfo.isBusiness,
632 call.getNumberPresentation(),
633 call.isConferenceCall() && !call.hasProperty(Details.PROPERTY_GENERIC_CONFERENCE));
Eric Erfanian83b20212017-05-31 08:53:10 -0700634 LetterTileDrawable lettertile = new LetterTileDrawable(resources);
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700635
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700636 lettertile.setCanonicalDialerLetterTileDetails(
637 contactInfo.namePrimary == null ? contactInfo.number : contactInfo.namePrimary,
638 contactInfo.lookupKey,
639 LetterTileDrawable.SHAPE_CIRCLE,
640 contactType);
641 largeIcon = lettertile.getBitmap(width, height);
642 }
643
Eric Erfanianccca3152017-02-22 16:32:36 -0800644 if (call.isSpam()) {
Eric Erfanian83b20212017-05-31 08:53:10 -0700645 Drawable drawable = resources.getDrawable(R.drawable.blocked_contact, context.getTheme());
Eric Erfanianccca3152017-02-22 16:32:36 -0800646 largeIcon = DrawableConverter.drawableToBitmap(drawable);
647 }
648 return largeIcon;
649 }
650
651 private Bitmap getRoundedIcon(Bitmap bitmap) {
652 if (bitmap == null) {
653 return null;
654 }
655 final int height =
656 (int) mContext.getResources().getDimension(android.R.dimen.notification_large_icon_height);
657 final int width =
658 (int) mContext.getResources().getDimension(android.R.dimen.notification_large_icon_width);
659 return BitmapUtil.getRoundedBitmap(bitmap, width, height);
660 }
661
662 /**
663 * Returns the appropriate icon res Id to display based on the call for which we want to display
664 * information.
665 */
666 private int getIconToDisplay(DialerCall call) {
667 // Even if both lines are in use, we only show a single item in
668 // the expanded Notifications UI. It's labeled "Ongoing call"
669 // (or "On hold" if there's only one call, and it's on hold.)
670 // Also, we don't have room to display caller-id info from two
671 // different calls. So if both lines are in use, display info
672 // from the foreground call. And if there's a ringing call,
673 // display that regardless of the state of the other calls.
674 if (call.getState() == DialerCall.State.ONHOLD) {
675 return R.drawable.ic_phone_paused_white_24dp;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700676 } else if (call.getVideoTech().getSessionModificationState()
calderwoodra1dc2cea2017-09-20 16:30:41 -0700677 == SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST
678 || call.isVideoCall()) {
Eric Erfaniand8046e52017-04-06 09:41:50 -0700679 return R.drawable.quantum_ic_videocam_white_24;
Eric Erfanian90508232017-03-24 09:31:16 -0700680 } else if (call.hasProperty(PROPERTY_HIGH_DEF_AUDIO)
681 && MotorolaUtils.shouldShowHdIconInNotification(mContext)) {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700682 // Normally when a call is ongoing the status bar displays an icon of a phone. This is a
683 // helpful hint for users so they know how to get back to the call. For Sprint HD calls, we
684 // replace this icon with an icon of a phone with a HD badge. This is a carrier requirement.
Eric Erfanian90508232017-03-24 09:31:16 -0700685 return R.drawable.ic_hd_call;
Eric Erfanianccca3152017-02-22 16:32:36 -0800686 }
Eric Erfanian2ca43182017-08-31 06:57:16 -0700687 // If ReturnToCall is enabled, use the static icon. The animated one will show in the bubble.
688 if (ReturnToCallController.isEnabled(mContext)) {
689 return R.drawable.quantum_ic_call_vd_theme_24;
690 } else {
691 return R.drawable.on_going_call;
692 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800693 }
694
695 /** Returns the message to use with the notification. */
twyend1d1d0c2017-10-05 17:34:43 -0700696 private CharSequence getContentString(DialerCall call, @UserType long userType) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800697 boolean isIncomingOrWaiting =
698 call.getState() == DialerCall.State.INCOMING
699 || call.getState() == DialerCall.State.CALL_WAITING;
700
701 if (isIncomingOrWaiting
702 && call.getNumberPresentation() == TelecomManager.PRESENTATION_ALLOWED) {
703
704 if (!TextUtils.isEmpty(call.getChildNumber())) {
705 return mContext.getString(R.string.child_number, call.getChildNumber());
706 } else if (!TextUtils.isEmpty(call.getCallSubject()) && call.isCallSubjectSupported()) {
707 return call.getCallSubject();
708 }
709 }
710
711 int resId = R.string.notification_ongoing_call;
Eric Erfanian2ca43182017-08-31 06:57:16 -0700712 String wifiBrand = mContext.getString(R.string.notification_call_wifi_brand);
Eric Erfanianccca3152017-02-22 16:32:36 -0800713 if (call.hasProperty(Details.PROPERTY_WIFI)) {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700714 resId = R.string.notification_ongoing_call_wifi_template;
Eric Erfanianccca3152017-02-22 16:32:36 -0800715 }
716
717 if (isIncomingOrWaiting) {
Eric Erfaniand8046e52017-04-06 09:41:50 -0700718 if (call.isSpam()) {
719 resId = R.string.notification_incoming_spam_call;
Eric Erfanian2ca43182017-08-31 06:57:16 -0700720 } else if (shouldShowEnrichedCallNotification(call.getEnrichedCallSession())) {
721 resId = getECIncomingCallText(call.getEnrichedCallSession());
Eric Erfaniand8046e52017-04-06 09:41:50 -0700722 } else if (call.hasProperty(Details.PROPERTY_WIFI)) {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700723 resId = R.string.notification_incoming_call_wifi_template;
wangqi9982f0d2017-10-11 17:46:07 -0700724 } else if (call.getAccountHandle() != null && hasMultiplePhoneAccounts(call)) {
twyend1d1d0c2017-10-05 17:34:43 -0700725 return getMultiSimIncomingText(call);
yueg45e45732017-10-09 14:35:06 -0700726 } else if (call.isVideoCall()) {
727 resId = R.string.notification_incoming_video_call;
Eric Erfanianccca3152017-02-22 16:32:36 -0800728 } else {
Eric Erfaniand8046e52017-04-06 09:41:50 -0700729 resId = R.string.notification_incoming_call;
Eric Erfanianccca3152017-02-22 16:32:36 -0800730 }
731 } else if (call.getState() == DialerCall.State.ONHOLD) {
732 resId = R.string.notification_on_hold;
calderwoodra1dc2cea2017-09-20 16:30:41 -0700733 } else if (call.isVideoCall()) {
734 resId =
735 call.getVideoTech().isPaused()
736 ? R.string.notification_ongoing_paused_video_call
737 : R.string.notification_ongoing_video_call;
Eric Erfanianccca3152017-02-22 16:32:36 -0800738 } else if (DialerCall.State.isDialing(call.getState())) {
739 resId = R.string.notification_dialing;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700740 } else if (call.getVideoTech().getSessionModificationState()
Eric Erfanian90508232017-03-24 09:31:16 -0700741 == SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800742 resId = R.string.notification_requesting_video_call;
743 }
744
745 // Is the call placed through work connection service.
746 boolean isWorkCall = call.hasProperty(PROPERTY_ENTERPRISE_CALL);
747 if (userType == ContactsUtils.USER_TYPE_WORK || isWorkCall) {
748 resId = getWorkStringFromPersonalString(resId);
Eric Erfanian2ca43182017-08-31 06:57:16 -0700749 wifiBrand = mContext.getString(R.string.notification_call_wifi_work_brand);
750 }
751
752 if (resId == R.string.notification_incoming_call_wifi_template
753 || resId == R.string.notification_ongoing_call_wifi_template) {
754 // TODO(b/64525903): Potentially apply this template logic everywhere.
755 return mContext.getString(resId, wifiBrand);
Eric Erfanianccca3152017-02-22 16:32:36 -0800756 }
757
758 return mContext.getString(resId);
759 }
760
Eric Erfanian2ca43182017-08-31 06:57:16 -0700761 private boolean shouldShowEnrichedCallNotification(Session session) {
762 if (session == null) {
763 return false;
764 }
765 return session.getMultimediaData().hasData() || session.getMultimediaData().isImportant();
766 }
767
Eric Erfaniand8046e52017-04-06 09:41:50 -0700768 private int getECIncomingCallText(Session session) {
769 int resId;
770 MultimediaData data = session.getMultimediaData();
771 boolean hasImage = data.hasImageData();
772 boolean hasSubject = !TextUtils.isEmpty(data.getText());
773 boolean hasMap = data.getLocation() != null;
774 if (data.isImportant()) {
775 if (hasMap) {
776 if (hasImage) {
777 if (hasSubject) {
778 resId = R.string.important_notification_incoming_call_with_photo_message_location;
779 } else {
780 resId = R.string.important_notification_incoming_call_with_photo_location;
781 }
782 } else if (hasSubject) {
783 resId = R.string.important_notification_incoming_call_with_message_location;
784 } else {
785 resId = R.string.important_notification_incoming_call_with_location;
786 }
787 } else if (hasImage) {
788 if (hasSubject) {
789 resId = R.string.important_notification_incoming_call_with_photo_message;
790 } else {
791 resId = R.string.important_notification_incoming_call_with_photo;
792 }
Eric Erfanian2ca43182017-08-31 06:57:16 -0700793 } else if (hasSubject) {
Eric Erfaniand8046e52017-04-06 09:41:50 -0700794 resId = R.string.important_notification_incoming_call_with_message;
Eric Erfanian2ca43182017-08-31 06:57:16 -0700795 } else {
796 resId = R.string.important_notification_incoming_call;
Eric Erfaniand8046e52017-04-06 09:41:50 -0700797 }
798 if (mContext.getString(resId).length() > 50) {
799 resId = R.string.important_notification_incoming_call_attachments;
800 }
801 } else {
802 if (hasMap) {
803 if (hasImage) {
804 if (hasSubject) {
805 resId = R.string.notification_incoming_call_with_photo_message_location;
806 } else {
807 resId = R.string.notification_incoming_call_with_photo_location;
808 }
809 } else if (hasSubject) {
810 resId = R.string.notification_incoming_call_with_message_location;
811 } else {
812 resId = R.string.notification_incoming_call_with_location;
813 }
814 } else if (hasImage) {
815 if (hasSubject) {
816 resId = R.string.notification_incoming_call_with_photo_message;
817 } else {
818 resId = R.string.notification_incoming_call_with_photo;
819 }
820 } else {
821 resId = R.string.notification_incoming_call_with_message;
822 }
823 }
824 if (mContext.getString(resId).length() > 50) {
825 resId = R.string.notification_incoming_call_attachments;
826 }
827 return resId;
828 }
829
twyend1d1d0c2017-10-05 17:34:43 -0700830 private CharSequence getMultiSimIncomingText(DialerCall call) {
831 PhoneAccount phoneAccount =
832 mContext.getSystemService(TelecomManager.class).getPhoneAccount(call.getAccountHandle());
833 SpannableString string =
834 new SpannableString(
835 mContext.getString(
836 R.string.notification_incoming_call_mutli_sim, phoneAccount.getLabel()));
837 int accountStart = string.toString().lastIndexOf(phoneAccount.getLabel().toString());
838 int accountEnd = accountStart + phoneAccount.getLabel().length();
839
840 string.setSpan(
841 new ForegroundColorSpan(phoneAccount.getHighlightColor()),
842 accountStart,
843 accountEnd,
844 Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
845 return string;
846 }
847
Eric Erfanianccca3152017-02-22 16:32:36 -0800848 /** Gets the most relevant call to display in the notification. */
849 private DialerCall getCallToShow(CallList callList) {
850 if (callList == null) {
851 return null;
852 }
853 DialerCall call = callList.getIncomingCall();
854 if (call == null) {
855 call = callList.getOutgoingCall();
856 }
857 if (call == null) {
858 call = callList.getVideoUpgradeRequestCall();
859 }
860 if (call == null) {
861 call = callList.getActiveOrBackgroundCall();
862 }
863 return call;
864 }
865
866 private Spannable getActionText(@StringRes int stringRes, @ColorRes int colorRes) {
867 Spannable spannable = new SpannableString(mContext.getText(stringRes));
868 if (VERSION.SDK_INT >= VERSION_CODES.N_MR1) {
869 // This will only work for cases where the Notification.Builder has a fullscreen intent set
870 // Notification.Builder that does not have a full screen intent will take the color of the
871 // app and the following leads to a no-op.
872 spannable.setSpan(
873 new ForegroundColorSpan(mContext.getColor(colorRes)), 0, spannable.length(), 0);
874 }
875 return spannable;
876 }
877
878 private void addAnswerAction(Notification.Builder builder) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700879 LogUtil.d(
880 "StatusBarNotifier.addAnswerAction",
881 "will show \"answer\" action in the incoming call Notification");
Eric Erfanianccca3152017-02-22 16:32:36 -0800882 PendingIntent answerVoicePendingIntent =
883 createNotificationPendingIntent(mContext, ACTION_ANSWER_VOICE_INCOMING_CALL);
884 builder.addAction(
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700885 new Notification.Action.Builder(
Eric Erfanian2ca43182017-08-31 06:57:16 -0700886 Icon.createWithResource(mContext, R.drawable.quantum_ic_call_white_24),
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700887 getActionText(
888 R.string.notification_action_answer, R.color.notification_action_accept),
889 answerVoicePendingIntent)
890 .build());
Eric Erfanianccca3152017-02-22 16:32:36 -0800891 }
892
893 private void addDismissAction(Notification.Builder builder) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700894 LogUtil.d(
895 "StatusBarNotifier.addDismissAction",
896 "will show \"decline\" action in the incoming call Notification");
Eric Erfanianccca3152017-02-22 16:32:36 -0800897 PendingIntent declinePendingIntent =
898 createNotificationPendingIntent(mContext, ACTION_DECLINE_INCOMING_CALL);
899 builder.addAction(
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700900 new Notification.Action.Builder(
Eric Erfaniand8046e52017-04-06 09:41:50 -0700901 Icon.createWithResource(mContext, R.drawable.quantum_ic_close_white_24),
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700902 getActionText(
903 R.string.notification_action_dismiss, R.color.notification_action_dismiss),
904 declinePendingIntent)
905 .build());
Eric Erfanianccca3152017-02-22 16:32:36 -0800906 }
907
908 private void addHangupAction(Notification.Builder builder) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700909 LogUtil.d(
910 "StatusBarNotifier.addHangupAction",
911 "will show \"hang-up\" action in the ongoing active call Notification");
Eric Erfanianccca3152017-02-22 16:32:36 -0800912 PendingIntent hangupPendingIntent =
913 createNotificationPendingIntent(mContext, ACTION_HANG_UP_ONGOING_CALL);
914 builder.addAction(
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700915 new Notification.Action.Builder(
Eric Erfanian2ca43182017-08-31 06:57:16 -0700916 Icon.createWithResource(mContext, R.drawable.quantum_ic_call_end_white_24),
Eric Erfanian10b34a52017-05-04 08:23:17 -0700917 mContext.getText(R.string.notification_action_end_call),
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700918 hangupPendingIntent)
919 .build());
Eric Erfanianccca3152017-02-22 16:32:36 -0800920 }
921
yuegb26c1ae2017-09-18 16:59:16 -0700922 private void addSpeakerAction(Notification.Builder builder, CallAudioState callAudioState) {
923 if ((callAudioState.getSupportedRouteMask() & CallAudioState.ROUTE_BLUETOOTH)
924 == CallAudioState.ROUTE_BLUETOOTH) {
925 // Don't add speaker button if bluetooth is connected
926 return;
927 }
928 if (callAudioState.getRoute() == CallAudioState.ROUTE_SPEAKER) {
929 addSpeakerOffAction(builder);
930 } else if ((callAudioState.getRoute() & CallAudioState.ROUTE_WIRED_OR_EARPIECE) != 0) {
931 addSpeakerOnAction(builder);
932 }
933 }
934
935 private void addSpeakerOnAction(Notification.Builder builder) {
936 LogUtil.d(
937 "StatusBarNotifier.addSpeakerOnAction",
938 "will show \"Speaker on\" action in the ongoing active call Notification");
939 PendingIntent speakerOnPendingIntent =
940 createNotificationPendingIntent(mContext, ACTION_TURN_ON_SPEAKER);
941 builder.addAction(
942 new Notification.Action.Builder(
943 Icon.createWithResource(mContext, R.drawable.quantum_ic_volume_up_white_24),
944 mContext.getText(R.string.notification_action_speaker_on),
945 speakerOnPendingIntent)
946 .build());
947 }
948
949 private void addSpeakerOffAction(Notification.Builder builder) {
950 LogUtil.d(
951 "StatusBarNotifier.addSpeakerOffAction",
952 "will show \"Speaker off\" action in the ongoing active call Notification");
953 PendingIntent speakerOffPendingIntent =
954 createNotificationPendingIntent(mContext, ACTION_TURN_OFF_SPEAKER);
955 builder.addAction(
956 new Notification.Action.Builder(
957 Icon.createWithResource(mContext, R.drawable.quantum_ic_phone_in_talk_white_24),
958 mContext.getText(R.string.notification_action_speaker_off),
959 speakerOffPendingIntent)
960 .build());
961 }
962
Eric Erfanianccca3152017-02-22 16:32:36 -0800963 private void addVideoCallAction(Notification.Builder builder) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700964 LogUtil.i(
965 "StatusBarNotifier.addVideoCallAction",
966 "will show \"video\" action in the incoming call Notification");
Eric Erfanianccca3152017-02-22 16:32:36 -0800967 PendingIntent answerVideoPendingIntent =
968 createNotificationPendingIntent(mContext, ACTION_ANSWER_VIDEO_INCOMING_CALL);
969 builder.addAction(
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700970 new Notification.Action.Builder(
Eric Erfaniand8046e52017-04-06 09:41:50 -0700971 Icon.createWithResource(mContext, R.drawable.quantum_ic_videocam_white_24),
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700972 getActionText(
973 R.string.notification_action_answer_video,
974 R.color.notification_action_answer_video),
975 answerVideoPendingIntent)
976 .build());
Eric Erfanianccca3152017-02-22 16:32:36 -0800977 }
978
979 private void addAcceptUpgradeRequestAction(Notification.Builder builder) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700980 LogUtil.i(
981 "StatusBarNotifier.addAcceptUpgradeRequestAction",
982 "will show \"accept upgrade\" action in the incoming call Notification");
Eric Erfanianccca3152017-02-22 16:32:36 -0800983 PendingIntent acceptVideoPendingIntent =
984 createNotificationPendingIntent(mContext, ACTION_ACCEPT_VIDEO_UPGRADE_REQUEST);
985 builder.addAction(
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700986 new Notification.Action.Builder(
Eric Erfaniand8046e52017-04-06 09:41:50 -0700987 Icon.createWithResource(mContext, R.drawable.quantum_ic_videocam_white_24),
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700988 getActionText(
989 R.string.notification_action_accept, R.color.notification_action_accept),
990 acceptVideoPendingIntent)
991 .build());
Eric Erfanianccca3152017-02-22 16:32:36 -0800992 }
993
994 private void addDismissUpgradeRequestAction(Notification.Builder builder) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700995 LogUtil.i(
996 "StatusBarNotifier.addDismissUpgradeRequestAction",
997 "will show \"dismiss upgrade\" action in the incoming call Notification");
Eric Erfanianccca3152017-02-22 16:32:36 -0800998 PendingIntent declineVideoPendingIntent =
999 createNotificationPendingIntent(mContext, ACTION_DECLINE_VIDEO_UPGRADE_REQUEST);
1000 builder.addAction(
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001001 new Notification.Action.Builder(
Eric Erfaniand8046e52017-04-06 09:41:50 -07001002 Icon.createWithResource(mContext, R.drawable.quantum_ic_videocam_white_24),
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001003 getActionText(
1004 R.string.notification_action_dismiss, R.color.notification_action_dismiss),
1005 declineVideoPendingIntent)
1006 .build());
Eric Erfanianccca3152017-02-22 16:32:36 -08001007 }
1008
1009 /** Adds fullscreen intent to the builder. */
Eric Erfanian10b34a52017-05-04 08:23:17 -07001010 private void configureFullScreenIntent(Notification.Builder builder, PendingIntent intent) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001011 // Ok, we actually want to launch the incoming call
1012 // UI at this point (in addition to simply posting a notification
1013 // to the status bar). Setting fullScreenIntent will cause
1014 // the InCallScreen to be launched immediately *unless* the
1015 // current foreground activity is marked as "immersive".
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001016 LogUtil.d("StatusBarNotifier.configureFullScreenIntent", "setting fullScreenIntent: " + intent);
Eric Erfanianccca3152017-02-22 16:32:36 -08001017 builder.setFullScreenIntent(intent, true);
Eric Erfanianccca3152017-02-22 16:32:36 -08001018 }
1019
1020 private Notification.Builder getNotificationBuilder() {
1021 final Notification.Builder builder = new Notification.Builder(mContext);
1022 builder.setOngoing(true);
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001023 builder.setOnlyAlertOnce(true);
Eric Erfanian10b34a52017-05-04 08:23:17 -07001024 // This will be ignored on O+ and handled by the channel
Eric Erfanian2ca43182017-08-31 06:57:16 -07001025 // noinspection deprecation
Eric Erfanian10b34a52017-05-04 08:23:17 -07001026 builder.setPriority(Notification.PRIORITY_HIGH);
Eric Erfanianccca3152017-02-22 16:32:36 -08001027
1028 return builder;
1029 }
1030
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001031 private PendingIntent createLaunchPendingIntent(boolean isFullScreen) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001032 Intent intent =
1033 InCallActivity.getIntent(
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001034 mContext, false /* showDialpad */, false /* newOutgoingCall */, isFullScreen);
Eric Erfanianccca3152017-02-22 16:32:36 -08001035
Eric Erfanian2ca43182017-08-31 06:57:16 -07001036 int requestCode = InCallActivity.PENDING_INTENT_REQUEST_CODE_NON_FULL_SCREEN;
Eric Erfanianccca3152017-02-22 16:32:36 -08001037 if (isFullScreen) {
1038 // Use a unique request code so that the pending intent isn't clobbered by the
1039 // non-full screen pending intent.
Eric Erfanian2ca43182017-08-31 06:57:16 -07001040 requestCode = InCallActivity.PENDING_INTENT_REQUEST_CODE_FULL_SCREEN;
Eric Erfanianccca3152017-02-22 16:32:36 -08001041 }
1042
1043 // PendingIntent that can be used to launch the InCallActivity. The
1044 // system fires off this intent if the user pulls down the windowshade
1045 // and clicks the notification's expanded view. It's also used to
1046 // launch the InCallActivity immediately when when there's an incoming
1047 // call (see the "fullScreenIntent" field below).
1048 return PendingIntent.getActivity(mContext, requestCode, intent, 0);
1049 }
1050
1051 private void setStatusBarCallListener(StatusBarCallListener listener) {
1052 if (mStatusBarCallListener != null) {
1053 mStatusBarCallListener.cleanup();
1054 }
1055 mStatusBarCallListener = listener;
1056 }
1057
wangqi9982f0d2017-10-11 17:46:07 -07001058 private boolean hasMultiplePhoneAccounts(DialerCall call) {
1059 if (call.getCallCapableAccounts() == null) {
1060 return false;
1061 }
1062 return call.getCallCapableAccounts().size() > 1;
twyend1d1d0c2017-10-05 17:34:43 -07001063 }
1064
yuegb26c1ae2017-09-18 16:59:16 -07001065 @Override
1066 public void onAudioStateChanged(CallAudioState audioState) {
1067 if (CallList.getInstance().getActiveOrBackgroundCall() == null) {
1068 // We only care about speaker mode when in call
1069 return;
1070 }
1071
1072 updateNotification(CallList.getInstance());
1073 }
1074
Eric Erfanianccca3152017-02-22 16:32:36 -08001075 private class StatusBarCallListener implements DialerCallListener {
1076
1077 private DialerCall mDialerCall;
1078
1079 StatusBarCallListener(DialerCall dialerCall) {
1080 mDialerCall = dialerCall;
1081 mDialerCall.addListener(this);
1082 }
1083
1084 void cleanup() {
1085 mDialerCall.removeListener(this);
1086 }
1087
1088 @Override
1089 public void onDialerCallDisconnect() {}
1090
1091 @Override
1092 public void onDialerCallUpdate() {
1093 if (CallList.getInstance().getIncomingCall() == null) {
1094 mDialerRingtoneManager.stopCallWaitingTone();
1095 }
1096 }
1097
1098 @Override
1099 public void onDialerCallChildNumberChange() {}
1100
1101 @Override
1102 public void onDialerCallLastForwardedNumberChange() {}
1103
1104 @Override
1105 public void onDialerCallUpgradeToVideo() {}
1106
1107 @Override
1108 public void onWiFiToLteHandover() {}
1109
1110 @Override
1111 public void onHandoverToWifiFailure() {}
1112
Eric Erfanianc857f902017-05-15 14:05:33 -07001113 @Override
1114 public void onInternationalCallOnWifi() {}
1115
Eric Erfanian2ca43182017-08-31 06:57:16 -07001116 @Override
1117 public void onEnrichedCallSessionUpdate() {}
1118
Eric Erfanianccca3152017-02-22 16:32:36 -08001119 /**
1120 * Responds to changes in the session modification state for the call by dismissing the status
1121 * bar notification as required.
1122 */
1123 @Override
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001124 public void onDialerCallSessionModificationStateChange() {
1125 if (mDialerCall.getVideoTech().getSessionModificationState()
Eric Erfanian90508232017-03-24 09:31:16 -07001126 == SessionModificationState.NO_REQUEST) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001127 cleanup();
1128 updateNotification(CallList.getInstance());
1129 }
1130 }
1131 }
1132}