StatusBarNotifier.java 27 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
/*
 * Copyright (C) 2013 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.incallui;

19
import android.net.Uri;
20

21 22 23 24 25 26 27
import com.google.common.base.Preconditions;

import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
28
import android.graphics.Bitmap;
29
import android.graphics.BitmapFactory;
30
import android.graphics.drawable.BitmapDrawable;
31 32
import android.os.Handler;
import android.os.Message;
Tyler Gunn's avatar
Tyler Gunn committed
33
import android.telecom.PhoneAccount;
34 35
import android.text.BidiFormatter;
import android.text.TextDirectionHeuristics;
36
import android.text.TextUtils;
37

38
import com.android.contacts.common.util.BitmapUtil;
39 40
import com.android.incallui.ContactInfoCache.ContactCacheEntry;
import com.android.incallui.ContactInfoCache.ContactInfoCacheCallback;
41
import com.android.incallui.InCallApp.NotificationBroadcastReceiver;
42 43 44 45 46
import com.android.incallui.InCallPresenter.InCallState;

/**
 * This class adds Notifications to the status bar for the in-call experience.
 */
47
public class StatusBarNotifier implements InCallPresenter.InCallStateListener {
48 49 50
    // notification types
    private static final int IN_CALL_NOTIFICATION = 1;

51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93
    private static final long IN_CALL_TIMEOUT = 1000L;

    private interface NotificationTimer {
        enum State {
            SCHEDULED,
            FIRED,
            CLEAR;
        }
        State getState();
        void schedule();
        void clear();
    }

