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