• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
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 
17 package com.android.incallui;
18 
19 import static com.android.incallui.NotificationBroadcastReceiver.*;
20 
21 import android.app.Notification;
22 import android.app.NotificationManager;
23 import android.app.PendingIntent;
24 import android.content.Context;
25 import android.content.Intent;
26 import android.graphics.Bitmap;
27 import android.graphics.BitmapFactory;
28 import android.graphics.drawable.BitmapDrawable;
29 import android.net.Uri;
30 import android.telecom.Call.Details;
31 import android.telecom.PhoneAccount;
32 import android.telecom.TelecomManager;
33 import android.text.BidiFormatter;
34 import android.text.TextDirectionHeuristics;
35 import android.text.TextUtils;
36 
37 import com.android.contacts.common.util.BitmapUtil;
38 import com.android.incallui.ContactInfoCache.ContactCacheEntry;
39 import com.android.incallui.ContactInfoCache.ContactInfoCacheCallback;
40 import com.android.incallui.InCallPresenter.InCallState;
41 
42 import com.google.common.base.Preconditions;
43 
44 import java.util.Objects;
45 
46 /**
47  * This class adds Notifications to the status bar for the in-call experience.
48  */
49 public class StatusBarNotifier implements InCallPresenter.InCallStateListener,
50         CallList.CallUpdateListener {
51 
52     // Notification types
53     // Indicates that no notification is currently showing.
54     private static final int NOTIFICATION_NONE = 0;
55     // Notification for an active call. This is non-interruptive, but cannot be dismissed.
56     private static final int NOTIFICATION_IN_CALL = 1;
57     // Notification for incoming calls. This is interruptive and will show up as a HUN.
58     private static final int NOTIFICATION_INCOMING_CALL = 2;
59 
60     private final Context mContext;
61     private final ContactInfoCache mContactInfoCache;
62     private final NotificationManager mNotificationManager;
63     private int mCurrentNotification = NOTIFICATION_NONE;
64     private int mCallState = Call.State.INVALID;
65     private int mSavedIcon = 0;
66     private String mSavedContent = null;
67     private Bitmap mSavedLargeIcon;
68     private String mSavedContentTitle;
69     private String mCallId = null;
70     private InCallState mInCallState;
71 
StatusBarNotifier(Context context, ContactInfoCache contactInfoCache)72     public StatusBarNotifier(Context context, ContactInfoCache contactInfoCache) {
73         Preconditions.checkNotNull(context);
74         mContext = context;
75         mContactInfoCache = contactInfoCache;
76         mNotificationManager =
77                 (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
78         mCurrentNotification = NOTIFICATION_NONE;
79     }
80 
81     /**
82      * Creates notifications according to the state we receive from {@link InCallPresenter}.
83      */
84     @Override
onStateChange(InCallState oldState, InCallState newState, CallList callList)85     public void onStateChange(InCallState oldState, InCallState newState, CallList callList) {
86         Log.d(this, "onStateChange");
87         mInCallState = newState;
88         updateNotification(newState, callList);
89     }
90 
91     /**
92      * Updates the phone app's status bar notification *and* launches the
93      * incoming call UI in response to a new incoming call.
94      *
95      * If an incoming call is ringing (or call-waiting), the notification
96      * will also include a "fullScreenIntent" that will cause the
97      * InCallScreen to be launched, unless the current foreground activity
98      * is marked as "immersive".
99      *
100      * (This is the mechanism that actually brings up the incoming call UI
101      * when we receive a "new ringing connection" event from the telephony
102      * layer.)
103      *
104      * Also note that this method is safe to call even if the phone isn't
105      * actually ringing (or, more likely, if an incoming call *was*
106      * ringing briefly but then disconnected).  In that case, we'll simply
107      * update or cancel the in-call notification based on the current
108      * phone state.
109      *
110      * @see #updateInCallNotification(InCallState,CallList)
111      */
updateNotification(InCallState state, CallList callList)112     public void updateNotification(InCallState state, CallList callList) {
113         updateInCallNotification(state, callList);
114     }
115 
116     /**
117      * Take down the in-call notification.
118      * @see #updateInCallNotification(InCallState,CallList)
119      */
cancelNotification()120     private void cancelNotification() {
121         if (!TextUtils.isEmpty(mCallId)) {
122             CallList.getInstance().removeCallUpdateListener(mCallId, this);
123             mCallId = null;
124         }
125         if (mCurrentNotification != NOTIFICATION_NONE) {
126             Log.d(this, "cancelInCall()...");
127             mNotificationManager.cancel(mCurrentNotification);
128         }
129         mCurrentNotification = NOTIFICATION_NONE;
130     }
131 
132     /**
133      * Should only be called from a irrecoverable state where it is necessary to dismiss all
134      * notifications.
135      */
clearAllCallNotifications(Context backupContext)136     static void clearAllCallNotifications(Context backupContext) {
137         Log.i(StatusBarNotifier.class.getSimpleName(),
138                 "Something terrible happened. Clear all InCall notifications");
139 
140         NotificationManager notificationManager =
141                 (NotificationManager) backupContext.getSystemService(Context.NOTIFICATION_SERVICE);
142         notificationManager.cancel(NOTIFICATION_IN_CALL);
143         notificationManager.cancel(NOTIFICATION_INCOMING_CALL);
144     }
145 
146     /**
147      * Helper method for updateInCallNotification() and
148      * updateNotification(): Update the phone app's
149      * status bar notification based on the current telephony state, or
150      * cancels the notification if the phone is totally idle.
151      */
updateInCallNotification(final InCallState state, CallList callList)152     private void updateInCallNotification(final InCallState state, CallList callList) {
153         Log.d(this, "updateInCallNotification...");
154 
155         final Call call = getCallToShow(callList);
156 
157         if (call != null) {
158             showNotification(call);
159         } else {
160             cancelNotification();
161         }
162     }
163 
showNotification(final Call call)164     private void showNotification(final Call call) {
165         final boolean isIncoming = (call.getState() == Call.State.INCOMING ||
166                 call.getState() == Call.State.CALL_WAITING);
167 
168         if (!TextUtils.isEmpty(mCallId)) {
169             CallList.getInstance().removeCallUpdateListener(mCallId, this);
170         }
171         mCallId = call.getId();
172         CallList.getInstance().addCallUpdateListener(call.getId(), this);
173 
174         // we make a call to the contact info cache to query for supplemental data to what the
175         // call provides.  This includes the contact name and photo.
176         // This callback will always get called immediately and synchronously with whatever data
177         // it has available, and may make a subsequent call later (same thread) if it had to
178         // call into the contacts provider for more data.
179         mContactInfoCache.findInfo(call, isIncoming, new ContactInfoCacheCallback() {
180             @Override
181             public void onContactInfoComplete(String callId, ContactCacheEntry entry) {
182                 Call call = CallList.getInstance().getCallById(callId);
183                 if (call != null) {
184                     buildAndSendNotification(call, entry);
185                 }
186             }
187 
188             @Override
189             public void onImageLoadComplete(String callId, ContactCacheEntry entry) {
190                 Call call = CallList.getInstance().getCallById(callId);
191                 if (call != null) {
192                     buildAndSendNotification(call, entry);
193                 }
194             }
195         });
196     }
197 
198     /**
199      * Sets up the main Ui for the notification
200      */
buildAndSendNotification(Call originalCall, ContactCacheEntry contactInfo)201     private void buildAndSendNotification(Call originalCall, ContactCacheEntry contactInfo) {
202 
203         // This can get called to update an existing notification after contact information has come
204         // back. However, it can happen much later. Before we continue, we need to make sure that
205         // the call being passed in is still the one we want to show in the notification.
206         final Call call = getCallToShow(CallList.getInstance());
207         if (call == null || !call.getId().equals(originalCall.getId())) {
208             return;
209         }
210 
211         final int state = call.getState();
212 
213         // Check if data has changed; if nothing is different, don't issue another notification.
214         final int iconResId = getIconToDisplay(call);
215         Bitmap largeIcon = getLargeIconToDisplay(contactInfo, call);
216         final String content = getContentString(call);
217         final String contentTitle = getContentTitle(contactInfo, call);
218 
219         final int notificationType;
220         if ((state == Call.State.INCOMING
221                 || state == Call.State.CALL_WAITING) &&
222                         !InCallPresenter.getInstance().isShowingInCallUi()) {
223             notificationType = NOTIFICATION_INCOMING_CALL;
224         } else {
225             notificationType = NOTIFICATION_IN_CALL;
226         }
227 
228         if (!checkForChangeAndSaveData(iconResId, content, largeIcon, contentTitle, state,
229                 notificationType)) {
230             return;
231         }
232 
233         if (largeIcon != null) {
234             largeIcon = getRoundedIcon(largeIcon);
235         }
236 
237         /*
238          * Nothing more to check...build and send it.
239          */
240         final Notification.Builder builder = getNotificationBuilder();
241 
242         // Set up the main intent to send the user to the in-call screen
243         final PendingIntent inCallPendingIntent = createLaunchPendingIntent();
244         builder.setContentIntent(inCallPendingIntent);
245 
246         // Set the intent as a full screen intent as well if a call is incoming
247         if (notificationType == NOTIFICATION_INCOMING_CALL) {
248             configureFullScreenIntent(builder, inCallPendingIntent, call);
249             // Set the notification category for incoming calls
250             builder.setCategory(Notification.CATEGORY_CALL);
251         }
252 
253         // Set the content
254         builder.setContentText(content);
255         builder.setSmallIcon(iconResId);
256         builder.setContentTitle(contentTitle);
257         builder.setLargeIcon(largeIcon);
258         builder.setColor(mContext.getResources().getColor(R.color.dialer_theme_color));
259 
260         final boolean isVideoUpgradeRequest = call.getSessionModificationState()
261                 == Call.SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST;
262         if (isVideoUpgradeRequest) {
263             builder.setUsesChronometer(false);
264             addDismissUpgradeRequestAction(builder);
265             addAcceptUpgradeRequestAction(builder);
266         } else {
267             createIncomingCallNotification(call, state, builder);
268         }
269 
270         addPersonReference(builder, contactInfo, call);
271 
272         /*
273          * Fire off the notification
274          */
275         Notification notification = builder.build();
276         if (mCurrentNotification != notificationType) {
277             Log.i(this, "Previous notification already showing - cancelling "
278                     + mCurrentNotification);
279             mNotificationManager.cancel(mCurrentNotification);
280         }
281         Log.i(this, "Displaying notification for " + notificationType);
282         mNotificationManager.notify(notificationType, notification);
283         mCurrentNotification = notificationType;
284     }
285 
createIncomingCallNotification( Call call, int state, Notification.Builder builder)286     private void createIncomingCallNotification(
287             Call call, int state, Notification.Builder builder) {
288         if (state == Call.State.ACTIVE) {
289             builder.setUsesChronometer(true);
290             builder.setWhen(call.getConnectTimeMillis());
291         } else {
292             builder.setUsesChronometer(false);
293         }
294 
295         // Add hang up option for any active calls (active | onhold), outgoing calls (dialing).
296         if (state == Call.State.ACTIVE ||
297                 state == Call.State.ONHOLD ||
298                 Call.State.isDialing(state)) {
299             addHangupAction(builder);
300         } else if (state == Call.State.INCOMING || state == Call.State.CALL_WAITING) {
301             addDismissAction(builder);
302             if (call.isVideoCall(mContext)) {
303                 addVoiceAction(builder);
304                 addVideoCallAction(builder);
305             } else {
306                 addAnswerAction(builder);
307             }
308         }
309     }
310 
311     /**
312      * Checks the new notification data and compares it against any notification that we
313      * are already displaying. If the data is exactly the same, we return false so that
314      * we do not issue a new notification for the exact same data.
315      */
checkForChangeAndSaveData(int icon, String content, Bitmap largeIcon, String contentTitle, int state, int notificationType)316     private boolean checkForChangeAndSaveData(int icon, String content, Bitmap largeIcon,
317             String contentTitle, int state, int notificationType) {
318 
319         // The two are different:
320         // if new title is not null, it should be different from saved version OR
321         // if new title is null, the saved version should not be null
322         final boolean contentTitleChanged =
323                 (contentTitle != null && !contentTitle.equals(mSavedContentTitle)) ||
324                 (contentTitle == null && mSavedContentTitle != null);
325 
326         // any change means we are definitely updating
327         boolean retval = (mSavedIcon != icon) || !Objects.equals(mSavedContent, content) ||
328                 (mCallState != state) || (mSavedLargeIcon != largeIcon) ||
329                 contentTitleChanged;
330 
331         // If we aren't showing a notification right now or the notification type is changing,
332         // definitely do an update.
333         if (mCurrentNotification != notificationType) {
334             if (mCurrentNotification == NOTIFICATION_NONE) {
335                 Log.d(this, "Showing notification for first time.");
336             }
337             retval = true;
338         }
339 
340         mSavedIcon = icon;
341         mSavedContent = content;
342         mCallState = state;
343         mSavedLargeIcon = largeIcon;
344         mSavedContentTitle = contentTitle;
345 
346         if (retval) {
347             Log.d(this, "Data changed.  Showing notification");
348         }
349 
350         return retval;
351     }
352 
353     /**
354      * Returns the main string to use in the notification.
355      */
getContentTitle(ContactCacheEntry contactInfo, Call call)356     private String getContentTitle(ContactCacheEntry contactInfo, Call call) {
357         if (call.isConferenceCall() && !call.hasProperty(Details.PROPERTY_GENERIC_CONFERENCE)) {
358             return mContext.getResources().getString(R.string.card_title_conf_call);
359         }
360         if (TextUtils.isEmpty(contactInfo.name)) {
361             return TextUtils.isEmpty(contactInfo.number) ? null
362                     : BidiFormatter.getInstance().unicodeWrap(
363                             contactInfo.number.toString(), TextDirectionHeuristics.LTR);
364         }
365 
366         return contactInfo.name;
367     }
368 
addPersonReference(Notification.Builder builder, ContactCacheEntry contactInfo, Call call)369     private void addPersonReference(Notification.Builder builder, ContactCacheEntry contactInfo,
370             Call call) {
371         if (contactInfo.lookupUri != null) {
372             builder.addPerson(contactInfo.lookupUri.toString());
373         } else if (!TextUtils.isEmpty(call.getNumber())) {
374             builder.addPerson(Uri.fromParts(PhoneAccount.SCHEME_TEL,
375                             call.getNumber(), null).toString());
376         }
377     }
378 
379     /**
380      * Gets a large icon from the contact info object to display in the notification.
381      */
getLargeIconToDisplay(ContactCacheEntry contactInfo, Call call)382     private Bitmap getLargeIconToDisplay(ContactCacheEntry contactInfo, Call call) {
383         Bitmap largeIcon = null;
384         if (call.isConferenceCall() && !call.hasProperty(Details.PROPERTY_GENERIC_CONFERENCE)) {
385             largeIcon = BitmapFactory.decodeResource(mContext.getResources(),
386                     R.drawable.img_conference);
387         }
388         if (contactInfo.photo != null && (contactInfo.photo instanceof BitmapDrawable)) {
389             largeIcon = ((BitmapDrawable) contactInfo.photo).getBitmap();
390         }
391         return largeIcon;
392     }
393 
getRoundedIcon(Bitmap bitmap)394     private Bitmap getRoundedIcon(Bitmap bitmap) {
395         if (bitmap == null) {
396             return null;
397         }
398         final int height = (int) mContext.getResources().getDimension(
399                 android.R.dimen.notification_large_icon_height);
400         final int width = (int) mContext.getResources().getDimension(
401                 android.R.dimen.notification_large_icon_width);
402         return BitmapUtil.getRoundedBitmap(bitmap, width, height);
403     }
404 
405     /**
406      * Returns the appropriate icon res Id to display based on the call for which
407      * we want to display information.
408      */
getIconToDisplay(Call call)409     private int getIconToDisplay(Call call) {
410         // Even if both lines are in use, we only show a single item in
411         // the expanded Notifications UI.  It's labeled "Ongoing call"
412         // (or "On hold" if there's only one call, and it's on hold.)
413         // Also, we don't have room to display caller-id info from two
414         // different calls.  So if both lines are in use, display info
415         // from the foreground call.  And if there's a ringing call,
416         // display that regardless of the state of the other calls.
417         if (call.getState() == Call.State.ONHOLD) {
418             return R.drawable.ic_phone_paused_white_24dp;
419         } else if (call.getSessionModificationState()
420                 == Call.SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST) {
421             return R.drawable.ic_videocam;
422         }
423         return R.drawable.ic_call_white_24dp;
424     }
425 
426     /**
427      * Returns the message to use with the notification.
428      */
getContentString(Call call)429     private String getContentString(Call call) {
430         boolean isIncomingOrWaiting = call.getState() == Call.State.INCOMING ||
431                 call.getState() == Call.State.CALL_WAITING;
432 
433         if (isIncomingOrWaiting &&
434                 call.getNumberPresentation() == TelecomManager.PRESENTATION_ALLOWED) {
435 
436             if (!TextUtils.isEmpty(call.getChildNumber())) {
437                 return mContext.getString(R.string.child_number, call.getChildNumber());
438             } else if (!TextUtils.isEmpty(call.getCallSubject()) && call.isCallSubjectSupported()) {
439                 return call.getCallSubject();
440             }
441         }
442 
443         int resId = R.string.notification_ongoing_call;
444         if (call.hasProperty(Details.PROPERTY_WIFI)) {
445             resId = R.string.notification_ongoing_call_wifi;
446         }
447 
448         if (isIncomingOrWaiting) {
449             if (call.hasProperty(Details.PROPERTY_WIFI)) {
450                 resId = R.string.notification_incoming_call_wifi;
451             } else {
452                 resId = R.string.notification_incoming_call;
453             }
454         } else if (call.getState() == Call.State.ONHOLD) {
455             resId = R.string.notification_on_hold;
456         } else if (Call.State.isDialing(call.getState())) {
457             resId = R.string.notification_dialing;
458         } else if (call.getSessionModificationState()
459                 == Call.SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST) {
460             resId = R.string.notification_requesting_video_call;
461         }
462 
463         return mContext.getString(resId);
464     }
465 
466     /**
467      * Gets the most relevant call to display in the notification.
468      */
getCallToShow(CallList callList)469     private Call getCallToShow(CallList callList) {
470         if (callList == null) {
471             return null;
472         }
473         Call call = callList.getIncomingCall();
474         if (call == null) {
475             call = callList.getOutgoingCall();
476         }
477         if (call == null) {
478             call = callList.getVideoUpgradeRequestCall();
479         }
480         if (call == null) {
481             call = callList.getActiveOrBackgroundCall();
482         }
483         return call;
484     }
485 
addAnswerAction(Notification.Builder builder)486     private void addAnswerAction(Notification.Builder builder) {
487         Log.d(this, "Will show \"answer\" action in the incoming call Notification");
488 
489         PendingIntent answerVoicePendingIntent = createNotificationPendingIntent(
490                 mContext, ACTION_ANSWER_VOICE_INCOMING_CALL);
491         builder.addAction(R.drawable.ic_call_white_24dp,
492                 mContext.getText(R.string.notification_action_answer),
493                 answerVoicePendingIntent);
494     }
495 
addDismissAction(Notification.Builder builder)496     private void addDismissAction(Notification.Builder builder) {
497         Log.d(this, "Will show \"dismiss\" action in the incoming call Notification");
498 
499         PendingIntent declinePendingIntent =
500                 createNotificationPendingIntent(mContext, ACTION_DECLINE_INCOMING_CALL);
501         builder.addAction(R.drawable.ic_close_dk,
502                 mContext.getText(R.string.notification_action_dismiss),
503                 declinePendingIntent);
504     }
505 
addHangupAction(Notification.Builder builder)506     private void addHangupAction(Notification.Builder builder) {
507         Log.d(this, "Will show \"hang-up\" action in the ongoing active call Notification");
508 
509         PendingIntent hangupPendingIntent =
510                 createNotificationPendingIntent(mContext, ACTION_HANG_UP_ONGOING_CALL);
511         builder.addAction(R.drawable.ic_call_end_white_24dp,
512                 mContext.getText(R.string.notification_action_end_call),
513                 hangupPendingIntent);
514     }
515 
addVideoCallAction(Notification.Builder builder)516     private void addVideoCallAction(Notification.Builder builder) {
517         Log.i(this, "Will show \"video\" action in the incoming call Notification");
518 
519         PendingIntent answerVideoPendingIntent = createNotificationPendingIntent(
520                 mContext, ACTION_ANSWER_VIDEO_INCOMING_CALL);
521         builder.addAction(R.drawable.ic_videocam,
522                 mContext.getText(R.string.notification_action_answer_video),
523                 answerVideoPendingIntent);
524     }
525 
addVoiceAction(Notification.Builder builder)526     private void addVoiceAction(Notification.Builder builder) {
527         Log.d(this, "Will show \"voice\" action in the incoming call Notification");
528 
529         PendingIntent answerVoicePendingIntent = createNotificationPendingIntent(
530                 mContext, ACTION_ANSWER_VOICE_INCOMING_CALL);
531         builder.addAction(R.drawable.ic_call_white_24dp,
532                 mContext.getText(R.string.notification_action_answer_voice),
533                 answerVoicePendingIntent);
534     }
535 
addAcceptUpgradeRequestAction(Notification.Builder builder)536     private void addAcceptUpgradeRequestAction(Notification.Builder builder) {
537         Log.i(this, "Will show \"accept upgrade\" action in the incoming call Notification");
538 
539         PendingIntent acceptVideoPendingIntent = createNotificationPendingIntent(
540                 mContext, ACTION_ACCEPT_VIDEO_UPGRADE_REQUEST);
541         builder.addAction(0, mContext.getText(R.string.notification_action_accept),
542                 acceptVideoPendingIntent);
543     }
544 
addDismissUpgradeRequestAction(Notification.Builder builder)545     private void addDismissUpgradeRequestAction(Notification.Builder builder) {
546         Log.i(this, "Will show \"dismiss upgrade\" action in the incoming call Notification");
547 
548         PendingIntent declineVideoPendingIntent = createNotificationPendingIntent(
549                 mContext, ACTION_DECLINE_VIDEO_UPGRADE_REQUEST);
550         builder.addAction(0, mContext.getText(R.string.notification_action_dismiss),
551                 declineVideoPendingIntent);
552     }
553 
554     /**
555      * Adds fullscreen intent to the builder.
556      */
configureFullScreenIntent(Notification.Builder builder, PendingIntent intent, Call call)557     private void configureFullScreenIntent(Notification.Builder builder, PendingIntent intent,
558             Call call) {
559         // Ok, we actually want to launch the incoming call
560         // UI at this point (in addition to simply posting a notification
561         // to the status bar).  Setting fullScreenIntent will cause
562         // the InCallScreen to be launched immediately *unless* the
563         // current foreground activity is marked as "immersive".
564         Log.d(this, "- Setting fullScreenIntent: " + intent);
565         builder.setFullScreenIntent(intent, true);
566 
567         // Ugly hack alert:
568         //
569         // The NotificationManager has the (undocumented) behavior
570         // that it will *ignore* the fullScreenIntent field if you
571         // post a new Notification that matches the ID of one that's
572         // already active.  Unfortunately this is exactly what happens
573         // when you get an incoming call-waiting call:  the
574         // "ongoing call" notification is already visible, so the
575         // InCallScreen won't get launched in this case!
576         // (The result: if you bail out of the in-call UI while on a
577         // call and then get a call-waiting call, the incoming call UI
578         // won't come up automatically.)
579         //
580         // The workaround is to just notice this exact case (this is a
581         // call-waiting call *and* the InCallScreen is not in the
582         // foreground) and manually cancel the in-call notification
583         // before (re)posting it.
584         //
585         // TODO: there should be a cleaner way of avoiding this
586         // problem (see discussion in bug 3184149.)
587 
588         // If a call is onhold during an incoming call, the call actually comes in as
589         // INCOMING.  For that case *and* traditional call-waiting, we want to
590         // cancel the notification.
591         boolean isCallWaiting = (call.getState() == Call.State.CALL_WAITING ||
592                 (call.getState() == Call.State.INCOMING &&
593                         CallList.getInstance().getBackgroundCall() != null));
594 
595         if (isCallWaiting) {
596             Log.i(this, "updateInCallNotification: call-waiting! force relaunch...");
597             // Cancel the IN_CALL_NOTIFICATION immediately before
598             // (re)posting it; this seems to force the
599             // NotificationManager to launch the fullScreenIntent.
600             mNotificationManager.cancel(NOTIFICATION_IN_CALL);
601         }
602     }
603 
getNotificationBuilder()604     private Notification.Builder getNotificationBuilder() {
605         final Notification.Builder builder = new Notification.Builder(mContext);
606         builder.setOngoing(true);
607 
608         // Make the notification prioritized over the other normal notifications.
609         builder.setPriority(Notification.PRIORITY_HIGH);
610 
611         return builder;
612     }
613 
createLaunchPendingIntent()614     private PendingIntent createLaunchPendingIntent() {
615 
616         final Intent intent = InCallPresenter.getInstance().getInCallIntent(
617                 false /* showDialpad */, false /* newOutgoingCall */);
618 
619         // PendingIntent that can be used to launch the InCallActivity.  The
620         // system fires off this intent if the user pulls down the windowshade
621         // and clicks the notification's expanded view.  It's also used to
622         // launch the InCallActivity immediately when when there's an incoming
623         // call (see the "fullScreenIntent" field below).
624         PendingIntent inCallPendingIntent = PendingIntent.getActivity(mContext, 0, intent, 0);
625 
626         return inCallPendingIntent;
627     }
628 
629     /**
630      * Returns PendingIntent for answering a phone call. This will typically be used from
631      * Notification context.
632      */
createNotificationPendingIntent(Context context, String action)633     private static PendingIntent createNotificationPendingIntent(Context context, String action) {
634         final Intent intent = new Intent(action, null,
635                 context, NotificationBroadcastReceiver.class);
636         return PendingIntent.getBroadcast(context, 0, intent, 0);
637     }
638 
639     @Override
onCallChanged(Call call)640     public void onCallChanged(Call call) {
641         // no-op
642     }
643 
644     /**
645      * Responds to changes in the session modification state for the call by dismissing the
646      * status bar notification as required.
647      *
648      * @param sessionModificationState The new session modification state.
649      */
650     @Override
onSessionModificationStateChange(int sessionModificationState)651     public void onSessionModificationStateChange(int sessionModificationState) {
652         if (sessionModificationState == Call.SessionModificationState.NO_REQUEST) {
653             if (mCallId != null) {
654                 CallList.getInstance().removeCallUpdateListener(mCallId, this);
655             }
656 
657             updateNotification(mInCallState, CallList.getInstance());
658         }
659     }
660 
661     @Override
onLastForwardedNumberChange()662     public void onLastForwardedNumberChange() {
663         // no-op
664     }
665 
666     @Override
onChildNumberChange()667     public void onChildNumberChange() {
668         // no-op
669     }
670 }
671