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