blob: 53017a10eb1abad512f11c04a68717f21a8ce62f [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 Erfanianff2ad7f2017-07-27 10:45:54 -070069import com.android.dialer.configprovider.ConfigProviderBindings;
Eric Erfaniand8046e52017-04-06 09:41:50 -070070import com.android.dialer.enrichedcall.EnrichedCallManager;
71import com.android.dialer.enrichedcall.Session;
72import com.android.dialer.multimedia.MultimediaData;
Eric Erfanianea7890c2017-06-19 12:40:59 -070073import com.android.dialer.notification.NotificationChannelId;
Eric Erfanian90508232017-03-24 09:31:16 -070074import com.android.dialer.oem.MotorolaUtils;
Eric Erfanianccca3152017-02-22 16:32:36 -080075import com.android.dialer.util.DrawableConverter;
76import com.android.incallui.ContactInfoCache.ContactCacheEntry;
77import com.android.incallui.ContactInfoCache.ContactInfoCacheCallback;
78import com.android.incallui.InCallPresenter.InCallState;
79import com.android.incallui.async.PausableExecutorImpl;
80import com.android.incallui.call.CallList;
81import com.android.incallui.call.DialerCall;
Eric Erfanianccca3152017-02-22 16:32:36 -080082import com.android.incallui.call.DialerCallListener;
83import com.android.incallui.ringtone.DialerRingtoneManager;
84import com.android.incallui.ringtone.InCallTonePlayer;
85import com.android.incallui.ringtone.ToneGeneratorFactory;
Eric Erfanian90508232017-03-24 09:31:16 -070086import com.android.incallui.videotech.utils.SessionModificationState;
Eric Erfaniand5e47f62017-03-15 14:41:07 -070087import java.util.List;
88import java.util.Locale;
Eric Erfanianccca3152017-02-22 16:32:36 -080089import java.util.Objects;
90
91/** This class adds Notifications to the status bar for the in-call experience. */
Eric Erfaniand8046e52017-04-06 09:41:50 -070092public class StatusBarNotifier
93 implements InCallPresenter.InCallStateListener, EnrichedCallManager.StateChangedListener {
Eric Erfanianccca3152017-02-22 16:32:36 -080094
Eric Erfanianea7890c2017-06-19 12:40:59 -070095 private static final String NOTIFICATION_TAG = "STATUS_BAR_NOTIFIER";
96 private static final int NOTIFICATION_ID = 1;
97
Eric Erfanianccca3152017-02-22 16:32:36 -080098 // Notification types
99 // Indicates that no notification is currently showing.
100 private static final int NOTIFICATION_NONE = 0;
101 // Notification for an active call. This is non-interruptive, but cannot be dismissed.
Eric Erfanian10b34a52017-05-04 08:23:17 -0700102 private static final int NOTIFICATION_IN_CALL = 1;
Eric Erfanianccca3152017-02-22 16:32:36 -0800103 // Notification for incoming calls. This is interruptive and will show up as a HUN.
Eric Erfanian10b34a52017-05-04 08:23:17 -0700104 private static final int NOTIFICATION_INCOMING_CALL = 2;
105 // Notification for incoming calls in the case where there is already an active call.
106 // This is non-interruptive, but otherwise behaves the same as NOTIFICATION_INCOMING_CALL
107 private static final int NOTIFICATION_INCOMING_CALL_QUIET = 3;
Eric Erfanianccca3152017-02-22 16:32:36 -0800108
109 private static final int PENDING_INTENT_REQUEST_CODE_NON_FULL_SCREEN = 0;
110 private static final int PENDING_INTENT_REQUEST_CODE_FULL_SCREEN = 1;
111
112 private static final long[] VIBRATE_PATTERN = new long[] {0, 1000, 1000};
113
114 private final Context mContext;
115 private final ContactInfoCache mContactInfoCache;
116 private final NotificationManager mNotificationManager;
117 private final DialerRingtoneManager mDialerRingtoneManager;
118 @Nullable private ContactsPreferences mContactsPreferences;
119 private int mCurrentNotification = NOTIFICATION_NONE;
120 private int mCallState = DialerCall.State.INVALID;
121 private int mSavedIcon = 0;
122 private String mSavedContent = null;
123 private Bitmap mSavedLargeIcon;
124 private String mSavedContentTitle;
125 private Uri mRingtone;
126 private StatusBarCallListener mStatusBarCallListener;
127
Eric Erfaniand8046e52017-04-06 09:41:50 -0700128 public StatusBarNotifier(@NonNull Context context, @NonNull ContactInfoCache contactInfoCache) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800129 Objects.requireNonNull(context);
130 mContext = context;
131 mContactsPreferences = ContactsPreferencesFactory.newContactsPreferences(mContext);
132 mContactInfoCache = contactInfoCache;
133 mNotificationManager = context.getSystemService(NotificationManager.class);
134 mDialerRingtoneManager =
135 new DialerRingtoneManager(
136 new InCallTonePlayer(new ToneGeneratorFactory(), new PausableExecutorImpl()),
137 CallList.getInstance());
138 mCurrentNotification = NOTIFICATION_NONE;
139 }
140
141 /**
142 * Should only be called from a irrecoverable state where it is necessary to dismiss all
143 * notifications.
144 */
145 static void clearAllCallNotifications(Context backupContext) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700146 LogUtil.i(
147 "StatusBarNotifier.clearAllCallNotifications",
148 "something terrible happened, clear all InCall notifications");
Eric Erfanianccca3152017-02-22 16:32:36 -0800149
150 NotificationManager notificationManager =
151 backupContext.getSystemService(NotificationManager.class);
Eric Erfanianea7890c2017-06-19 12:40:59 -0700152 notificationManager.cancel(NOTIFICATION_TAG, NOTIFICATION_ID);
Eric Erfanianccca3152017-02-22 16:32:36 -0800153 }
154
155 private static int getWorkStringFromPersonalString(int resId) {
156 if (resId == R.string.notification_ongoing_call) {
157 return R.string.notification_ongoing_work_call;
158 } else if (resId == R.string.notification_ongoing_call_wifi) {
159 return R.string.notification_ongoing_work_call_wifi;
160 } else if (resId == R.string.notification_incoming_call_wifi) {
161 return R.string.notification_incoming_work_call_wifi;
162 } else if (resId == R.string.notification_incoming_call) {
163 return R.string.notification_incoming_work_call;
164 } else {
165 return resId;
166 }
167 }
168
169 /**
170 * Returns PendingIntent for answering a phone call. This will typically be used from Notification
171 * context.
172 */
173 private static PendingIntent createNotificationPendingIntent(Context context, String action) {
174 final Intent intent = new Intent(action, null, context, NotificationBroadcastReceiver.class);
175 return PendingIntent.getBroadcast(context, 0, intent, 0);
176 }
177
178 /** Creates notifications according to the state we receive from {@link InCallPresenter}. */
179 @Override
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700180 @RequiresPermission(Manifest.permission.READ_PHONE_STATE)
Eric Erfanianccca3152017-02-22 16:32:36 -0800181 public void onStateChange(InCallState oldState, InCallState newState, CallList callList) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700182 LogUtil.d("StatusBarNotifier.onStateChange", "%s->%s", oldState, newState);
Eric Erfanianccca3152017-02-22 16:32:36 -0800183 updateNotification(callList);
184 }
185
Eric Erfaniand8046e52017-04-06 09:41:50 -0700186 @Override
187 public void onEnrichedCallStateChanged() {
188 LogUtil.enterBlock("StatusBarNotifier.onEnrichedCallStateChanged");
189 updateNotification(CallList.getInstance());
190 }
191
Eric Erfanianccca3152017-02-22 16:32:36 -0800192 /**
193 * Updates the phone app's status bar notification *and* launches the incoming call UI in response
194 * to a new incoming call.
195 *
196 * <p>If an incoming call is ringing (or call-waiting), the notification will also include a
197 * "fullScreenIntent" that will cause the InCallScreen to be launched, unless the current
198 * foreground activity is marked as "immersive".
199 *
200 * <p>(This is the mechanism that actually brings up the incoming call UI when we receive a "new
201 * ringing connection" event from the telephony layer.)
202 *
203 * <p>Also note that this method is safe to call even if the phone isn't actually ringing (or,
204 * more likely, if an incoming call *was* ringing briefly but then disconnected). In that case,
205 * we'll simply update or cancel the in-call notification based on the current phone state.
206 *
207 * @see #updateInCallNotification(CallList)
208 */
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700209 @RequiresPermission(Manifest.permission.READ_PHONE_STATE)
Eric Erfaniand8046e52017-04-06 09:41:50 -0700210 public void updateNotification(CallList callList) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800211 updateInCallNotification(callList);
212 }
213
214 /**
215 * Take down the in-call notification.
216 *
217 * @see #updateInCallNotification(CallList)
218 */
219 private void cancelNotification() {
220 if (mStatusBarCallListener != null) {
221 setStatusBarCallListener(null);
222 }
223 if (mCurrentNotification != NOTIFICATION_NONE) {
Eric Erfanian8369df02017-05-03 10:27:13 -0700224 LogUtil.i("StatusBarNotifier.cancelNotification", "cancel");
Eric Erfanianea7890c2017-06-19 12:40:59 -0700225 mNotificationManager.cancel(NOTIFICATION_TAG, NOTIFICATION_ID);
Eric Erfanianccca3152017-02-22 16:32:36 -0800226 }
227 mCurrentNotification = NOTIFICATION_NONE;
228 }
229
230 /**
231 * Helper method for updateInCallNotification() and updateNotification(): Update the phone app's
232 * status bar notification based on the current telephony state, or cancels the notification if
233 * the phone is totally idle.
234 */
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700235 @RequiresPermission(Manifest.permission.READ_PHONE_STATE)
Eric Erfanianccca3152017-02-22 16:32:36 -0800236 private void updateInCallNotification(CallList callList) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700237 LogUtil.d("StatusBarNotifier.updateInCallNotification", "");
Eric Erfanianccca3152017-02-22 16:32:36 -0800238
239 final DialerCall call = getCallToShow(callList);
240
241 if (call != null) {
242 showNotification(callList, call);
243 } else {
244 cancelNotification();
245 }
246 }
247
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700248 @RequiresPermission(Manifest.permission.READ_PHONE_STATE)
Eric Erfanianccca3152017-02-22 16:32:36 -0800249 private void showNotification(final CallList callList, final DialerCall call) {
250 final boolean isIncoming =
251 (call.getState() == DialerCall.State.INCOMING
252 || call.getState() == DialerCall.State.CALL_WAITING);
253 setStatusBarCallListener(new StatusBarCallListener(call));
254
255 // we make a call to the contact info cache to query for supplemental data to what the
256 // call provides. This includes the contact name and photo.
257 // This callback will always get called immediately and synchronously with whatever data
258 // it has available, and may make a subsequent call later (same thread) if it had to
259 // call into the contacts provider for more data.
260 mContactInfoCache.findInfo(
261 call,
262 isIncoming,
263 new ContactInfoCacheCallback() {
264 @Override
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700265 @RequiresPermission(Manifest.permission.READ_PHONE_STATE)
Eric Erfanianccca3152017-02-22 16:32:36 -0800266 public void onContactInfoComplete(String callId, ContactCacheEntry entry) {
267 DialerCall call = callList.getCallById(callId);
268 if (call != null) {
269 call.getLogState().contactLookupResult = entry.contactLookupResult;
270 buildAndSendNotification(callList, call, entry);
271 }
272 }
273
274 @Override
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700275 @RequiresPermission(Manifest.permission.READ_PHONE_STATE)
Eric Erfanianccca3152017-02-22 16:32:36 -0800276 public void onImageLoadComplete(String callId, ContactCacheEntry entry) {
277 DialerCall call = callList.getCallById(callId);
278 if (call != null) {
279 buildAndSendNotification(callList, call, entry);
280 }
281 }
282 });
283 }
284
285 /** Sets up the main Ui for the notification */
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700286 @RequiresPermission(Manifest.permission.READ_PHONE_STATE)
Eric Erfanianccca3152017-02-22 16:32:36 -0800287 private void buildAndSendNotification(
288 CallList callList, DialerCall originalCall, ContactCacheEntry contactInfo) {
289 // This can get called to update an existing notification after contact information has come
290 // back. However, it can happen much later. Before we continue, we need to make sure that
291 // the call being passed in is still the one we want to show in the notification.
292 final DialerCall call = getCallToShow(callList);
293 if (call == null || !call.getId().equals(originalCall.getId())) {
294 return;
295 }
296
297 final int callState = call.getState();
298
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);
Eric Erfanianccca3152017-02-22 16:32:36 -0800302 final String content = getContentString(call, contactInfo.userType);
303 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 Erfanianff2ad7f2017-07-27 10:45:54 -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 }
328
329 if (!checkForChangeAndSaveData(
330 iconResId,
331 content,
332 largeIcon,
333 contentTitle,
334 callState,
335 notificationType,
Eric Erfanian8369df02017-05-03 10:27:13 -0700336 contactInfo.contactRingtoneUri)) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800337 return;
338 }
339
340 if (largeIcon != null) {
341 largeIcon = getRoundedIcon(largeIcon);
342 }
343
344 // This builder is used for the notification shown when the device is locked and the user
345 // has set their notification settings to 'hide sensitive content'
346 // {@see Notification.Builder#setPublicVersion}.
347 Notification.Builder publicBuilder = new Notification.Builder(mContext);
348 publicBuilder
349 .setSmallIcon(iconResId)
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700350 .setColor(mContext.getResources().getColor(R.color.dialer_theme_color, mContext.getTheme()))
Eric Erfanianccca3152017-02-22 16:32:36 -0800351 // Hide work call state for the lock screen notification
352 .setContentTitle(getContentString(call, ContactsUtils.USER_TYPE_CURRENT));
353 setNotificationWhen(call, callState, publicBuilder);
354
355 // Builder for the notification shown when the device is unlocked or the user has set their
356 // notification settings to 'show all notification content'.
357 final Notification.Builder builder = getNotificationBuilder();
358 builder.setPublicVersion(publicBuilder.build());
359
360 // Set up the main intent to send the user to the in-call screen
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700361 builder.setContentIntent(createLaunchPendingIntent(false /* isFullScreen */));
Eric Erfanianccca3152017-02-22 16:32:36 -0800362
363 // Set the intent as a full screen intent as well if a call is incoming
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700364 PhoneAccountHandle accountHandle = call.getAccountHandle();
365 if (accountHandle == null) {
366 accountHandle = getAnyPhoneAccount();
367 }
Eric Erfanian10b34a52017-05-04 08:23:17 -0700368
369 LogUtil.i("StatusBarNotifier.buildAndSendNotification", "notificationType=" + notificationType);
370 switch (notificationType) {
371 case NOTIFICATION_INCOMING_CALL:
Eric Erfanianea7890c2017-06-19 12:40:59 -0700372 if (BuildCompat.isAtLeastO()) {
373 builder.setChannelId(NotificationChannelId.INCOMING_CALL);
374 }
Eric Erfanian10b34a52017-05-04 08:23:17 -0700375 configureFullScreenIntent(builder, createLaunchPendingIntent(true /* isFullScreen */));
376 // Set the notification category and bump the priority for incoming calls
377 builder.setCategory(Notification.CATEGORY_CALL);
378 // This will be ignored on O+ and handled by the channel
Eric Erfanian10b34a52017-05-04 08:23:17 -0700379 builder.setPriority(Notification.PRIORITY_MAX);
380 if (mCurrentNotification != NOTIFICATION_INCOMING_CALL) {
381 LogUtil.i(
382 "StatusBarNotifier.buildAndSendNotification",
383 "Canceling old notification so this one can be noisy");
384 // Moving from a non-interuptive notification (or none) to a noisy one. Cancel the old
385 // notification (if there is one) so the fullScreenIntent or HUN will show
Eric Erfanianea7890c2017-06-19 12:40:59 -0700386 mNotificationManager.cancel(NOTIFICATION_TAG, NOTIFICATION_ID);
Eric Erfanian10b34a52017-05-04 08:23:17 -0700387 }
388 break;
389 case NOTIFICATION_INCOMING_CALL_QUIET:
Eric Erfanianea7890c2017-06-19 12:40:59 -0700390 if (BuildCompat.isAtLeastO()) {
391 builder.setChannelId(NotificationChannelId.ONGOING_CALL);
392 }
Eric Erfanian10b34a52017-05-04 08:23:17 -0700393 break;
394 case NOTIFICATION_IN_CALL:
Eric Erfanianea7890c2017-06-19 12:40:59 -0700395 if (BuildCompat.isAtLeastO()) {
396 publicBuilder.setColorized(true);
397 builder.setColorized(true);
398 builder.setChannelId(NotificationChannelId.ONGOING_CALL);
399 }
Eric Erfanian10b34a52017-05-04 08:23:17 -0700400 break;
Eric Erfanianccca3152017-02-22 16:32:36 -0800401 }
402
403 // Set the content
404 builder.setContentText(content);
405 builder.setSmallIcon(iconResId);
406 builder.setContentTitle(contentTitle);
407 builder.setLargeIcon(largeIcon);
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700408 builder.setColor(
409 mContext.getResources().getColor(R.color.dialer_theme_color, mContext.getTheme()));
Eric Erfanianccca3152017-02-22 16:32:36 -0800410
411 if (isVideoUpgradeRequest) {
412 builder.setUsesChronometer(false);
413 addDismissUpgradeRequestAction(builder);
414 addAcceptUpgradeRequestAction(builder);
415 } else {
416 createIncomingCallNotification(call, callState, builder);
417 }
418
419 addPersonReference(builder, contactInfo, call);
420
421 // Fire off the notification
422 Notification notification = builder.build();
423
424 if (mDialerRingtoneManager.shouldPlayRingtone(callState, contactInfo.contactRingtoneUri)) {
425 notification.flags |= Notification.FLAG_INSISTENT;
426 notification.sound = contactInfo.contactRingtoneUri;
427 AudioAttributes.Builder audioAttributes = new AudioAttributes.Builder();
428 audioAttributes.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC);
429 audioAttributes.setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE);
430 notification.audioAttributes = audioAttributes.build();
431 if (mDialerRingtoneManager.shouldVibrate(mContext.getContentResolver())) {
432 notification.vibrate = VIBRATE_PATTERN;
433 }
434 }
435 if (mDialerRingtoneManager.shouldPlayCallWaitingTone(callState)) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700436 LogUtil.v("StatusBarNotifier.buildAndSendNotification", "playing call waiting tone");
Eric Erfanianccca3152017-02-22 16:32:36 -0800437 mDialerRingtoneManager.playCallWaitingTone();
438 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800439
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700440 LogUtil.i(
441 "StatusBarNotifier.buildAndSendNotification",
442 "displaying notification for " + notificationType);
443
Eric Erfanianccca3152017-02-22 16:32:36 -0800444 try {
Eric Erfanianea7890c2017-06-19 12:40:59 -0700445 mNotificationManager.notify(NOTIFICATION_TAG, NOTIFICATION_ID, notification);
Eric Erfanianccca3152017-02-22 16:32:36 -0800446 } catch (RuntimeException e) {
447 // TODO(b/34744003): Move the memory stats into silent feedback PSD.
448 ActivityManager activityManager = mContext.getSystemService(ActivityManager.class);
449 ActivityManager.MemoryInfo memoryInfo = new ActivityManager.MemoryInfo();
450 activityManager.getMemoryInfo(memoryInfo);
451 throw new RuntimeException(
452 String.format(
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700453 Locale.US,
Eric Erfanianccca3152017-02-22 16:32:36 -0800454 "Error displaying notification with photo type: %d (low memory? %b, availMem: %d)",
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700455 contactInfo.photoType,
456 memoryInfo.lowMemory,
457 memoryInfo.availMem),
Eric Erfanianccca3152017-02-22 16:32:36 -0800458 e);
459 }
460 call.getLatencyReport().onNotificationShown();
461 mCurrentNotification = notificationType;
462 }
463
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700464 @Nullable
465 @RequiresPermission(Manifest.permission.READ_PHONE_STATE)
466 private PhoneAccountHandle getAnyPhoneAccount() {
467 PhoneAccountHandle accountHandle;
468 TelecomManager telecomManager = mContext.getSystemService(TelecomManager.class);
469 accountHandle = telecomManager.getDefaultOutgoingPhoneAccount(PhoneAccount.SCHEME_TEL);
470 if (accountHandle == null) {
471 List<PhoneAccountHandle> accountHandles = telecomManager.getCallCapablePhoneAccounts();
472 if (!accountHandles.isEmpty()) {
473 accountHandle = accountHandles.get(0);
474 }
475 }
476 return accountHandle;
477 }
478
Eric Erfanianccca3152017-02-22 16:32:36 -0800479 private void createIncomingCallNotification(
480 DialerCall call, int state, Notification.Builder builder) {
481 setNotificationWhen(call, state, builder);
482
483 // Add hang up option for any active calls (active | onhold), outgoing calls (dialing).
484 if (state == DialerCall.State.ACTIVE
485 || state == DialerCall.State.ONHOLD
486 || DialerCall.State.isDialing(state)) {
487 addHangupAction(builder);
488 } else if (state == DialerCall.State.INCOMING || state == DialerCall.State.CALL_WAITING) {
489 addDismissAction(builder);
490 if (call.isVideoCall()) {
491 addVideoCallAction(builder);
492 } else {
493 addAnswerAction(builder);
494 }
495 }
496 }
497
498 /**
499 * Sets the notification's when section as needed. For active calls, this is explicitly set as the
500 * duration of the call. For all other states, the notification will automatically show the time
501 * at which the notification was created.
502 */
503 private void setNotificationWhen(DialerCall call, int state, Notification.Builder builder) {
504 if (state == DialerCall.State.ACTIVE) {
505 builder.setUsesChronometer(true);
506 builder.setWhen(call.getConnectTimeMillis());
507 } else {
508 builder.setUsesChronometer(false);
509 }
510 }
511
512 /**
513 * Checks the new notification data and compares it against any notification that we are already
514 * displaying. If the data is exactly the same, we return false so that we do not issue a new
515 * notification for the exact same data.
516 */
517 private boolean checkForChangeAndSaveData(
518 int icon,
519 String content,
520 Bitmap largeIcon,
521 String contentTitle,
522 int state,
523 int notificationType,
Eric Erfanian8369df02017-05-03 10:27:13 -0700524 Uri ringtone) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800525
526 // The two are different:
527 // if new title is not null, it should be different from saved version OR
528 // if new title is null, the saved version should not be null
529 final boolean contentTitleChanged =
530 (contentTitle != null && !contentTitle.equals(mSavedContentTitle))
531 || (contentTitle == null && mSavedContentTitle != null);
532
Eric Erfanian8369df02017-05-03 10:27:13 -0700533 boolean largeIconChanged =
534 mSavedLargeIcon == null ? largeIcon != null : !mSavedLargeIcon.sameAs(largeIcon);
535
Eric Erfanianccca3152017-02-22 16:32:36 -0800536 // any change means we are definitely updating
537 boolean retval =
538 (mSavedIcon != icon)
539 || !Objects.equals(mSavedContent, content)
540 || (mCallState != state)
Eric Erfanian8369df02017-05-03 10:27:13 -0700541 || largeIconChanged
Eric Erfanianccca3152017-02-22 16:32:36 -0800542 || contentTitleChanged
Eric Erfanian8369df02017-05-03 10:27:13 -0700543 || !Objects.equals(mRingtone, ringtone);
Eric Erfanianccca3152017-02-22 16:32:36 -0800544
545 // If we aren't showing a notification right now or the notification type is changing,
546 // definitely do an update.
547 if (mCurrentNotification != notificationType) {
548 if (mCurrentNotification == NOTIFICATION_NONE) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700549 LogUtil.d(
550 "StatusBarNotifier.checkForChangeAndSaveData", "showing notification for first time.");
Eric Erfanianccca3152017-02-22 16:32:36 -0800551 }
552 retval = true;
553 }
554
555 mSavedIcon = icon;
556 mSavedContent = content;
557 mCallState = state;
558 mSavedLargeIcon = largeIcon;
559 mSavedContentTitle = contentTitle;
560 mRingtone = ringtone;
561
562 if (retval) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700563 LogUtil.d(
564 "StatusBarNotifier.checkForChangeAndSaveData", "data changed. Showing notification");
Eric Erfanianccca3152017-02-22 16:32:36 -0800565 }
566
567 return retval;
568 }
569
570 /** Returns the main string to use in the notification. */
571 @VisibleForTesting
572 @Nullable
573 String getContentTitle(ContactCacheEntry contactInfo, DialerCall call) {
Eric Erfanian91ce7d22017-06-05 13:35:02 -0700574 if (call.isConferenceCall()) {
575 return CallerInfoUtils.getConferenceString(
576 mContext, call.hasProperty(Details.PROPERTY_GENERIC_CONFERENCE));
Eric Erfanianccca3152017-02-22 16:32:36 -0800577 }
578
579 String preferredName =
580 ContactDisplayUtils.getPreferredDisplayName(
581 contactInfo.namePrimary, contactInfo.nameAlternative, mContactsPreferences);
582 if (TextUtils.isEmpty(preferredName)) {
583 return TextUtils.isEmpty(contactInfo.number)
584 ? null
585 : BidiFormatter.getInstance()
586 .unicodeWrap(contactInfo.number, TextDirectionHeuristics.LTR);
587 }
588 return preferredName;
589 }
590
591 private void addPersonReference(
592 Notification.Builder builder, ContactCacheEntry contactInfo, DialerCall call) {
593 // Query {@link Contacts#CONTENT_LOOKUP_URI} directly with work lookup key is not allowed.
594 // So, do not pass {@link Contacts#CONTENT_LOOKUP_URI} to NotificationManager to avoid
595 // NotificationManager using it.
596 if (contactInfo.lookupUri != null && contactInfo.userType != ContactsUtils.USER_TYPE_WORK) {
597 builder.addPerson(contactInfo.lookupUri.toString());
598 } else if (!TextUtils.isEmpty(call.getNumber())) {
599 builder.addPerson(Uri.fromParts(PhoneAccount.SCHEME_TEL, call.getNumber(), null).toString());
600 }
601 }
602
603 /** Gets a large icon from the contact info object to display in the notification. */
Eric Erfanian83b20212017-05-31 08:53:10 -0700604 private static Bitmap getLargeIconToDisplay(
605 Context context, ContactCacheEntry contactInfo, DialerCall call) {
606 Resources resources = context.getResources();
Eric Erfanianccca3152017-02-22 16:32:36 -0800607 Bitmap largeIcon = null;
Eric Erfanianccca3152017-02-22 16:32:36 -0800608 if (contactInfo.photo != null && (contactInfo.photo instanceof BitmapDrawable)) {
609 largeIcon = ((BitmapDrawable) contactInfo.photo).getBitmap();
610 }
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700611 if (contactInfo.photo == null) {
Eric Erfanian83b20212017-05-31 08:53:10 -0700612 int width = (int) resources.getDimension(android.R.dimen.notification_large_icon_width);
613 int height = (int) resources.getDimension(android.R.dimen.notification_large_icon_height);
Eric Erfanian91ce7d22017-06-05 13:35:02 -0700614 @ContactType
615 int contactType =
616 LetterTileDrawable.getContactTypeFromPrimitives(
617 CallerInfoUtils.isVoiceMailNumber(context, call),
618 call.isSpam(),
619 contactInfo.isBusiness,
620 call.getNumberPresentation(),
621 call.isConferenceCall() && !call.hasProperty(Details.PROPERTY_GENERIC_CONFERENCE));
Eric Erfanian83b20212017-05-31 08:53:10 -0700622 LetterTileDrawable lettertile = new LetterTileDrawable(resources);
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700623
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700624 lettertile.setCanonicalDialerLetterTileDetails(
625 contactInfo.namePrimary == null ? contactInfo.number : contactInfo.namePrimary,
626 contactInfo.lookupKey,
627 LetterTileDrawable.SHAPE_CIRCLE,
628 contactType);
629 largeIcon = lettertile.getBitmap(width, height);
630 }
631
Eric Erfanianccca3152017-02-22 16:32:36 -0800632 if (call.isSpam()) {
Eric Erfanian83b20212017-05-31 08:53:10 -0700633 Drawable drawable = resources.getDrawable(R.drawable.blocked_contact, context.getTheme());
Eric Erfanianccca3152017-02-22 16:32:36 -0800634 largeIcon = DrawableConverter.drawableToBitmap(drawable);
635 }
636 return largeIcon;
637 }
638
639 private Bitmap getRoundedIcon(Bitmap bitmap) {
640 if (bitmap == null) {
641 return null;
642 }
643 final int height =
644 (int) mContext.getResources().getDimension(android.R.dimen.notification_large_icon_height);
645 final int width =
646 (int) mContext.getResources().getDimension(android.R.dimen.notification_large_icon_width);
647 return BitmapUtil.getRoundedBitmap(bitmap, width, height);
648 }
649
650 /**
651 * Returns the appropriate icon res Id to display based on the call for which we want to display
652 * information.
653 */
654 private int getIconToDisplay(DialerCall call) {
655 // Even if both lines are in use, we only show a single item in
656 // the expanded Notifications UI. It's labeled "Ongoing call"
657 // (or "On hold" if there's only one call, and it's on hold.)
658 // Also, we don't have room to display caller-id info from two
659 // different calls. So if both lines are in use, display info
660 // from the foreground call. And if there's a ringing call,
661 // display that regardless of the state of the other calls.
662 if (call.getState() == DialerCall.State.ONHOLD) {
663 return R.drawable.ic_phone_paused_white_24dp;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700664 } else if (call.getVideoTech().getSessionModificationState()
Eric Erfanian90508232017-03-24 09:31:16 -0700665 == SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST) {
Eric Erfaniand8046e52017-04-06 09:41:50 -0700666 return R.drawable.quantum_ic_videocam_white_24;
Eric Erfanian90508232017-03-24 09:31:16 -0700667 } else if (call.hasProperty(PROPERTY_HIGH_DEF_AUDIO)
668 && MotorolaUtils.shouldShowHdIconInNotification(mContext)) {
Eric Erfanianea7890c2017-06-19 12:40:59 -0700669 // Normally when a call is ongoing the status bar displays an icon of a phone. This is a
670 // helpful hint for users so they know how to get back to the call. For Sprint HD calls, we
671 // 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 -0700672 return R.drawable.ic_hd_call;
Eric Erfanianccca3152017-02-22 16:32:36 -0800673 }
Eric Erfanianea7890c2017-06-19 12:40:59 -0700674 // If ReturnToCall is enabled, use the static icon. The animated one will show in the bubble.
675 if (ReturnToCallController.isEnabled(mContext)) {
676 return R.drawable.quantum_ic_call_white_24;
677 } else {
678 return R.drawable.on_going_call;
679 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800680 }
681
682 /** Returns the message to use with the notification. */
683 private String getContentString(DialerCall call, @UserType long userType) {
684 boolean isIncomingOrWaiting =
685 call.getState() == DialerCall.State.INCOMING
686 || call.getState() == DialerCall.State.CALL_WAITING;
687
688 if (isIncomingOrWaiting
689 && call.getNumberPresentation() == TelecomManager.PRESENTATION_ALLOWED) {
690
691 if (!TextUtils.isEmpty(call.getChildNumber())) {
692 return mContext.getString(R.string.child_number, call.getChildNumber());
693 } else if (!TextUtils.isEmpty(call.getCallSubject()) && call.isCallSubjectSupported()) {
694 return call.getCallSubject();
695 }
696 }
697
698 int resId = R.string.notification_ongoing_call;
699 if (call.hasProperty(Details.PROPERTY_WIFI)) {
700 resId = R.string.notification_ongoing_call_wifi;
701 }
702
703 if (isIncomingOrWaiting) {
Eric Erfaniand8046e52017-04-06 09:41:50 -0700704 if (call.isSpam()) {
705 resId = R.string.notification_incoming_spam_call;
Eric Erfanian91ce7d22017-06-05 13:35:02 -0700706 } else if (shouldShowEnrichedCallNotification(call.getEnrichedCallSession())) {
707 resId = getECIncomingCallText(call.getEnrichedCallSession());
Eric Erfaniand8046e52017-04-06 09:41:50 -0700708 } else if (call.hasProperty(Details.PROPERTY_WIFI)) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800709 resId = R.string.notification_incoming_call_wifi;
710 } else {
Eric Erfaniand8046e52017-04-06 09:41:50 -0700711 resId = R.string.notification_incoming_call;
Eric Erfanianccca3152017-02-22 16:32:36 -0800712 }
713 } else if (call.getState() == DialerCall.State.ONHOLD) {
714 resId = R.string.notification_on_hold;
715 } else if (DialerCall.State.isDialing(call.getState())) {
716 resId = R.string.notification_dialing;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700717 } else if (call.getVideoTech().getSessionModificationState()
Eric Erfanian90508232017-03-24 09:31:16 -0700718 == SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800719 resId = R.string.notification_requesting_video_call;
720 }
721
722 // Is the call placed through work connection service.
723 boolean isWorkCall = call.hasProperty(PROPERTY_ENTERPRISE_CALL);
724 if (userType == ContactsUtils.USER_TYPE_WORK || isWorkCall) {
725 resId = getWorkStringFromPersonalString(resId);
726 }
727
728 return mContext.getString(resId);
729 }
730
Eric Erfanian91ce7d22017-06-05 13:35:02 -0700731 private boolean shouldShowEnrichedCallNotification(Session session) {
732 if (session == null) {
733 return false;
734 }
735 return session.getMultimediaData().hasData() || session.getMultimediaData().isImportant();
736 }
737
Eric Erfaniand8046e52017-04-06 09:41:50 -0700738 private int getECIncomingCallText(Session session) {
739 int resId;
740 MultimediaData data = session.getMultimediaData();
741 boolean hasImage = data.hasImageData();
742 boolean hasSubject = !TextUtils.isEmpty(data.getText());
743 boolean hasMap = data.getLocation() != null;
744 if (data.isImportant()) {
745 if (hasMap) {
746 if (hasImage) {
747 if (hasSubject) {
748 resId = R.string.important_notification_incoming_call_with_photo_message_location;
749 } else {
750 resId = R.string.important_notification_incoming_call_with_photo_location;
751 }
752 } else if (hasSubject) {
753 resId = R.string.important_notification_incoming_call_with_message_location;
754 } else {
755 resId = R.string.important_notification_incoming_call_with_location;
756 }
757 } else if (hasImage) {
758 if (hasSubject) {
759 resId = R.string.important_notification_incoming_call_with_photo_message;
760 } else {
761 resId = R.string.important_notification_incoming_call_with_photo;
762 }
Eric Erfanian91ce7d22017-06-05 13:35:02 -0700763 } else if (hasSubject) {
Eric Erfaniand8046e52017-04-06 09:41:50 -0700764 resId = R.string.important_notification_incoming_call_with_message;
Eric Erfanian91ce7d22017-06-05 13:35:02 -0700765 } else {
766 resId = R.string.important_notification_incoming_call;
Eric Erfaniand8046e52017-04-06 09:41:50 -0700767 }
768 if (mContext.getString(resId).length() > 50) {
769 resId = R.string.important_notification_incoming_call_attachments;
770 }
771 } else {
772 if (hasMap) {
773 if (hasImage) {
774 if (hasSubject) {
775 resId = R.string.notification_incoming_call_with_photo_message_location;
776 } else {
777 resId = R.string.notification_incoming_call_with_photo_location;
778 }
779 } else if (hasSubject) {
780 resId = R.string.notification_incoming_call_with_message_location;
781 } else {
782 resId = R.string.notification_incoming_call_with_location;
783 }
784 } else if (hasImage) {
785 if (hasSubject) {
786 resId = R.string.notification_incoming_call_with_photo_message;
787 } else {
788 resId = R.string.notification_incoming_call_with_photo;
789 }
790 } else {
791 resId = R.string.notification_incoming_call_with_message;
792 }
793 }
794 if (mContext.getString(resId).length() > 50) {
795 resId = R.string.notification_incoming_call_attachments;
796 }
797 return resId;
798 }
799
Eric Erfanianccca3152017-02-22 16:32:36 -0800800 /** Gets the most relevant call to display in the notification. */
801 private DialerCall getCallToShow(CallList callList) {
802 if (callList == null) {
803 return null;
804 }
805 DialerCall call = callList.getIncomingCall();
806 if (call == null) {
807 call = callList.getOutgoingCall();
808 }
809 if (call == null) {
810 call = callList.getVideoUpgradeRequestCall();
811 }
812 if (call == null) {
813 call = callList.getActiveOrBackgroundCall();
814 }
815 return call;
816 }
817
818 private Spannable getActionText(@StringRes int stringRes, @ColorRes int colorRes) {
819 Spannable spannable = new SpannableString(mContext.getText(stringRes));
820 if (VERSION.SDK_INT >= VERSION_CODES.N_MR1) {
821 // This will only work for cases where the Notification.Builder has a fullscreen intent set
822 // Notification.Builder that does not have a full screen intent will take the color of the
823 // app and the following leads to a no-op.
824 spannable.setSpan(
825 new ForegroundColorSpan(mContext.getColor(colorRes)), 0, spannable.length(), 0);
826 }
827 return spannable;
828 }
829
830 private void addAnswerAction(Notification.Builder builder) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700831 LogUtil.d(
832 "StatusBarNotifier.addAnswerAction",
833 "will show \"answer\" action in the incoming call Notification");
Eric Erfanianccca3152017-02-22 16:32:36 -0800834 PendingIntent answerVoicePendingIntent =
835 createNotificationPendingIntent(mContext, ACTION_ANSWER_VOICE_INCOMING_CALL);
836 builder.addAction(
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700837 new Notification.Action.Builder(
Eric Erfanianea7890c2017-06-19 12:40:59 -0700838 Icon.createWithResource(mContext, R.drawable.quantum_ic_call_white_24),
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700839 getActionText(
840 R.string.notification_action_answer, R.color.notification_action_accept),
841 answerVoicePendingIntent)
842 .build());
Eric Erfanianccca3152017-02-22 16:32:36 -0800843 }
844
845 private void addDismissAction(Notification.Builder builder) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700846 LogUtil.d(
847 "StatusBarNotifier.addDismissAction",
848 "will show \"decline\" action in the incoming call Notification");
Eric Erfanianccca3152017-02-22 16:32:36 -0800849 PendingIntent declinePendingIntent =
850 createNotificationPendingIntent(mContext, ACTION_DECLINE_INCOMING_CALL);
851 builder.addAction(
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700852 new Notification.Action.Builder(
Eric Erfaniand8046e52017-04-06 09:41:50 -0700853 Icon.createWithResource(mContext, R.drawable.quantum_ic_close_white_24),
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700854 getActionText(
855 R.string.notification_action_dismiss, R.color.notification_action_dismiss),
856 declinePendingIntent)
857 .build());
Eric Erfanianccca3152017-02-22 16:32:36 -0800858 }
859
860 private void addHangupAction(Notification.Builder builder) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700861 LogUtil.d(
862 "StatusBarNotifier.addHangupAction",
863 "will show \"hang-up\" action in the ongoing active call Notification");
Eric Erfanianccca3152017-02-22 16:32:36 -0800864 PendingIntent hangupPendingIntent =
865 createNotificationPendingIntent(mContext, ACTION_HANG_UP_ONGOING_CALL);
866 builder.addAction(
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700867 new Notification.Action.Builder(
868 Icon.createWithResource(mContext, R.drawable.ic_call_end_white_24dp),
Eric Erfanian10b34a52017-05-04 08:23:17 -0700869 mContext.getText(R.string.notification_action_end_call),
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700870 hangupPendingIntent)
871 .build());
Eric Erfanianccca3152017-02-22 16:32:36 -0800872 }
873
874 private void addVideoCallAction(Notification.Builder builder) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700875 LogUtil.i(
876 "StatusBarNotifier.addVideoCallAction",
877 "will show \"video\" action in the incoming call Notification");
Eric Erfanianccca3152017-02-22 16:32:36 -0800878 PendingIntent answerVideoPendingIntent =
879 createNotificationPendingIntent(mContext, ACTION_ANSWER_VIDEO_INCOMING_CALL);
880 builder.addAction(
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700881 new Notification.Action.Builder(
Eric Erfaniand8046e52017-04-06 09:41:50 -0700882 Icon.createWithResource(mContext, R.drawable.quantum_ic_videocam_white_24),
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700883 getActionText(
884 R.string.notification_action_answer_video,
885 R.color.notification_action_answer_video),
886 answerVideoPendingIntent)
887 .build());
Eric Erfanianccca3152017-02-22 16:32:36 -0800888 }
889
890 private void addAcceptUpgradeRequestAction(Notification.Builder builder) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700891 LogUtil.i(
892 "StatusBarNotifier.addAcceptUpgradeRequestAction",
893 "will show \"accept upgrade\" action in the incoming call Notification");
Eric Erfanianccca3152017-02-22 16:32:36 -0800894 PendingIntent acceptVideoPendingIntent =
895 createNotificationPendingIntent(mContext, ACTION_ACCEPT_VIDEO_UPGRADE_REQUEST);
896 builder.addAction(
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700897 new Notification.Action.Builder(
Eric Erfaniand8046e52017-04-06 09:41:50 -0700898 Icon.createWithResource(mContext, R.drawable.quantum_ic_videocam_white_24),
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700899 getActionText(
900 R.string.notification_action_accept, R.color.notification_action_accept),
901 acceptVideoPendingIntent)
902 .build());
Eric Erfanianccca3152017-02-22 16:32:36 -0800903 }
904
905 private void addDismissUpgradeRequestAction(Notification.Builder builder) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700906 LogUtil.i(
907 "StatusBarNotifier.addDismissUpgradeRequestAction",
908 "will show \"dismiss upgrade\" action in the incoming call Notification");
Eric Erfanianccca3152017-02-22 16:32:36 -0800909 PendingIntent declineVideoPendingIntent =
910 createNotificationPendingIntent(mContext, ACTION_DECLINE_VIDEO_UPGRADE_REQUEST);
911 builder.addAction(
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700912 new Notification.Action.Builder(
Eric Erfaniand8046e52017-04-06 09:41:50 -0700913 Icon.createWithResource(mContext, R.drawable.quantum_ic_videocam_white_24),
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700914 getActionText(
915 R.string.notification_action_dismiss, R.color.notification_action_dismiss),
916 declineVideoPendingIntent)
917 .build());
Eric Erfanianccca3152017-02-22 16:32:36 -0800918 }
919
920 /** Adds fullscreen intent to the builder. */
Eric Erfanian10b34a52017-05-04 08:23:17 -0700921 private void configureFullScreenIntent(Notification.Builder builder, PendingIntent intent) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800922 // Ok, we actually want to launch the incoming call
923 // UI at this point (in addition to simply posting a notification
924 // to the status bar). Setting fullScreenIntent will cause
925 // the InCallScreen to be launched immediately *unless* the
926 // current foreground activity is marked as "immersive".
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700927 LogUtil.d("StatusBarNotifier.configureFullScreenIntent", "setting fullScreenIntent: " + intent);
Eric Erfanianccca3152017-02-22 16:32:36 -0800928 builder.setFullScreenIntent(intent, true);
Eric Erfanianccca3152017-02-22 16:32:36 -0800929 }
930
931 private Notification.Builder getNotificationBuilder() {
932 final Notification.Builder builder = new Notification.Builder(mContext);
933 builder.setOngoing(true);
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700934 builder.setOnlyAlertOnce(true);
Eric Erfanian10b34a52017-05-04 08:23:17 -0700935 // This will be ignored on O+ and handled by the channel
Eric Erfanianea7890c2017-06-19 12:40:59 -0700936 // noinspection deprecation
Eric Erfanian10b34a52017-05-04 08:23:17 -0700937 builder.setPriority(Notification.PRIORITY_HIGH);
Eric Erfanianccca3152017-02-22 16:32:36 -0800938
939 return builder;
940 }
941
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700942 private PendingIntent createLaunchPendingIntent(boolean isFullScreen) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800943 Intent intent =
944 InCallActivity.getIntent(
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700945 mContext, false /* showDialpad */, false /* newOutgoingCall */, isFullScreen);
Eric Erfanianccca3152017-02-22 16:32:36 -0800946
947 int requestCode = PENDING_INTENT_REQUEST_CODE_NON_FULL_SCREEN;
948 if (isFullScreen) {
949 // Use a unique request code so that the pending intent isn't clobbered by the
950 // non-full screen pending intent.
951 requestCode = PENDING_INTENT_REQUEST_CODE_FULL_SCREEN;
952 }
953
954 // PendingIntent that can be used to launch the InCallActivity. The
955 // system fires off this intent if the user pulls down the windowshade
956 // and clicks the notification's expanded view. It's also used to
957 // launch the InCallActivity immediately when when there's an incoming
958 // call (see the "fullScreenIntent" field below).
959 return PendingIntent.getActivity(mContext, requestCode, intent, 0);
960 }
961
962 private void setStatusBarCallListener(StatusBarCallListener listener) {
963 if (mStatusBarCallListener != null) {
964 mStatusBarCallListener.cleanup();
965 }
966 mStatusBarCallListener = listener;
967 }
968
969 private class StatusBarCallListener implements DialerCallListener {
970
971 private DialerCall mDialerCall;
972
973 StatusBarCallListener(DialerCall dialerCall) {
974 mDialerCall = dialerCall;
975 mDialerCall.addListener(this);
976 }
977
978 void cleanup() {
979 mDialerCall.removeListener(this);
980 }
981
982 @Override
983 public void onDialerCallDisconnect() {}
984
985 @Override
986 public void onDialerCallUpdate() {
987 if (CallList.getInstance().getIncomingCall() == null) {
988 mDialerRingtoneManager.stopCallWaitingTone();
989 }
990 }
991
992 @Override
993 public void onDialerCallChildNumberChange() {}
994
995 @Override
996 public void onDialerCallLastForwardedNumberChange() {}
997
998 @Override
999 public void onDialerCallUpgradeToVideo() {}
1000
1001 @Override
1002 public void onWiFiToLteHandover() {}
1003
1004 @Override
1005 public void onHandoverToWifiFailure() {}
1006
Eric Erfanianc857f902017-05-15 14:05:33 -07001007 @Override
1008 public void onInternationalCallOnWifi() {}
1009
Eric Erfanian91ce7d22017-06-05 13:35:02 -07001010 @Override
1011 public void onEnrichedCallSessionUpdate() {}
1012
Eric Erfanianccca3152017-02-22 16:32:36 -08001013 /**
1014 * Responds to changes in the session modification state for the call by dismissing the status
1015 * bar notification as required.
1016 */
1017 @Override
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001018 public void onDialerCallSessionModificationStateChange() {
1019 if (mDialerCall.getVideoTech().getSessionModificationState()
Eric Erfanian90508232017-03-24 09:31:16 -07001020 == SessionModificationState.NO_REQUEST) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001021 cleanup();
1022 updateNotification(CallList.getInstance());
1023 }
1024 }
1025 }
1026}