    private NotificationTimer mNotificationTimer = new NotificationTimer() {
        private final Handler mHandler = new Handler(new Handler.Callback() {
            public boolean handleMessage(Message m) {
                fire();
                return true;
            }
        });
        private State mState = State.CLEAR;
        public State getState() { return mState; }
        public void schedule() {
            if (mState == State.CLEAR) {
                Log.d(this, "updateInCallNotification: timer scheduled");
                mHandler.sendEmptyMessageDelayed(0, IN_CALL_TIMEOUT);
                mState = State.SCHEDULED;
            }
        }
        public void clear() {
            Log.d(this, "updateInCallNotification: timer cleared");
            mHandler.removeMessages(0);
            mState = State.CLEAR;
        }
        private void fire() {
            Log.d(this, "updateInCallNotification: timer fired");
            mState = State.FIRED;
            updateNotification(
                    InCallPresenter.getInstance().getInCallState(),
                    InCallPresenter.getInstance().getCallList());
        }
    };

94
    private final Context mContext;
95
    private final ContactInfoCache mContactInfoCache;
96
    private final NotificationManager mNotificationManager;
97
    private boolean mIsShowingNotification = false;
98
    private int mCallState = Call.State.INVALID;
99 100
    private int mSavedIcon = 0;
    private int mSavedContent = 0;
101 102
    private Bitmap mSavedLargeIcon;
    private String mSavedContentTitle;
103

104
    public StatusBarNotifier(Context context, ContactInfoCache contactInfoCache) {
105 106 107
        Preconditions.checkNotNull(context);

        mContext = context;
108
        mContactInfoCache = contactInfoCache;
109 110 111 112 113 114 115 116
        mNotificationManager =
                (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
    }

    /**
     * Creates notifications according to the state we receive from {@link InCallPresenter}.
     */
    @Override
117
    public void onStateChange(InCallState oldState, InCallState newState, CallList callList) {
118
        Log.d(this, "onStateChange");
119

120
        updateNotification(newState, callList);
121 122
    }

123 124 125 126 127 128
    /**
     * Updates the phone app's status bar notification *and* launches the
     * incoming call UI in response to a new incoming call.
     *
     * If an incoming call is ringing (or call-waiting), the notification
     * will also include a "fullScreenIntent" that will cause the
129 130
     * InCallScreen to be launched, unless the current foreground activity
     * is marked as "immersive".
131 132 133 134 135 136 137 138 139 140 141
     *
     * (This is the mechanism that actually brings up the incoming call UI
     * when we receive a "new ringing connection" event from the telephony
     * layer.)
     *
     * Also note that this method is safe to call even if the phone isn't
     * actually ringing (or, more likely, if an incoming call *was*
     * ringing briefly but then disconnected).  In that case, we'll simply
     * update or cancel the in-call notification based on the current
     * phone state.
     *
142
     * @see #updateInCallNotification(InCallState,CallList)
143
     */
144 145
    public void updateNotification(InCallState state, CallList callList) {
        updateInCallNotification(state, callList);
146 147 148 149
    }

    /**
     * Take down the in-call notification.
150
     * @see #updateInCallNotification(InCallState,CallList)
151 152
     */
    private void cancelInCall() {
153 154 155 156
        if (mIsShowingNotification) {
            Log.d(this, "cancelInCall()...");
            mNotificationManager.cancel(IN_CALL_NOTIFICATION);
        }
157
        mIsShowingNotification = false;
158 159
    }

160 161 162 163 164 165 166 167 168
    /* package */ static void clearInCallNotification(Context backupContext) {
        Log.i(StatusBarNotifier.class.getSimpleName(),
                "Something terrible happened. Clear all InCall notifications");

        NotificationManager notificationManager =
                (NotificationManager) backupContext.getSystemService(Context.NOTIFICATION_SERVICE);
        notificationManager.cancel(IN_CALL_NOTIFICATION);
    }

169 170
    /**
     * Helper method for updateInCallNotification() and
171
     * updateNotification(): Update the phone app's
172 173 174
     * status bar notification based on the current telephony state, or
     * cancels the notification if the phone is totally idle.
     */
175 176
    private void updateInCallNotification(final InCallState state, CallList callList) {
        Log.d(this, "updateInCallNotification...");
177

178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209
        Call call = getCallToShow(callList);

        // Whether we have an outgoing call but the incall UI has yet to show up.
        // Since we don't normally show a notification while the incall screen is
        // in the foreground, if we show the outgoing notification before the activity
        // comes up the user will see it flash on and off on an outgoing call. We therefore
        // do not show the notification for outgoing calls before the activity has started.
        boolean isOutgoingWithoutIncallUi =
                state == InCallState.OUTGOING &&
                !InCallPresenter.getInstance().isActivityPreviouslyStarted();

        // Whether to show a notification immediately.
        boolean showNotificationNow =

                // We can still be in the INCALL state when a call is disconnected (in order to show
                // the "Call ended" screen. So check that we have an active connection too.
                (call != null) &&

                // We show a notification iff there is an active call.
                state.isConnectingOrConnected() &&

                // If the UI is already showing, then for most cases we do not want to show
                // a notification since that would be redundant, unless it is an incoming call,
                // in which case the notification is actually an important alert.
                (!InCallPresenter.getInstance().isShowingInCallUi() || state.isIncoming()) &&

                // If we have an outgoing call with no UI but the timer has fired, we show
                // a notification anyway.
                (!isOutgoingWithoutIncallUi ||
                        mNotificationTimer.getState() == NotificationTimer.State.FIRED);

        if (showNotificationNow) {
210
            showNotification(call);
211
        } else {
212
            cancelInCall();
213 214 215 216 217 218 219 220 221
            if (isOutgoingWithoutIncallUi &&
                    mNotificationTimer.getState() == NotificationTimer.State.CLEAR) {
                mNotificationTimer.schedule();
            }
        }

        // If we see a UI, or we are done with calls for now, reset to ground state.
        if (InCallPresenter.getInstance().isShowingInCallUi() || call == null) {
            mNotificationTimer.clear();
222
        }
223
    }
224

225
    private void showNotification(final Call call) {
226 227 228
        final boolean isIncoming = (call.getState() == Call.State.INCOMING ||
                call.getState() == Call.State.CALL_WAITING);

229 230 231 232 233
        // we make a call to the contact info cache to query for supplemental data to what the
        // call provides.  This includes the contact name and photo.
        // This callback will always get called immediately and synchronously with whatever data
        // it has available, and may make a subsequent call later (same thread) if it had to
        // call into the contacts provider for more data.
234
        mContactInfoCache.findInfo(call, isIncoming, new ContactInfoCacheCallback() {
235 236 237 238 239
            @Override
            public void onContactInfoComplete(String callId, ContactCacheEntry entry) {
                Call call = CallList.getInstance().getCallById(callId);
                if (call != null) {
                    buildAndSendNotification(call, entry);
240
                }
241
            }
242

243 244 245 246 247 248 249 250
            @Override
            public void onImageLoadComplete(String callId, ContactCacheEntry entry) {
                Call call = CallList.getInstance().getCallById(callId);
                if (call != null) {
                    buildAndSendNotification(call, entry);
                }
            }
        });
251 252 253 254 255
    }

    /**
     * Sets up the main Ui for the notification
     */
256
    private void buildAndSendNotification(Call originalCall, ContactCacheEntry contactInfo) {
257 258 259 260 261

        // This can get called to update an existing notification after contact information has come
        // back. However, it can happen much later. Before we continue, we need to make sure that
        // the call being passed in is still the one we want to show in the notification.
        final Call call = getCallToShow(CallList.getInstance());
262
        if (call == null || !call.getId().equals(originalCall.getId())) {
263 264
            return;
        }
265

266
        final int state = call.getState();
267 268

        // Check if data has changed; if nothing is different, don't issue another notification.
269
        final int iconResId = getIconToDisplay(call);
270
        final Bitmap largeIcon = getLargeIconToDisplay(contactInfo, call);
271
        final int contentResId = getContentString(call);
272
        final String contentTitle = getContentTitle(contactInfo, call);
273

274
        if (!checkForChangeAndSaveData(iconResId, contentResId, largeIcon, contentTitle, state)) {
275 276 277 278
            return;
        }

        /*
279
         * Nothing more to check...build and send it.
280
         */
281 282 283 284 285 286
        final Notification.Builder builder = getNotificationBuilder();

        // Set up the main intent to send the user to the in-call screen
        final PendingIntent inCallPendingIntent = createLaunchPendingIntent();
        builder.setContentIntent(inCallPendingIntent);

287
        // Set the intent as a full screen intent as well if a call is incoming
288 289
        if ((state == Call.State.INCOMING || state == Call.State.CALL_WAITING) &&
                !InCallPresenter.getInstance().isShowingInCallUi()) {
290
            configureFullScreenIntent(builder, inCallPendingIntent, call);
291 292
            // Set the notification category for incoming calls
            builder.setCategory(Notification.CATEGORY_CALL);
293 294
        }

295
        // Set the content
296 297
        builder.setContentText(mContext.getString(contentResId));
        builder.setSmallIcon(iconResId);
298 299
        builder.setContentTitle(contentTitle);
        builder.setLargeIcon(largeIcon);
300
        builder.setColor(mContext.getResources().getColor(R.color.dialer_theme_color));
301

302 303
        final boolean isVideoUpgradeRequest = call.getSessionModificationState()
                == Call.SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST;
304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324
        if (isVideoUpgradeRequest) {
            builder.setUsesChronometer(false);
            addDismissUpgradeRequestAction(builder);
            addAcceptUpgradeRequestAction(builder);
        } else {
            createIncomingCallNotification(call, state, builder);
        }

        addPersonReference(builder, contactInfo, call);

        /*
         * Fire off the notification
         */
        Notification notification = builder.build();
        Log.d(this, "Notifying IN_CALL_NOTIFICATION: " + notification);
        mNotificationManager.notify(IN_CALL_NOTIFICATION, notification);
        mIsShowingNotification = true;
    }

    private void createIncomingCallNotification(
            Call call, int state, Notification.Builder builder) {
325
        if (state == Call.State.ACTIVE) {
Santos Cordon's avatar
Santos Cordon committed
326
            builder.setUsesChronometer(true);
327
            builder.setWhen(call.getConnectTimeMillis());
Santos Cordon's avatar
Santos Cordon committed
328 329 330 331
        } else {
            builder.setUsesChronometer(false);
        }

332 333
        // Add hang up option for any active calls (active | onhold), outgoing calls (dialing).
        if (state == Call.State.ACTIVE ||
334 335
                state == Call.State.ONHOLD ||
                Call.State.isDialing(state)) {
336
            addHangupAction(builder);
337 338
        } else if (state == Call.State.INCOMING || state == Call.State.CALL_WAITING) {
            addDismissAction(builder);
339
            if (call.isVideoCall(mContext)) {
340 341 342 343 344
                addVoiceAction(builder);
                addVideoCallAction(builder);
            } else {
                addAnswerAction(builder);
            }
345
        }
346 347 348
    }

    /**
349 350 351
     * Checks the new notification data and compares it against any notification that we
     * are already displaying. If the data is exactly the same, we return false so that
     * we do not issue a new notification for the exact same data.
352
     */
353
    private boolean checkForChangeAndSaveData(int icon, int content, Bitmap largeIcon,
354
            String contentTitle, int state) {
355 356 357 358 359 360 361 362 363

        // The two are different:
        // if new title is not null, it should be different from saved version OR
        // if new title is null, the saved version should not be null
        final boolean contentTitleChanged =
                (contentTitle != null && !contentTitle.equals(mSavedContentTitle)) ||
                (contentTitle == null && mSavedContentTitle != null);

        // any change means we are definitely updating
364
        boolean retval = (mSavedIcon != icon) || (mSavedContent != content) ||
365
                (mCallState != state) || (mSavedLargeIcon != largeIcon) ||
366
                contentTitleChanged;
367 368 369

        // If we aren't showing a notification right now, definitely start showing one.
        if (!mIsShowingNotification) {
Chiao Cheng's avatar
Chiao Cheng committed
370
            Log.d(this, "Showing notification for first time.");
371
            retval = true;
372 373
        }

374 375
        mSavedIcon = icon;
        mSavedContent = content;
376
        mCallState = state;
377 378
        mSavedLargeIcon = largeIcon;
        mSavedContentTitle = contentTitle;
379 380

        if (retval) {
Chiao Cheng's avatar
Chiao Cheng committed
381
            Log.d(this, "Data changed.  Showing notification");
382 383 384
        }

        return retval;
385 386
    }

387 388 389
    /**
     * Returns the main string to use in the notification.
     */
390
    private String getContentTitle(ContactCacheEntry contactInfo, Call call) {
391 392
        if (call.isConferenceCall()
                && !call.can(android.telecom.Call.Details.CAPABILITY_GENERIC_CONFERENCE)) {
393 394
            return mContext.getResources().getString(R.string.card_title_conf_call);
        }
395
        if (TextUtils.isEmpty(contactInfo.name)) {
396 397 398
            return TextUtils.isEmpty(contactInfo.number) ? null
                    : BidiFormatter.getInstance().unicodeWrap(
                            contactInfo.number.toString(), TextDirectionHeuristics.LTR);
399 400 401 402 403
        }

        return contactInfo.name;
    }

404 405 406 407 408
    private void addPersonReference(Notification.Builder builder, ContactCacheEntry contactInfo,
            Call call) {
        if (contactInfo.lookupUri != null) {
            builder.addPerson(contactInfo.lookupUri.toString());
        } else if (!TextUtils.isEmpty(call.getNumber())) {
409 410
            builder.addPerson(Uri.fromParts(PhoneAccount.SCHEME_TEL,
                            call.getNumber(), null).toString());
411 412 413
        }
    }

414 415 416
    /**
     * Gets a large icon from the contact info object to display in the notification.
     */
417
    private Bitmap getLargeIconToDisplay(ContactCacheEntry contactInfo, Call call) {
418
        Bitmap largeIcon = null;
419 420
        if (call.isConferenceCall()
                && !call.can(android.telecom.Call.Details.CAPABILITY_GENERIC_CONFERENCE)) {
421
            largeIcon = BitmapFactory.decodeResource(mContext.getResources(),
422
                    R.drawable.img_conference);
423
        }
424
        if (contactInfo.photo != null && (contactInfo.photo instanceof BitmapDrawable)) {
425
            largeIcon = ((BitmapDrawable) contactInfo.photo).getBitmap();
426 427
        }

428 429 430 431 432
        if (largeIcon != null) {
            final int height = (int) mContext.getResources().getDimension(
                    android.R.dimen.notification_large_icon_height);
            final int width = (int) mContext.getResources().getDimension(
                    android.R.dimen.notification_large_icon_width);
433
            largeIcon = BitmapUtil.getRoundedBitmap(largeIcon, width, height);
434 435
        }
        return largeIcon;
436 437
    }

438 439 440 441 442 443 444 445 446 447 448 449 450
    /**
     * Returns the appropriate icon res Id to display based on the call for which
     * we want to display information.
     */
    private int getIconToDisplay(Call call) {
        // Even if both lines are in use, we only show a single item in
        // the expanded Notifications UI.  It's labeled "Ongoing call"
        // (or "On hold" if there's only one call, and it's on hold.)
        // Also, we don't have room to display caller-id info from two
        // different calls.  So if both lines are in use, display info
        // from the foreground call.  And if there's a ringing call,
        // display that regardless of the state of the other calls.
        if (call.getState() == Call.State.ONHOLD) {
451
            return R.drawable.ic_phone_paused_white_24dp;
452 453 454
        } else if (call.getSessionModificationState()
                == Call.SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST) {
            return R.drawable.ic_videocam;
455
        }
456
        return R.drawable.ic_call_white_24dp;
457 458 459
    }

    /**
460
     * Returns the message to use with the notification.
461 462 463 464
     */
    private int getContentString(Call call) {
        int resId = R.string.notification_ongoing_call;

465
        if (call.getState() == Call.State.INCOMING || call.getState() == Call.State.CALL_WAITING) {
466 467 468
            resId = R.string.notification_incoming_call;
        } else if (call.getState() == Call.State.ONHOLD) {
            resId = R.string.notification_on_hold;
469
        } else if (Call.State.isDialing(call.getState())) {
470
            resId = R.string.notification_dialing;
471 472 473
        } else if (call.getSessionModificationState()
                == Call.SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST) {
            resId = R.string.notification_requesting_video_call;
474 475 476 477 478 479 480 481 482
        }

        return resId;
    }

    /**
     * Gets the most relevant call to display in the notification.
     */
    private Call getCallToShow(CallList callList) {
483 484 485
        if (callList == null) {
            return null;
        }
486 487 488 489
        Call call = callList.getIncomingCall();
        if (call == null) {
            call = callList.getOutgoingCall();
        }
490 491 492
        if (call == null) {
            call = callList.getVideoUpgradeRequestCall();
        }
493 494 495 496 497 498
        if (call == null) {
            call = callList.getActiveOrBackgroundCall();
        }
        return call;
    }

499 500 501 502 503
    private void addAnswerAction(Notification.Builder builder) {
        Log.i(this, "Will show \"answer\" action in the incoming call Notification");

        PendingIntent answerVoicePendingIntent = createNotificationPendingIntent(
                mContext, InCallApp.ACTION_ANSWER_VOICE_INCOMING_CALL);
504
        builder.addAction(R.drawable.ic_call_white_24dp,
505
                mContext.getText(R.string.notification_action_answer),
506 507 508 509 510 511 512 513 514 515 516 517 518
                answerVoicePendingIntent);
    }

    private void addDismissAction(Notification.Builder builder) {
        Log.i(this, "Will show \"dismiss\" action in the incoming call Notification");

        PendingIntent declinePendingIntent =
                createNotificationPendingIntent(mContext, InCallApp.ACTION_DECLINE_INCOMING_CALL);
        builder.addAction(R.drawable.ic_close_dk,
                mContext.getText(R.string.notification_action_dismiss),
                declinePendingIntent);
    }

519
    private void addHangupAction(Notification.Builder builder) {
Chiao Cheng's avatar
Chiao Cheng committed
520
        Log.i(this, "Will show \"hang-up\" action in the ongoing active call Notification");
521

522 523
        PendingIntent hangupPendingIntent =
                createNotificationPendingIntent(mContext, InCallApp.ACTION_HANG_UP_ONGOING_CALL);
524
        builder.addAction(R.drawable.ic_call_end_white_24dp,
525
                mContext.getText(R.string.notification_action_end_call),
526
                hangupPendingIntent);
527 528
    }

529 530
    private void addVideoCallAction(Notification.Builder builder) {
        Log.i(this, "Will show \"video\" action in the incoming call Notification");
531

532 533 534 535 536
        PendingIntent answerVideoPendingIntent = createNotificationPendingIntent(
                mContext, InCallApp.ACTION_ANSWER_VIDEO_INCOMING_CALL);
        builder.addAction(R.drawable.ic_videocam,
                mContext.getText(R.string.notification_action_answer_video),
                answerVideoPendingIntent);
537 538
    }

539 540
    private void addVoiceAction(Notification.Builder builder) {
        Log.i(this, "Will show \"voice\" action in the incoming call Notification");
541

542 543
        PendingIntent answerVoicePendingIntent = createNotificationPendingIntent(
                mContext, InCallApp.ACTION_ANSWER_VOICE_INCOMING_CALL);
544
        builder.addAction(R.drawable.ic_call_white_24dp,
545 546
                mContext.getText(R.string.notification_action_answer_voice),
                answerVoicePendingIntent);
547 548
    }

549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566
    private void addAcceptUpgradeRequestAction(Notification.Builder builder) {
        Log.i(this, "Will show \"accept\" action in the incoming call Notification");

        PendingIntent acceptVideoPendingIntent = createNotificationPendingIntent(
                mContext, InCallApp.ACTION_ANSWER_VOICE_INCOMING_CALL);
        builder.addAction(0, mContext.getText(R.string.notification_action_accept),
        acceptVideoPendingIntent);
    }

    private void addDismissUpgradeRequestAction(Notification.Builder builder) {
        Log.i(this, "Will show \"dismiss\" action in the incoming call Notification");

        PendingIntent declineVideoPendingIntent = createNotificationPendingIntent(
                mContext, InCallApp.ACTION_ANSWER_VOICE_INCOMING_CALL);
        builder.addAction(0, mContext.getText(R.string.notification_action_dismiss),
                declineVideoPendingIntent);
    }

567 568 569
    /**
     * Adds fullscreen intent to the builder.
     */
570 571
    private void configureFullScreenIntent(Notification.Builder builder, PendingIntent intent,
            Call call) {
572 573 574 575 576
        // Ok, we actually want to launch the incoming call
        // UI at this point (in addition to simply posting a notification
        // to the status bar).  Setting fullScreenIntent will cause
        // the InCallScreen to be launched immediately *unless* the
        // current foreground activity is marked as "immersive".
Chiao Cheng's avatar
Chiao Cheng committed
577
        Log.d(this, "- Setting fullScreenIntent: " + intent);
578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600
        builder.setFullScreenIntent(intent, true);

        // Ugly hack alert:
        //
        // The NotificationManager has the (undocumented) behavior
        // that it will *ignore* the fullScreenIntent field if you
        // post a new Notification that matches the ID of one that's
        // already active.  Unfortunately this is exactly what happens
        // when you get an incoming call-waiting call:  the
        // "ongoing call" notification is already visible, so the
        // InCallScreen won't get launched in this case!
        // (The result: if you bail out of the in-call UI while on a
        // call and then get a call-waiting call, the incoming call UI
        // won't come up automatically.)
        //
        // The workaround is to just notice this exact case (this is a
        // call-waiting call *and* the InCallScreen is not in the
        // foreground) and manually cancel the in-call notification
        // before (re)posting it.
        //
        // TODO: there should be a cleaner way of avoiding this
        // problem (see discussion in bug 3184149.)

601 602 603 604 605 606 607 608
        // If a call is onhold during an incoming call, the call actually comes in as
        // INCOMING.  For that case *and* traditional call-waiting, we want to
        // cancel the notification.
        boolean isCallWaiting = (call.getState() == Call.State.CALL_WAITING ||
                (call.getState() == Call.State.INCOMING &&
                        CallList.getInstance().getBackgroundCall() != null));

        if (isCallWaiting) {
Chiao Cheng's avatar
Chiao Cheng committed
609
            Log.i(this, "updateInCallNotification: call-waiting! force relaunch...");
610 611 612 613
            // Cancel the IN_CALL_NOTIFICATION immediately before
            // (re)posting it; this seems to force the
            // NotificationManager to launch the fullScreenIntent.
            mNotificationManager.cancel(IN_CALL_NOTIFICATION);
614
        }
615 616 617 618 619 620 621 622 623 624 625
    }

    private Notification.Builder getNotificationBuilder() {
        final Notification.Builder builder = new Notification.Builder(mContext);
        builder.setOngoing(true);

        // Make the notification prioritized over the other normal notifications.
        builder.setPriority(Notification.PRIORITY_HIGH);

        return builder;
    }
626

627
    private PendingIntent createLaunchPendingIntent() {
628

629
        final Intent intent = InCallPresenter.getInstance().getInCallIntent(
Yorke Lee's avatar
Yorke Lee committed
630
                false /* showDialpad */, false /* newOutgoingCall */);
631 632 633 634 635 636 637 638 639 640

        // PendingIntent that can be used to launch the InCallActivity.  The
        // system fires off this intent if the user pulls down the windowshade
        // and clicks the notification's expanded view.  It's also used to
        // launch the InCallActivity immediately when when there's an incoming
        // call (see the "fullScreenIntent" field below).
        PendingIntent inCallPendingIntent = PendingIntent.getActivity(mContext, 0, intent, 0);

        return inCallPendingIntent;
    }
641

642 643 644 645 646 647
    /**
     * Returns PendingIntent for answering a phone call. This will typically be used from
     * Notification context.
     */
    private static PendingIntent createNotificationPendingIntent(Context context, String action) {
        final Intent intent = new Intent(action, null,
648 649 650
                context, NotificationBroadcastReceiver.class);
        return PendingIntent.getBroadcast(context, 0, intent, 0);
    }
651

652
}