blob: 458df51494a187be01a2d108be6aaeaba3d973b6 [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;
27
Eric Erfaniand5e47f62017-03-15 14:41:07 -070028import android.Manifest;
Eric Erfanianccca3152017-02-22 16:32:36 -080029import android.app.ActivityManager;
30import android.app.Notification;
31import android.app.NotificationManager;
32import 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;
44import android.support.annotation.ColorRes;
45import android.support.annotation.NonNull;
46import android.support.annotation.Nullable;
Eric Erfaniand5e47f62017-03-15 14:41:07 -070047import android.support.annotation.RequiresPermission;
Eric Erfanianccca3152017-02-22 16:32:36 -080048import android.support.annotation.StringRes;
49import android.support.annotation.VisibleForTesting;
Eric Erfaniand5e47f62017-03-15 14:41:07 -070050import android.support.v4.os.BuildCompat;
Eric Erfanianccca3152017-02-22 16:32:36 -080051import android.telecom.Call.Details;
52import android.telecom.PhoneAccount;
Eric Erfaniand5e47f62017-03-15 14:41:07 -070053import android.telecom.PhoneAccountHandle;
Eric Erfanianccca3152017-02-22 16:32:36 -080054import android.telecom.TelecomManager;
55import android.text.BidiFormatter;
56import android.text.Spannable;
57import android.text.SpannableString;
58import android.text.TextDirectionHeuristics;
59import android.text.TextUtils;
60import android.text.style.ForegroundColorSpan;
61import com.android.contacts.common.ContactsUtils;
62import com.android.contacts.common.ContactsUtils.UserType;
Eric Erfaniand5e47f62017-03-15 14:41:07 -070063import com.android.contacts.common.lettertiles.LetterTileDrawable;
Eric Erfanian91ce7d22017-06-05 13:35:02 -070064import com.android.contacts.common.lettertiles.LetterTileDrawable.ContactType;
Eric Erfanianccca3152017-02-22 16:32:36 -080065import com.android.contacts.common.preference.ContactsPreferences;
66import com.android.contacts.common.util.BitmapUtil;
67import com.android.contacts.common.util.ContactDisplayUtils;
68import com.android.dialer.common.LogUtil;
Eric Erfaniand8046e52017-04-06 09:41:50 -070069import com.android.dialer.enrichedcall.EnrichedCallManager;
70import com.android.dialer.enrichedcall.Session;
71import com.android.dialer.multimedia.MultimediaData;
Eric Erfanianea7890c2017-06-19 12:40:59 -070072import com.android.dialer.notification.NotificationChannelId;
Eric Erfanian90508232017-03-24 09:31:16 -070073import com.android.dialer.oem.MotorolaUtils;
Eric Erfanianccca3152017-02-22 16:32:36 -080074import com.android.dialer.util.DrawableConverter;
75import com.android.incallui.ContactInfoCache.ContactCacheEntry;
76import com.android.incallui.ContactInfoCache.ContactInfoCacheCallback;
77import com.android.incallui.InCallPresenter.InCallState;
78import com.android.incallui.async.PausableExecutorImpl;
79import com.android.incallui.call.CallList;
80import com.android.incallui.call.DialerCall;
Eric Erfanianccca3152017-02-22 16:32:36 -080081import com.android.incallui.call.DialerCallListener;
82import com.android.incallui.ringtone.DialerRingtoneManager;
83import com.android.incallui.ringtone.InCallTonePlayer;
84import com.android.incallui.ringtone.ToneGeneratorFactory;
Eric Erfanian90508232017-03-24 09:31:16 -070085import com.android.incallui.videotech.utils.SessionModificationState;
Eric Erfaniand5e47f62017-03-15 14:41:07 -070086import java.util.List;
87import java.util.Locale;
Eric Erfanianccca3152017-02-22 16:32:36 -080088import java.util.Objects;
89
90/** This class adds Notifications to the status bar for the in-call experience. */
Eric Erfaniand8046e52017-04-06 09:41:50 -070091public class StatusBarNotifier
92 implements InCallPresenter.InCallStateListener, EnrichedCallManager.StateChangedListener {
Eric Erfanianccca3152017-02-22 16:32:36 -080093
Eric Erfanianea7890c2017-06-19 12:40:59 -070094 private static final String NOTIFICATION_TAG = "STATUS_BAR_NOTIFIER";
95 private static final int NOTIFICATION_ID = 1;
96
Eric Erfanianccca3152017-02-22 16:32:36 -080097 // Notification types
98 // Indicates that no notification is currently showing.
99 private static final int NOTIFICATION_NONE = 0;
100 // Notification for an active call. This is non-interruptive, but cannot be dismissed.
Eric Erfanian10b34a52017-05-04 08:23:17 -0700101 private static final int NOTIFICATION_IN_CALL = 1;
Eric Erfanianccca3152017-02-22 16:32:36 -0800102 // Notification for incoming calls. This is interruptive and will show up as a HUN.
Eric Erfanian10b34a52017-05-04 08:23:17 -0700103 private static final int NOTIFICATION_INCOMING_CALL = 2;
104 // Notification for incoming calls in the case where there is already an active call.
105 // This is non-interruptive, but otherwise behaves the same as NOTIFICATION_INCOMING_CALL
106 private static final int NOTIFICATION_INCOMING_CALL_QUIET = 3;
Eric Erfanianccca3152017-02-22 16:32:36 -0800107
108 private static final int PENDING_INTENT_REQUEST_CODE_NON_FULL_SCREEN = 0;
109 private static final int PENDING_INTENT_REQUEST_CODE_FULL_SCREEN = 1;
110
111 private static final long[] VIBRATE_PATTERN = new long[] {0, 1000, 1000};
112
113 private final Context mContext;
114 private final ContactInfoCache mContactInfoCache;
115 private final NotificationManager mNotificationManager;
116 private final DialerRingtoneManager mDialerRingtoneManager;
117 @Nullable private ContactsPreferences mContactsPreferences;
118 private int mCurrentNotification = NOTIFICATION_NONE;
119 private int mCallState = DialerCall.State.INVALID;
120 private int mSavedIcon = 0;
121 private String mSavedContent = null;
122 private Bitmap mSavedLargeIcon;
123 private String mSavedContentTitle;
124 private Uri mRingtone;
125 private StatusBarCallListener mStatusBarCallListener;
126
Eric Erfaniand8046e52017-04-06 09:41:50 -0700127 public StatusBarNotifier(@NonNull Context context, @NonNull ContactInfoCache contactInfoCache) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800128 Objects.requireNonNull(context);
129 mContext = context;
130 mContactsPreferences = ContactsPreferencesFactory.newContactsPreferences(mContext);
131 mContactInfoCache = contactInfoCache;
132 mNotificationManager = context.getSystemService(NotificationManager.class);
133 mDialerRingtoneManager =
134 new DialerRingtoneManager(
135 new InCallTonePlayer(new ToneGeneratorFactory(), new PausableExecutorImpl()),
136 CallList.getInstance());
137 mCurrentNotification = NOTIFICATION_NONE;
138 }
139
140 /**
141 * Should only be called from a irrecoverable state where it is necessary to dismiss all
142 * notifications.
143 */
144 static void clearAllCallNotifications(Context backupContext) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700145 LogUtil.i(
146 "StatusBarNotifier.clearAllCallNotifications",
147 "something terrible happened, clear all InCall notifications");
Eric Erfanianccca3152017-02-22 16:32:36 -0800148
149 NotificationManager notificationManager =
150 backupContext.getSystemService(NotificationManager.class);
Eric Erfanianea7890c2017-06-19 12:40:59 -0700151 notificationManager.cancel(NOTIFICATION_TAG, NOTIFICATION_ID);
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;
157 } else if (resId == R.string.notification_ongoing_call_wifi) {
158 return R.string.notification_ongoing_work_call_wifi;
159 } else if (resId == R.string.notification_incoming_call_wifi) {
160 return R.string.notification_incoming_work_call_wifi;
161 } else if (resId == R.string.notification_incoming_call) {
162 return R.string.notification_incoming_work_call;
163 } else {
164 return resId;
165 }
166 }
167
168 /**
169 * Returns PendingIntent for answering a phone call. This will typically be used from Notification
170 * context.
171 */
172 private static PendingIntent createNotificationPendingIntent(Context context, String action) {
173 final Intent intent = new Intent(action, null, context, NotificationBroadcastReceiver.class);
174 return PendingIntent.getBroadcast(context, 0, intent, 0);
175 }
176
177 /** Creates notifications according to the state we receive from {@link InCallPresenter}. */
178 @Override
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700179 @RequiresPermission(Manifest.permission.READ_PHONE_STATE)
Eric Erfanianccca3152017-02-22 16:32:36 -0800180 public void onStateChange(InCallState oldState, InCallState newState, CallList callList) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700181 LogUtil.d("StatusBarNotifier.onStateChange", "%s->%s", oldState, newState);
Eric Erfanianccca3152017-02-22 16:32:36 -0800182 updateNotification(callList);
183 }
184
Eric Erfaniand8046e52017-04-06 09:41:50 -0700185 @Override
186 public void onEnrichedCallStateChanged() {
187 LogUtil.enterBlock("StatusBarNotifier.onEnrichedCallStateChanged");
188 updateNotification(CallList.getInstance());
189 }
190
Eric Erfanianccca3152017-02-22 16:32:36 -0800191 /**
192 * Updates the phone app's status bar notification *and* launches the incoming call UI in response
193 * to a new incoming call.
194 *
195 * <p>If an incoming call is ringing (or call-waiting), the notification will also include a
196 * "fullScreenIntent" that will cause the InCallScreen to be launched, unless the current
197 * foreground activity is marked as "immersive".
198 *
199 * <p>(This is the mechanism that actually brings up the incoming call UI when we receive a "new
200 * ringing connection" event from the telephony layer.)
201 *
202 * <p>Also note that this method is safe to call even if the phone isn't actually ringing (or,
203 * more likely, if an incoming call *was* ringing briefly but then disconnected). In that case,
204 * we'll simply update or cancel the in-call notification based on the current phone state.
205 *
206 * @see #updateInCallNotification(CallList)
207 */
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700208 @RequiresPermission(Manifest.permission.READ_PHONE_STATE)
Eric Erfaniand8046e52017-04-06 09:41:50 -0700209 public void updateNotification(CallList callList) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800210 updateInCallNotification(callList);
211 }
212
213 /**
214 * Take down the in-call notification.
215 *
216 * @see #updateInCallNotification(CallList)
217 */
218 private void cancelNotification() {
219 if (mStatusBarCallListener != null) {
220 setStatusBarCallListener(null);
221 }
222 if (mCurrentNotification != NOTIFICATION_NONE) {
Eric Erfanian8369df02017-05-03 10:27:13 -0700223 LogUtil.i("StatusBarNotifier.cancelNotification", "cancel");
Eric Erfanianea7890c2017-06-19 12:40:59 -0700224 mNotificationManager.cancel(NOTIFICATION_TAG, NOTIFICATION_ID);
Eric Erfanianccca3152017-02-22 16:32:36 -0800225 }
226 mCurrentNotification = NOTIFICATION_NONE;
227 }
228
229 /**
230 * Helper method for updateInCallNotification() and updateNotification(): Update the phone app's
231 * status bar notification based on the current telephony state, or cancels the notification if
232 * the phone is totally idle.
233 */
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700234 @RequiresPermission(Manifest.permission.READ_PHONE_STATE)
Eric Erfanianccca3152017-02-22 16:32:36 -0800235 private void updateInCallNotification(CallList callList) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700236 LogUtil.d("StatusBarNotifier.updateInCallNotification", "");
Eric Erfanianccca3152017-02-22 16:32:36 -0800237
238 final DialerCall call = getCallToShow(callList);
239
240 if (call != null) {
241 showNotification(callList, call);
242 } else {
243 cancelNotification();
244 }
245 }
246
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700247 @RequiresPermission(Manifest.permission.READ_PHONE_STATE)
Eric Erfanianccca3152017-02-22 16:32:36 -0800248 private void showNotification(final CallList callList, final DialerCall call) {
249 final boolean isIncoming =
250 (call.getState() == DialerCall.State.INCOMING
251 || call.getState() == DialerCall.State.CALL_WAITING);
252 setStatusBarCallListener(new StatusBarCallListener(call));
253
254 // we make a call to the contact info cache to query for supplemental data to what the
255 // call provides. This includes the contact name and photo.
256 // This callback will always get called immediately and synchronously with whatever data
257 // it has available, and may make a subsequent call later (same thread) if it had to
258 // call into the contacts provider for more data.
259 mContactInfoCache.findInfo(
260 call,
261 isIncoming,
262 new ContactInfoCacheCallback() {
263 @Override
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700264 @RequiresPermission(Manifest.permission.READ_PHONE_STATE)
Eric Erfanianccca3152017-02-22 16:32:36 -0800265 public void onContactInfoComplete(String callId, ContactCacheEntry entry) {
266 DialerCall call = callList.getCallById(callId);
267 if (call != null) {
268 call.getLogState().contactLookupResult = entry.contactLookupResult;
269 buildAndSendNotification(callList, call, entry);
270 }
271 }
272
273 @Override
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700274 @RequiresPermission(Manifest.permission.READ_PHONE_STATE)
Eric Erfanianccca3152017-02-22 16:32:36 -0800275 public void onImageLoadComplete(String callId, ContactCacheEntry entry) {
276 DialerCall call = callList.getCallById(callId);
277 if (call != null) {
278 buildAndSendNotification(callList, call, entry);
279 }
280 }
281 });
282 }
283
284 /** Sets up the main Ui for the notification */
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700285 @RequiresPermission(Manifest.permission.READ_PHONE_STATE)
Eric Erfanianccca3152017-02-22 16:32:36 -0800286 private void buildAndSendNotification(
287 CallList callList, DialerCall originalCall, ContactCacheEntry contactInfo) {
288 // This can get called to update an existing notification after contact information has come
289 // back. However, it can happen much later. Before we continue, we need to make sure that
290 // the call being passed in is still the one we want to show in the notification.
291 final DialerCall call = getCallToShow(callList);
292 if (call == null || !call.getId().equals(originalCall.getId())) {
293 return;
294 }
295
296 final int callState = call.getState();
297
298 // Check if data has changed; if nothing is different, don't issue another notification.
299 final int iconResId = getIconToDisplay(call);
Eric Erfanian83b20212017-05-31 08:53:10 -0700300 Bitmap largeIcon = getLargeIconToDisplay(mContext, contactInfo, call);
Eric Erfanianccca3152017-02-22 16:32:36 -0800301 final String content = getContentString(call, contactInfo.userType);
302 final String contentTitle = getContentTitle(contactInfo, call);
303
304 final boolean isVideoUpgradeRequest =
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700305 call.getVideoTech().getSessionModificationState()
Eric Erfanian90508232017-03-24 09:31:16 -0700306 == SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST;
Eric Erfanianccca3152017-02-22 16:32:36 -0800307 final int notificationType;
308 if (callState == DialerCall.State.INCOMING
309 || callState == DialerCall.State.CALL_WAITING
310 || isVideoUpgradeRequest) {
Eric Erfanian10b34a52017-05-04 08:23:17 -0700311 boolean alreadyActive =
312 callList.getActiveOrBackgroundCall() != null
313 && InCallPresenter.getInstance().isShowingInCallUi();
314 notificationType =
315 alreadyActive ? NOTIFICATION_INCOMING_CALL_QUIET : NOTIFICATION_INCOMING_CALL;
Eric Erfanianccca3152017-02-22 16:32:36 -0800316 } else {
317 notificationType = NOTIFICATION_IN_CALL;
318 }
319
320 if (!checkForChangeAndSaveData(
321 iconResId,
322 content,
323 largeIcon,
324 contentTitle,
325 callState,
326 notificationType,
Eric Erfanian8369df02017-05-03 10:27:13 -0700327 contactInfo.contactRingtoneUri)) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800328 return;
329 }
330
331 if (largeIcon != null) {
332 largeIcon = getRoundedIcon(largeIcon);
333 }
334
335 // This builder is used for the notification shown when the device is locked and the user
336 // has set their notification settings to 'hide sensitive content'
337 // {@see Notification.Builder#setPublicVersion}.
338 Notification.Builder publicBuilder = new Notification.Builder(mContext);
339 publicBuilder
340 .setSmallIcon(iconResId)
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700341 .setColor(mContext.getResources().getColor(R.color.dialer_theme_color, mContext.getTheme()))
Eric Erfanianccca3152017-02-22 16:32:36 -0800342 // Hide work call state for the lock screen notification
343 .setContentTitle(getContentString(call, ContactsUtils.USER_TYPE_CURRENT));
344 setNotificationWhen(call, callState, publicBuilder);
345
346 // Builder for the notification shown when the device is unlocked or the user has set their
347 // notification settings to 'show all notification content'.
348 final Notification.Builder builder = getNotificationBuilder();
349 builder.setPublicVersion(publicBuilder.build());
350
351 // Set up the main intent to send the user to the in-call screen
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700352 builder.setContentIntent(createLaunchPendingIntent(false /* isFullScreen */));
Eric Erfanianccca3152017-02-22 16:32:36 -0800353
354 // Set the intent as a full screen intent as well if a call is incoming
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700355 PhoneAccountHandle accountHandle = call.getAccountHandle();
356 if (accountHandle == null) {
357 accountHandle = getAnyPhoneAccount();
358 }
Eric Erfanian10b34a52017-05-04 08:23:17 -0700359
360 LogUtil.i("StatusBarNotifier.buildAndSendNotification", "notificationType=" + notificationType);
361 switch (notificationType) {
362 case NOTIFICATION_INCOMING_CALL:
Eric Erfanianea7890c2017-06-19 12:40:59 -0700363 if (BuildCompat.isAtLeastO()) {
364 builder.setChannelId(NotificationChannelId.INCOMING_CALL);
365 }
Eric Erfanian10b34a52017-05-04 08:23:17 -0700366 configureFullScreenIntent(builder, createLaunchPendingIntent(true /* isFullScreen */));
367 // Set the notification category and bump the priority for incoming calls
368 builder.setCategory(Notification.CATEGORY_CALL);
369 // This will be ignored on O+ and handled by the channel
Eric Erfanian10b34a52017-05-04 08:23:17 -0700370 builder.setPriority(Notification.PRIORITY_MAX);
371 if (mCurrentNotification != NOTIFICATION_INCOMING_CALL) {
372 LogUtil.i(
373 "StatusBarNotifier.buildAndSendNotification",
374 "Canceling old notification so this one can be noisy");
375 // Moving from a non-interuptive notification (or none) to a noisy one. Cancel the old
376 // notification (if there is one) so the fullScreenIntent or HUN will show
Eric Erfanianea7890c2017-06-19 12:40:59 -0700377 mNotificationManager.cancel(NOTIFICATION_TAG, NOTIFICATION_ID);
Eric Erfanian10b34a52017-05-04 08:23:17 -0700378 }
379 break;
380 case NOTIFICATION_INCOMING_CALL_QUIET:
Eric Erfanianea7890c2017-06-19 12:40:59 -0700381 if (BuildCompat.isAtLeastO()) {
382 builder.setChannelId(NotificationChannelId.ONGOING_CALL);
383 }
Eric Erfanian10b34a52017-05-04 08:23:17 -0700384 break;
385 case NOTIFICATION_IN_CALL:
Eric Erfanianea7890c2017-06-19 12:40:59 -0700386 if (BuildCompat.isAtLeastO()) {
387 publicBuilder.setColorized(true);
388 builder.setColorized(true);
389 builder.setChannelId(NotificationChannelId.ONGOING_CALL);
390 }
Eric Erfanian10b34a52017-05-04 08:23:17 -0700391 break;
Eric Erfanianccca3152017-02-22 16:32:36 -0800392 }
393
394 // Set the content
395 builder.setContentText(content);
396 builder.setSmallIcon(iconResId);
397 builder.setContentTitle(contentTitle);
398 builder.setLargeIcon(largeIcon);
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700399 builder.setColor(
400 mContext.getResources().getColor(R.color.dialer_theme_color, mContext.getTheme()));
Eric Erfanianccca3152017-02-22 16:32:36 -0800401
402 if (isVideoUpgradeRequest) {
403 builder.setUsesChronometer(false);
404 addDismissUpgradeRequestAction(builder);
405 addAcceptUpgradeRequestAction(builder);
406 } else {
407 createIncomingCallNotification(call, callState, builder);
408 }
409
410 addPersonReference(builder, contactInfo, call);
411
412 // Fire off the notification
413 Notification notification = builder.build();
414
415 if (mDialerRingtoneManager.shouldPlayRingtone(callState, contactInfo.contactRingtoneUri)) {
416 notification.flags |= Notification.FLAG_INSISTENT;
417 notification.sound = contactInfo.contactRingtoneUri;
418 AudioAttributes.Builder audioAttributes = new AudioAttributes.Builder();
419 audioAttributes.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC);
420 audioAttributes.setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE);
421 notification.audioAttributes = audioAttributes.build();
422 if (mDialerRingtoneManager.shouldVibrate(mContext.getContentResolver())) {
423 notification.vibrate = VIBRATE_PATTERN;
424 }
425 }
426 if (mDialerRingtoneManager.shouldPlayCallWaitingTone(callState)) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700427 LogUtil.v("StatusBarNotifier.buildAndSendNotification", "playing call waiting tone");
Eric Erfanianccca3152017-02-22 16:32:36 -0800428 mDialerRingtoneManager.playCallWaitingTone();
429 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800430
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700431 LogUtil.i(
432 "StatusBarNotifier.buildAndSendNotification",
433 "displaying notification for " + notificationType);
434
Eric Erfanianccca3152017-02-22 16:32:36 -0800435 try {
Eric Erfanianea7890c2017-06-19 12:40:59 -0700436 mNotificationManager.notify(NOTIFICATION_TAG, NOTIFICATION_ID, notification);
Eric Erfanianccca3152017-02-22 16:32:36 -0800437 } catch (RuntimeException e) {
438 // TODO(b/34744003): Move the memory stats into silent feedback PSD.
439 ActivityManager activityManager = mContext.getSystemService(ActivityManager.class);
440 ActivityManager.MemoryInfo memoryInfo = new ActivityManager.MemoryInfo();
441 activityManager.getMemoryInfo(memoryInfo);
442 throw new RuntimeException(
443 String.format(
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700444 Locale.US,
Eric Erfanianccca3152017-02-22 16:32:36 -0800445 "Error displaying notification with photo type: %d (low memory? %b, availMem: %d)",
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700446 contactInfo.photoType,
447 memoryInfo.lowMemory,
448 memoryInfo.availMem),
Eric Erfanianccca3152017-02-22 16:32:36 -0800449 e);
450 }
451 call.getLatencyReport().onNotificationShown();
452 mCurrentNotification = notificationType;
453 }
454
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700455 @Nullable
456 @RequiresPermission(Manifest.permission.READ_PHONE_STATE)
457 private PhoneAccountHandle getAnyPhoneAccount() {
458 PhoneAccountHandle accountHandle;
459 TelecomManager telecomManager = mContext.getSystemService(TelecomManager.class);
460 accountHandle = telecomManager.getDefaultOutgoingPhoneAccount(PhoneAccount.SCHEME_TEL);
461 if (accountHandle == null) {
462 List<PhoneAccountHandle> accountHandles = telecomManager.getCallCapablePhoneAccounts();
463 if (!accountHandles.isEmpty()) {
464 accountHandle = accountHandles.get(0);
465 }
466 }
467 return accountHandle;
468 }
469
Eric Erfanianccca3152017-02-22 16:32:36 -0800470 private void createIncomingCallNotification(
471 DialerCall call, int state, Notification.Builder builder) {
472 setNotificationWhen(call, state, builder);
473
474 // Add hang up option for any active calls (active | onhold), outgoing calls (dialing).
475 if (state == DialerCall.State.ACTIVE
476 || state == DialerCall.State.ONHOLD
477 || DialerCall.State.isDialing(state)) {
478 addHangupAction(builder);
479 } else if (state == DialerCall.State.INCOMING || state == DialerCall.State.CALL_WAITING) {
480 addDismissAction(builder);
481 if (call.isVideoCall()) {
482 addVideoCallAction(builder);
483 } else {
484 addAnswerAction(builder);
485 }
486 }
487 }
488
489 /**
490 * Sets the notification's when section as needed. For active calls, this is explicitly set as the
491 * duration of the call. For all other states, the notification will automatically show the time
492 * at which the notification was created.
493 */
494 private void setNotificationWhen(DialerCall call, int state, Notification.Builder builder) {
495 if (state == DialerCall.State.ACTIVE) {
496 builder.setUsesChronometer(true);
497 builder.setWhen(call.getConnectTimeMillis());
498 } else {
499 builder.setUsesChronometer(false);
500 }
501 }
502
503 /**
504 * Checks the new notification data and compares it against any notification that we are already
505 * displaying. If the data is exactly the same, we return false so that we do not issue a new
506 * notification for the exact same data.
507 */
508 private boolean checkForChangeAndSaveData(
509 int icon,
510 String content,
511 Bitmap largeIcon,
512 String contentTitle,
513 int state,
514 int notificationType,
Eric Erfanian8369df02017-05-03 10:27:13 -0700515 Uri ringtone) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800516
517 // The two are different:
518 // if new title is not null, it should be different from saved version OR
519 // if new title is null, the saved version should not be null
520 final boolean contentTitleChanged =
521 (contentTitle != null && !contentTitle.equals(mSavedContentTitle))
522 || (contentTitle == null && mSavedContentTitle != null);
523
Eric Erfanian8369df02017-05-03 10:27:13 -0700524 boolean largeIconChanged =
525 mSavedLargeIcon == null ? largeIcon != null : !mSavedLargeIcon.sameAs(largeIcon);
526
Eric Erfanianccca3152017-02-22 16:32:36 -0800527 // any change means we are definitely updating
528 boolean retval =
529 (mSavedIcon != icon)
530 || !Objects.equals(mSavedContent, content)
531 || (mCallState != state)
Eric Erfanian8369df02017-05-03 10:27:13 -0700532 || largeIconChanged
Eric Erfanianccca3152017-02-22 16:32:36 -0800533 || contentTitleChanged
Eric Erfanian8369df02017-05-03 10:27:13 -0700534 || !Objects.equals(mRingtone, ringtone);
Eric Erfanianccca3152017-02-22 16:32:36 -0800535
536 // If we aren't showing a notification right now or the notification type is changing,
537 // definitely do an update.
538 if (mCurrentNotification != notificationType) {
539 if (mCurrentNotification == NOTIFICATION_NONE) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700540 LogUtil.d(
541 "StatusBarNotifier.checkForChangeAndSaveData", "showing notification for first time.");
Eric Erfanianccca3152017-02-22 16:32:36 -0800542 }
543 retval = true;
544 }
545
546 mSavedIcon = icon;
547 mSavedContent = content;
548 mCallState = state;
549 mSavedLargeIcon = largeIcon;
550 mSavedContentTitle = contentTitle;
551 mRingtone = ringtone;
552
553 if (retval) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700554 LogUtil.d(
555 "StatusBarNotifier.checkForChangeAndSaveData", "data changed. Showing notification");
Eric Erfanianccca3152017-02-22 16:32:36 -0800556 }
557
558 return retval;
559 }
560
561 /** Returns the main string to use in the notification. */
562 @VisibleForTesting
563 @Nullable
564 String getContentTitle(ContactCacheEntry contactInfo, DialerCall call) {
Eric Erfanian91ce7d22017-06-05 13:35:02 -0700565 if (call.isConferenceCall()) {
566 return CallerInfoUtils.getConferenceString(
567 mContext, call.hasProperty(Details.PROPERTY_GENERIC_CONFERENCE));
Eric Erfanianccca3152017-02-22 16:32:36 -0800568 }
569
570 String preferredName =
571 ContactDisplayUtils.getPreferredDisplayName(
572 contactInfo.namePrimary, contactInfo.nameAlternative, mContactsPreferences);
573 if (TextUtils.isEmpty(preferredName)) {
574 return TextUtils.isEmpty(contactInfo.number)
575 ? null
576 : BidiFormatter.getInstance()
577 .unicodeWrap(contactInfo.number, TextDirectionHeuristics.LTR);
578 }
579 return preferredName;
580 }
581
582 private void addPersonReference(
583 Notification.Builder builder, ContactCacheEntry contactInfo, DialerCall call) {
584 // Query {@link Contacts#CONTENT_LOOKUP_URI} directly with work lookup key is not allowed.
585 // So, do not pass {@link Contacts#CONTENT_LOOKUP_URI} to NotificationManager to avoid
586 // NotificationManager using it.
587 if (contactInfo.lookupUri != null && contactInfo.userType != ContactsUtils.USER_TYPE_WORK) {
588 builder.addPerson(contactInfo.lookupUri.toString());
589 } else if (!TextUtils.isEmpty(call.getNumber())) {
590 builder.addPerson(Uri.fromParts(PhoneAccount.SCHEME_TEL, call.getNumber(), null).toString());
591 }
592 }
593
594 /** Gets a large icon from the contact info object to display in the notification. */
Eric Erfanian83b20212017-05-31 08:53:10 -0700595 private static Bitmap getLargeIconToDisplay(
596 Context context, ContactCacheEntry contactInfo, DialerCall call) {
597 Resources resources = context.getResources();
Eric Erfanianccca3152017-02-22 16:32:36 -0800598 Bitmap largeIcon = null;
Eric Erfanianccca3152017-02-22 16:32:36 -0800599 if (contactInfo.photo != null && (contactInfo.photo instanceof BitmapDrawable)) {
600 largeIcon = ((BitmapDrawable) contactInfo.photo).getBitmap();
601 }
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700602 if (contactInfo.photo == null) {
Eric Erfanian83b20212017-05-31 08:53:10 -0700603 int width = (int) resources.getDimension(android.R.dimen.notification_large_icon_width);
604 int height = (int) resources.getDimension(android.R.dimen.notification_large_icon_height);
Eric Erfanian91ce7d22017-06-05 13:35:02 -0700605 @ContactType
606 int contactType =
607 LetterTileDrawable.getContactTypeFromPrimitives(
608 CallerInfoUtils.isVoiceMailNumber(context, call),
609 call.isSpam(),
610 contactInfo.isBusiness,
611 call.getNumberPresentation(),
612 call.isConferenceCall() && !call.hasProperty(Details.PROPERTY_GENERIC_CONFERENCE));
Eric Erfanian83b20212017-05-31 08:53:10 -0700613 LetterTileDrawable lettertile = new LetterTileDrawable(resources);
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700614
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700615 lettertile.setCanonicalDialerLetterTileDetails(
616 contactInfo.namePrimary == null ? contactInfo.number : contactInfo.namePrimary,
617 contactInfo.lookupKey,
618 LetterTileDrawable.SHAPE_CIRCLE,
619 contactType);
620 largeIcon = lettertile.getBitmap(width, height);
621 }
622
Eric Erfanianccca3152017-02-22 16:32:36 -0800623 if (call.isSpam()) {
Eric Erfanian83b20212017-05-31 08:53:10 -0700624 Drawable drawable = resources.getDrawable(R.drawable.blocked_contact, context.getTheme());
Eric Erfanianccca3152017-02-22 16:32:36 -0800625 largeIcon = DrawableConverter.drawableToBitmap(drawable);
626 }
627 return largeIcon;
628 }
629
630 private Bitmap getRoundedIcon(Bitmap bitmap) {
631 if (bitmap == null) {
632 return null;
633 }
634 final int height =
635 (int) mContext.getResources().getDimension(android.R.dimen.notification_large_icon_height);
636 final int width =
637 (int) mContext.getResources().getDimension(android.R.dimen.notification_large_icon_width);
638 return BitmapUtil.getRoundedBitmap(bitmap, width, height);
639 }
640
641 /**
642 * Returns the appropriate icon res Id to display based on the call for which we want to display
643 * information.
644 */
645 private int getIconToDisplay(DialerCall call) {
646 // Even if both lines are in use, we only show a single item in
647 // the expanded Notifications UI. It's labeled "Ongoing call"
648 // (or "On hold" if there's only one call, and it's on hold.)
649 // Also, we don't have room to display caller-id info from two
650 // different calls. So if both lines are in use, display info
651 // from the foreground call. And if there's a ringing call,
652 // display that regardless of the state of the other calls.
653 if (call.getState() == DialerCall.State.ONHOLD) {
654 return R.drawable.ic_phone_paused_white_24dp;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700655 } else if (call.getVideoTech().getSessionModificationState()
Eric Erfanian90508232017-03-24 09:31:16 -0700656 == SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST) {
Eric Erfaniand8046e52017-04-06 09:41:50 -0700657 return R.drawable.quantum_ic_videocam_white_24;
Eric Erfanian90508232017-03-24 09:31:16 -0700658 } else if (call.hasProperty(PROPERTY_HIGH_DEF_AUDIO)
659 && MotorolaUtils.shouldShowHdIconInNotification(mContext)) {
Eric Erfanianea7890c2017-06-19 12:40:59 -0700660 // Normally when a call is ongoing the status bar displays an icon of a phone. This is a
661 // helpful hint for users so they know how to get back to the call. For Sprint HD calls, we
662 // 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 -0700663 return R.drawable.ic_hd_call;
Eric Erfanianccca3152017-02-22 16:32:36 -0800664 }
Eric Erfanianea7890c2017-06-19 12:40:59 -0700665 // If ReturnToCall is enabled, use the static icon. The animated one will show in the bubble.
666 if (ReturnToCallController.isEnabled(mContext)) {
667 return R.drawable.quantum_ic_call_white_24;
668 } else {
669 return R.drawable.on_going_call;
670 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800671 }
672
673 /** Returns the message to use with the notification. */
674 private String getContentString(DialerCall call, @UserType long userType) {
675 boolean isIncomingOrWaiting =
676 call.getState() == DialerCall.State.INCOMING
677 || call.getState() == DialerCall.State.CALL_WAITING;
678
679 if (isIncomingOrWaiting
680 && call.getNumberPresentation() == TelecomManager.PRESENTATION_ALLOWED) {
681
682 if (!TextUtils.isEmpty(call.getChildNumber())) {
683 return mContext.getString(R.string.child_number, call.getChildNumber());
684 } else if (!TextUtils.isEmpty(call.getCallSubject()) && call.isCallSubjectSupported()) {
685 return call.getCallSubject();
686 }
687 }
688
689 int resId = R.string.notification_ongoing_call;
690 if (call.hasProperty(Details.PROPERTY_WIFI)) {
691 resId = R.string.notification_ongoing_call_wifi;
692 }
693
694 if (isIncomingOrWaiting) {
Eric Erfaniand8046e52017-04-06 09:41:50 -0700695 if (call.isSpam()) {
696 resId = R.string.notification_incoming_spam_call;
Eric Erfanian91ce7d22017-06-05 13:35:02 -0700697 } else if (shouldShowEnrichedCallNotification(call.getEnrichedCallSession())) {
698 resId = getECIncomingCallText(call.getEnrichedCallSession());
Eric Erfaniand8046e52017-04-06 09:41:50 -0700699 } else if (call.hasProperty(Details.PROPERTY_WIFI)) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800700 resId = R.string.notification_incoming_call_wifi;
701 } else {
Eric Erfaniand8046e52017-04-06 09:41:50 -0700702 resId = R.string.notification_incoming_call;
Eric Erfanianccca3152017-02-22 16:32:36 -0800703 }
704 } else if (call.getState() == DialerCall.State.ONHOLD) {
705 resId = R.string.notification_on_hold;
706 } else if (DialerCall.State.isDialing(call.getState())) {
707 resId = R.string.notification_dialing;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700708 } else if (call.getVideoTech().getSessionModificationState()
Eric Erfanian90508232017-03-24 09:31:16 -0700709 == SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800710 resId = R.string.notification_requesting_video_call;
711 }
712
713 // Is the call placed through work connection service.
714 boolean isWorkCall = call.hasProperty(PROPERTY_ENTERPRISE_CALL);
715 if (userType == ContactsUtils.USER_TYPE_WORK || isWorkCall) {
716 resId = getWorkStringFromPersonalString(resId);
717 }
718
719 return mContext.getString(resId);
720 }
721
Eric Erfanian91ce7d22017-06-05 13:35:02 -0700722 private boolean shouldShowEnrichedCallNotification(Session session) {
723 if (session == null) {
724 return false;
725 }
726 return session.getMultimediaData().hasData() || session.getMultimediaData().isImportant();
727 }
728
Eric Erfaniand8046e52017-04-06 09:41:50 -0700729 private int getECIncomingCallText(Session session) {
730 int resId;
731 MultimediaData data = session.getMultimediaData();
732 boolean hasImage = data.hasImageData();
733 boolean hasSubject = !TextUtils.isEmpty(data.getText());
734 boolean hasMap = data.getLocation() != null;
735 if (data.isImportant()) {
736 if (hasMap) {
737 if (hasImage) {
738 if (hasSubject) {
739 resId = R.string.important_notification_incoming_call_with_photo_message_location;
740 } else {
741 resId = R.string.important_notification_incoming_call_with_photo_location;
742 }
743 } else if (hasSubject) {
744 resId = R.string.important_notification_incoming_call_with_message_location;
745 } else {
746 resId = R.string.important_notification_incoming_call_with_location;
747 }
748 } else if (hasImage) {
749 if (hasSubject) {
750 resId = R.string.important_notification_incoming_call_with_photo_message;
751 } else {
752 resId = R.string.important_notification_incoming_call_with_photo;
753 }
Eric Erfanian91ce7d22017-06-05 13:35:02 -0700754 } else if (hasSubject) {
Eric Erfaniand8046e52017-04-06 09:41:50 -0700755 resId = R.string.important_notification_incoming_call_with_message;
Eric Erfanian91ce7d22017-06-05 13:35:02 -0700756 } else {
757 resId = R.string.important_notification_incoming_call;
Eric Erfaniand8046e52017-04-06 09:41:50 -0700758 }
759 if (mContext.getString(resId).length() > 50) {
760 resId = R.string.important_notification_incoming_call_attachments;
761 }
762 } else {
763 if (hasMap) {
764 if (hasImage) {
765 if (hasSubject) {
766 resId = R.string.notification_incoming_call_with_photo_message_location;
767 } else {
768 resId = R.string.notification_incoming_call_with_photo_location;
769 }
770 } else if (hasSubject) {
771 resId = R.string.notification_incoming_call_with_message_location;
772 } else {
773 resId = R.string.notification_incoming_call_with_location;
774 }
775 } else if (hasImage) {
776 if (hasSubject) {
777 resId = R.string.notification_incoming_call_with_photo_message;
778 } else {
779 resId = R.string.notification_incoming_call_with_photo;
780 }
781 } else {
782 resId = R.string.notification_incoming_call_with_message;
783 }
784 }
785 if (mContext.getString(resId).length() > 50) {
786 resId = R.string.notification_incoming_call_attachments;
787 }
788 return resId;
789 }
790
Eric Erfanianccca3152017-02-22 16:32:36 -0800791 /** Gets the most relevant call to display in the notification. */
792 private DialerCall getCallToShow(CallList callList) {
793 if (callList == null) {
794 return null;
795 }
796 DialerCall call = callList.getIncomingCall();
797 if (call == null) {
798 call = callList.getOutgoingCall();
799 }
800 if (call == null) {
801 call = callList.getVideoUpgradeRequestCall();
802 }
803 if (call == null) {
804 call = callList.getActiveOrBackgroundCall();
805 }
806 return call;
807 }
808
809 private Spannable getActionText(@StringRes int stringRes, @ColorRes int colorRes) {
810 Spannable spannable = new SpannableString(mContext.getText(stringRes));
811 if (VERSION.SDK_INT >= VERSION_CODES.N_MR1) {
812 // This will only work for cases where the Notification.Builder has a fullscreen intent set
813 // Notification.Builder that does not have a full screen intent will take the color of the
814 // app and the following leads to a no-op.
815 spannable.setSpan(
816 new ForegroundColorSpan(mContext.getColor(colorRes)), 0, spannable.length(), 0);
817 }
818 return spannable;
819 }
820
821 private void addAnswerAction(Notification.Builder builder) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700822 LogUtil.d(
823 "StatusBarNotifier.addAnswerAction",
824 "will show \"answer\" action in the incoming call Notification");
Eric Erfanianccca3152017-02-22 16:32:36 -0800825 PendingIntent answerVoicePendingIntent =
826 createNotificationPendingIntent(mContext, ACTION_ANSWER_VOICE_INCOMING_CALL);
827 builder.addAction(
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700828 new Notification.Action.Builder(
Eric Erfanianea7890c2017-06-19 12:40:59 -0700829 Icon.createWithResource(mContext, R.drawable.quantum_ic_call_white_24),
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700830 getActionText(
831 R.string.notification_action_answer, R.color.notification_action_accept),
832 answerVoicePendingIntent)
833 .build());
Eric Erfanianccca3152017-02-22 16:32:36 -0800834 }
835
836 private void addDismissAction(Notification.Builder builder) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700837 LogUtil.d(
838 "StatusBarNotifier.addDismissAction",
839 "will show \"decline\" action in the incoming call Notification");
Eric Erfanianccca3152017-02-22 16:32:36 -0800840 PendingIntent declinePendingIntent =
841 createNotificationPendingIntent(mContext, ACTION_DECLINE_INCOMING_CALL);
842 builder.addAction(
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700843 new Notification.Action.Builder(
Eric Erfaniand8046e52017-04-06 09:41:50 -0700844 Icon.createWithResource(mContext, R.drawable.quantum_ic_close_white_24),
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700845 getActionText(
846 R.string.notification_action_dismiss, R.color.notification_action_dismiss),
847 declinePendingIntent)
848 .build());
Eric Erfanianccca3152017-02-22 16:32:36 -0800849 }
850
851 private void addHangupAction(Notification.Builder builder) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700852 LogUtil.d(
853 "StatusBarNotifier.addHangupAction",
854 "will show \"hang-up\" action in the ongoing active call Notification");
Eric Erfanianccca3152017-02-22 16:32:36 -0800855 PendingIntent hangupPendingIntent =
856 createNotificationPendingIntent(mContext, ACTION_HANG_UP_ONGOING_CALL);
857 builder.addAction(
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700858 new Notification.Action.Builder(
859 Icon.createWithResource(mContext, R.drawable.ic_call_end_white_24dp),
Eric Erfanian10b34a52017-05-04 08:23:17 -0700860 mContext.getText(R.string.notification_action_end_call),
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700861 hangupPendingIntent)
862 .build());
Eric Erfanianccca3152017-02-22 16:32:36 -0800863 }
864
865 private void addVideoCallAction(Notification.Builder builder) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700866 LogUtil.i(
867 "StatusBarNotifier.addVideoCallAction",
868 "will show \"video\" action in the incoming call Notification");
Eric Erfanianccca3152017-02-22 16:32:36 -0800869 PendingIntent answerVideoPendingIntent =
870 createNotificationPendingIntent(mContext, ACTION_ANSWER_VIDEO_INCOMING_CALL);
871 builder.addAction(
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700872 new Notification.Action.Builder(
Eric Erfaniand8046e52017-04-06 09:41:50 -0700873 Icon.createWithResource(mContext, R.drawable.quantum_ic_videocam_white_24),
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700874 getActionText(
875 R.string.notification_action_answer_video,
876 R.color.notification_action_answer_video),
877 answerVideoPendingIntent)
878 .build());
Eric Erfanianccca3152017-02-22 16:32:36 -0800879 }
880
881 private void addAcceptUpgradeRequestAction(Notification.Builder builder) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700882 LogUtil.i(
883 "StatusBarNotifier.addAcceptUpgradeRequestAction",
884 "will show \"accept upgrade\" action in the incoming call Notification");
Eric Erfanianccca3152017-02-22 16:32:36 -0800885 PendingIntent acceptVideoPendingIntent =
886 createNotificationPendingIntent(mContext, ACTION_ACCEPT_VIDEO_UPGRADE_REQUEST);
887 builder.addAction(
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700888 new Notification.Action.Builder(
Eric Erfaniand8046e52017-04-06 09:41:50 -0700889 Icon.createWithResource(mContext, R.drawable.quantum_ic_videocam_white_24),
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700890 getActionText(
891 R.string.notification_action_accept, R.color.notification_action_accept),
892 acceptVideoPendingIntent)
893 .build());
Eric Erfanianccca3152017-02-22 16:32:36 -0800894 }
895
896 private void addDismissUpgradeRequestAction(Notification.Builder builder) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700897 LogUtil.i(
898 "StatusBarNotifier.addDismissUpgradeRequestAction",
899 "will show \"dismiss upgrade\" action in the incoming call Notification");
Eric Erfanianccca3152017-02-22 16:32:36 -0800900 PendingIntent declineVideoPendingIntent =
901 createNotificationPendingIntent(mContext, ACTION_DECLINE_VIDEO_UPGRADE_REQUEST);
902 builder.addAction(
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700903 new Notification.Action.Builder(
Eric Erfaniand8046e52017-04-06 09:41:50 -0700904 Icon.createWithResource(mContext, R.drawable.quantum_ic_videocam_white_24),
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700905 getActionText(
906 R.string.notification_action_dismiss, R.color.notification_action_dismiss),
907 declineVideoPendingIntent)
908 .build());
Eric Erfanianccca3152017-02-22 16:32:36 -0800909 }
910
911 /** Adds fullscreen intent to the builder. */
Eric Erfanian10b34a52017-05-04 08:23:17 -0700912 private void configureFullScreenIntent(Notification.Builder builder, PendingIntent intent) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800913 // Ok, we actually want to launch the incoming call
914 // UI at this point (in addition to simply posting a notification
915 // to the status bar). Setting fullScreenIntent will cause
916 // the InCallScreen to be launched immediately *unless* the
917 // current foreground activity is marked as "immersive".
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700918 LogUtil.d("StatusBarNotifier.configureFullScreenIntent", "setting fullScreenIntent: " + intent);
Eric Erfanianccca3152017-02-22 16:32:36 -0800919 builder.setFullScreenIntent(intent, true);
Eric Erfanianccca3152017-02-22 16:32:36 -0800920 }
921
922 private Notification.Builder getNotificationBuilder() {
923 final Notification.Builder builder = new Notification.Builder(mContext);
924 builder.setOngoing(true);
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700925 builder.setOnlyAlertOnce(true);
Eric Erfanian10b34a52017-05-04 08:23:17 -0700926 // This will be ignored on O+ and handled by the channel
Eric Erfanianea7890c2017-06-19 12:40:59 -0700927 // noinspection deprecation
Eric Erfanian10b34a52017-05-04 08:23:17 -0700928 builder.setPriority(Notification.PRIORITY_HIGH);
Eric Erfanianccca3152017-02-22 16:32:36 -0800929
930 return builder;
931 }
932
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700933 private PendingIntent createLaunchPendingIntent(boolean isFullScreen) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800934 Intent intent =
935 InCallActivity.getIntent(
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700936 mContext, false /* showDialpad */, false /* newOutgoingCall */, isFullScreen);
Eric Erfanianccca3152017-02-22 16:32:36 -0800937
938 int requestCode = PENDING_INTENT_REQUEST_CODE_NON_FULL_SCREEN;
939 if (isFullScreen) {
940 // Use a unique request code so that the pending intent isn't clobbered by the
941 // non-full screen pending intent.
942 requestCode = PENDING_INTENT_REQUEST_CODE_FULL_SCREEN;
943 }
944
945 // PendingIntent that can be used to launch the InCallActivity. The
946 // system fires off this intent if the user pulls down the windowshade
947 // and clicks the notification's expanded view. It's also used to
948 // launch the InCallActivity immediately when when there's an incoming
949 // call (see the "fullScreenIntent" field below).
950 return PendingIntent.getActivity(mContext, requestCode, intent, 0);
951 }
952
953 private void setStatusBarCallListener(StatusBarCallListener listener) {
954 if (mStatusBarCallListener != null) {
955 mStatusBarCallListener.cleanup();
956 }
957 mStatusBarCallListener = listener;
958 }
959
960 private class StatusBarCallListener implements DialerCallListener {
961
962 private DialerCall mDialerCall;
963
964 StatusBarCallListener(DialerCall dialerCall) {
965 mDialerCall = dialerCall;
966 mDialerCall.addListener(this);
967 }
968
969 void cleanup() {
970 mDialerCall.removeListener(this);
971 }
972
973 @Override
974 public void onDialerCallDisconnect() {}
975
976 @Override
977 public void onDialerCallUpdate() {
978 if (CallList.getInstance().getIncomingCall() == null) {
979 mDialerRingtoneManager.stopCallWaitingTone();
980 }
981 }
982
983 @Override
984 public void onDialerCallChildNumberChange() {}
985
986 @Override
987 public void onDialerCallLastForwardedNumberChange() {}
988
989 @Override
990 public void onDialerCallUpgradeToVideo() {}
991
992 @Override
993 public void onWiFiToLteHandover() {}
994
995 @Override
996 public void onHandoverToWifiFailure() {}
997
Eric Erfanianc857f902017-05-15 14:05:33 -0700998 @Override
999 public void onInternationalCallOnWifi() {}
1000
Eric Erfanian91ce7d22017-06-05 13:35:02 -07001001 @Override
1002 public void onEnrichedCallSessionUpdate() {}
1003
Eric Erfanianccca3152017-02-22 16:32:36 -08001004 /**
1005 * Responds to changes in the session modification state for the call by dismissing the status
1006 * bar notification as required.
1007 */
1008 @Override
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001009 public void onDialerCallSessionModificationStateChange() {
1010 if (mDialerCall.getVideoTech().getSessionModificationState()
Eric Erfanian90508232017-03-24 09:31:16 -07001011 == SessionModificationState.NO_REQUEST) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001012 cleanup();
1013 updateNotification(CallList.getInstance());
1014 }
1015 }
1016 }
1017}