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