Last forwarded number and incoming call subject.

Last forwarded number:
- Added assets for the "forward" icon for last forwarded number.
- Modified InCall Call to store last forwarded number; also calls
onLastForwardedNumberChange callback (the last forwarded number is
received via a supp service update, so may change after call starts).

Call subject (i.e. instant lettering):
- Added placeholder subject_bubble asset to form the chat bubble for
incoming calls with a subject.
- Modified InCall Call to store the call subject (expected to be populated
in extras at start of call.
- Added code to hide the call status (e.g. "incoming call via XYZ") line
and primary call label (e.g. a location "California", or the number type
"Mobile" for the number).  This was necessary to make room for the call
subject bubble, and is in line with the UX mocks.
- Change call subject text color to background color of call card (per
UX mocks)
- Modified call notification to show call subject if it is specified.

- Moved code to show HD icon into common method.

Bug: 22685114
Change-Id: I22d9dae16658490e3245cfdd9c936bb0584cd6db
diff --git a/res/drawable-hdpi/ic_forward_white_24dp.png b/res/drawable-hdpi/ic_forward_white_24dp.png
new file mode 100644
index 0000000..a0711d3
--- /dev/null
+++ b/res/drawable-hdpi/ic_forward_white_24dp.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_forward_white_24dp.png b/res/drawable-mdpi/ic_forward_white_24dp.png
new file mode 100644
index 0000000..65f7329
--- /dev/null
+++ b/res/drawable-mdpi/ic_forward_white_24dp.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_forward_white_24dp.png b/res/drawable-xhdpi/ic_forward_white_24dp.png
new file mode 100644
index 0000000..7a5df52
--- /dev/null
+++ b/res/drawable-xhdpi/ic_forward_white_24dp.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_forward_white_24dp.png b/res/drawable-xxhdpi/ic_forward_white_24dp.png
new file mode 100644
index 0000000..7bd5b16
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_forward_white_24dp.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_forward_white_24dp.png b/res/drawable-xxxhdpi/ic_forward_white_24dp.png
new file mode 100644
index 0000000..428009c
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_forward_white_24dp.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/subject_bubble.9.png b/res/drawable-xxxhdpi/subject_bubble.9.png
new file mode 100644
index 0000000..2627bf8
--- /dev/null
+++ b/res/drawable-xxxhdpi/subject_bubble.9.png
Binary files differ
diff --git a/res/layout/primary_call_info.xml b/res/layout/primary_call_info.xml
index c680ed6..d65b07f 100644
--- a/res/layout/primary_call_info.xml
+++ b/res/layout/primary_call_info.xml
@@ -33,6 +33,27 @@
     android:animateLayoutChanges="true"
     android:gravity="center">
 
+    <LinearLayout android:id="@+id/callSubjectLayout"
+                  android:layout_width="match_parent"
+                  android:layout_height="wrap_content"
+                  android:orientation="horizontal"
+                  android:clipChildren="false"
+                  android:clipToPadding="false">
+
+        <TextView android:id="@+id/callSubject"
+                  android:layout_width="wrap_content"
+                  android:layout_height="wrap_content"
+                  android:textAlignment="viewStart"
+                  android:textAppearance="?android:attr/textAppearanceSmall"
+                  android:textColor="@color/incall_call_banner_background_color"
+                  android:textSize="@dimen/call_label_text_size"
+                  android:background="@drawable/subject_bubble"
+                  android:maxLines="2"
+                  android:ellipsize="end"
+                  android:singleLine="false"
+                  android:visibility="gone" />
+    </LinearLayout>
+
     <LinearLayout android:id="@+id/callStateButton"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
@@ -108,6 +129,15 @@
             android:scaleType="fitCenter"
             android:visibility="gone" />
 
+        <ImageView android:id="@+id/forwardIcon"
+            android:src="@drawable/ic_forward_white_24dp"
+            android:layout_width="24dp"
+            android:layout_height="match_parent"
+            android:layout_marginEnd="8dp"
+            android:tint="@color/incall_call_banner_subtext_color"
+            android:scaleType="fitCenter"
+            android:visibility="gone" />
+
         <!-- Label (like "Mobile" or "Work", if present) and phone number, side by side -->
         <LinearLayout android:id="@+id/labelAndNumber"
             android:layout_width="wrap_content"
diff --git a/src/com/android/incallui/AnswerPresenter.java b/src/com/android/incallui/AnswerPresenter.java
index a5a88ff..fc75bf0 100644
--- a/src/com/android/incallui/AnswerPresenter.java
+++ b/src/com/android/incallui/AnswerPresenter.java
@@ -108,6 +108,11 @@
         }
     }
 
+    @Override
+    public void onLastForwardedNumberChange() {
+        // no-op
+    }
+
     private boolean isVideoUpgradePending(Call call) {
         return call.getSessionModificationState()
                 == Call.SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST;
diff --git a/src/com/android/incallui/Call.java b/src/com/android/incallui/Call.java
index 807d43a..7205b73 100644
--- a/src/com/android/incallui/Call.java
+++ b/src/com/android/incallui/Call.java
@@ -18,13 +18,13 @@
 
 import com.android.contacts.common.CallUtil;
 import com.android.contacts.common.testing.NeededForTesting;
-import com.android.incallui.CallList.Listener;
 
 import android.content.Context;
 import android.hardware.camera2.CameraCharacteristics;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.Trace;
+import android.telecom.Connection;
 import android.telecom.DisconnectCause;
 import android.telecom.GatewayInfo;
 import android.telecom.InCallService.VideoCall;
@@ -43,13 +43,6 @@
  */
 @NeededForTesting
 public class Call {
-    /**
-     * Call extras key used to store a child number associated with the current call.
-     * Used to communicate that the connection was received via a child phone number associated with
-     * the {@link PhoneAccount}'s primary number.
-     */
-    public static final String EXTRA_CHILD_ADDRESS = "android.telecom.EXTRA_CHILD_ADDRESS";
-
     /* Defines different states of this call */
     public static class State {
         public static final int INVALID = 0;
@@ -264,6 +257,8 @@
 
     private InCallVideoCallCallback mVideoCallCallback;
     private String mChildNumber;
+    private String mLastForwardedNumber;
+    private String mCallSubject;
 
     /**
      * Used only to create mock calls for testing
@@ -326,10 +321,44 @@
         }
 
         Bundle callExtras = mTelecommCall.getDetails().getExtras();
-        if (callExtras != null && callExtras.containsKey(EXTRA_CHILD_ADDRESS)) {
-            String childNumber = callExtras.getString(EXTRA_CHILD_ADDRESS);
-            if (!Objects.equals(childNumber, mChildNumber)) {
-                mChildNumber = childNumber;
+        if (callExtras != null) {
+            // Child address arrives when the call is first set up, so we do not need to notify the
+            // UI of this.
+            if (callExtras.containsKey(Connection.EXTRA_CHILD_ADDRESS)) {
+                String childNumber = callExtras.getString(Connection.EXTRA_CHILD_ADDRESS);
+                if (!Objects.equals(childNumber, mChildNumber)) {
+                    mChildNumber = childNumber;
+                }
+            }
+
+            // Last forwarded number comes in as an array of strings.  We want to choose the last
+            // item in the array.  The forwarding numbers arrive independently of when the call is
+            // originally set up, so we need to notify the the UI of the change.
+            if (callExtras.containsKey(Connection.EXTRA_LAST_FORWARDED_NUMBER)) {
+                ArrayList<String> lastForwardedNumbers =
+                        callExtras.getStringArrayList(Connection.EXTRA_LAST_FORWARDED_NUMBER);
+
+                if (lastForwardedNumbers != null) {
+                    String lastForwardedNumber = null;
+                    if (!lastForwardedNumbers.isEmpty()) {
+                        lastForwardedNumber = lastForwardedNumbers.get(
+                                lastForwardedNumbers.size() - 1);
+                    }
+
+                    if (!Objects.equals(lastForwardedNumber, mLastForwardedNumber)) {
+                        mLastForwardedNumber = lastForwardedNumber;
+                        CallList.getInstance().onLastForwardedNumberChange(this);
+                    }
+                }
+            }
+
+            // Call subject is present in the extras at the start of call, so we do not need to
+            // notify any other listeners of this.
+            if (callExtras.containsKey(Connection.EXTRA_CALL_SUBJECT)) {
+                String callSubject = callExtras.getString(Connection.EXTRA_CALL_SUBJECT);
+                if (!Objects.equals(mCallSubject, callSubject)) {
+                    mCallSubject = callSubject;
+                }
             }
         }
     }
@@ -418,6 +447,20 @@
         return mChildNumber;
     }
 
+    /**
+     * @return The last forwarded number for the call, or {@code null} if none specified.
+     */
+    public String getLastForwardedNumber() {
+        return mLastForwardedNumber;
+    }
+
+    /**
+     * @return The call subject, or {@code null} if none specified.
+     */
+    public String getCallSubject() {
+        return mCallSubject;
+    }
+
     /** Returns call disconnect cause, defined by {@link DisconnectCause}. */
     public DisconnectCause getDisconnectCause() {
         if (mState == State.DISCONNECTED || mState == State.IDLE) {
diff --git a/src/com/android/incallui/CallCardFragment.java b/src/com/android/incallui/CallCardFragment.java
index 4121390..5f28960 100644
--- a/src/com/android/incallui/CallCardFragment.java
+++ b/src/com/android/incallui/CallCardFragment.java
@@ -117,10 +117,12 @@
     private TextView mCallStateLabel;
     private TextView mCallTypeLabel;
     private ImageView mHdAudioIcon;
+    private ImageView mForwardIcon;
     private View mCallNumberAndLabel;
     private ImageView mPhoto;
     private TextView mElapsedTime;
     private Drawable mPrimaryPhotoDrawable;
+    private TextView mCallSubject;
 
     // Container view that houses the entire primary call card, including the call buttons
     private View mPrimaryCallCardContainer;
@@ -231,6 +233,7 @@
         mCallStateVideoCallIcon = (ImageView) view.findViewById(R.id.videoCallIcon);
         mCallStateLabel = (TextView) view.findViewById(R.id.callStateLabel);
         mHdAudioIcon = (ImageView) view.findViewById(R.id.hdAudioIcon);
+        mForwardIcon = (ImageView) view.findViewById(R.id.forwardIcon);
         mCallNumberAndLabel = view.findViewById(R.id.labelAndNumber);
         mCallTypeLabel = (TextView) view.findViewById(R.id.callTypeLabel);
         mElapsedTime = (TextView) view.findViewById(R.id.elapsedTime);
@@ -281,6 +284,7 @@
 
         mPrimaryName.setElegantTextHeight(false);
         mCallStateLabel.setElegantTextHeight(false);
+        mCallSubject = (TextView) view.findViewById(R.id.callSubject);
     }
 
     @Override
@@ -339,7 +343,7 @@
                 float videoViewTranslation = 0f;
 
                 // Translate the call card to its pre-animation state.
-                if (!mIsLandscape){
+                if (!mIsLandscape) {
                     mPrimaryCallCardContainer.setTranslationY(visible ?
                             -mPrimaryCallCardContainer.getHeight() : 0);
 
@@ -553,7 +557,12 @@
         Log.v(this, "DisconnectCause " + disconnectCause.toString());
         Log.v(this, "gateway " + connectionLabel + gatewayNumber);
 
-        if (TextUtils.equals(callStateLabel.getCallStateLabel(), mCallStateLabel.getText())) {
+        // Check if the call subject is showing -- if it is, we want to bypass showing the call
+        // state.
+        boolean isSubjectShowing = mCallSubject.getVisibility() == View.VISIBLE;
+
+        if (TextUtils.equals(callStateLabel.getCallStateLabel(), mCallStateLabel.getText()) &&
+                !isSubjectShowing) {
             // Nothing to do if the labels are the same
             if (state == Call.State.ACTIVE || state == Call.State.CONFERENCED) {
                 mCallStateLabel.clearAnimation();
@@ -562,8 +571,14 @@
             return;
         }
 
-        // Update the call state label and icon.
-        setCallStateLabel(callStateLabel);
+        if (isSubjectShowing) {
+            changeCallStateLabel(null);
+            callStateIcon = null;
+        } else {
+            // Update the call state label and icon.
+            setCallStateLabel(callStateLabel);
+        }
+
         if (!TextUtils.isEmpty(callStateLabel.getCallStateLabel())) {
             if (state == Call.State.ACTIVE || state == Call.State.CONFERENCED) {
                 mCallStateLabel.clearAnimation();
@@ -677,6 +692,23 @@
         mInCallMessageLabel.setVisibility(View.VISIBLE);
     }
 
+    /**
+     * Sets and shows the call subject if it is not empty.  Hides the call subject otherwise.
+     *
+     * @param callSubject The call subject.
+     */
+    @Override
+    public void setCallSubject(String callSubject) {
+        boolean showSubject = !TextUtils.isEmpty(callSubject);
+
+        mCallSubject.setVisibility(showSubject ? View.VISIBLE : View.GONE);
+        if (showSubject) {
+            mCallSubject.setText(callSubject);
+        } else {
+            mCallSubject.setText(null);
+        }
+    }
+
     public boolean isAnimating() {
         return mIsAnimating;
     }
@@ -922,6 +954,17 @@
     }
 
     /**
+     * Changes the visibility of the forward icon.
+     *
+     * @param visible {@code true} if the UI should show the forward icon.
+     */
+    @Override
+    public void showForwardIndicator(boolean visible) {
+        mForwardIcon.setVisibility(visible ? View.VISIBLE : View.GONE);
+    }
+
+
+    /**
      * Changes the visibility of the "manage conference call" button.
      *
      * @param visible Whether to set the button to be visible or not.
@@ -942,6 +985,16 @@
     }
 
     /**
+     * Determines the current visibility of the call subject.
+     *
+     * @return {@code true} if the subject is visible.
+     */
+    @Override
+    public boolean isCallSubjectVisible() {
+        return mCallSubject.getVisibility() == View.VISIBLE;
+    }
+
+    /**
      * Get the overall InCallUI background colors and apply to call card.
      */
     public void updateColors() {
@@ -959,6 +1012,7 @@
             mPrimaryCallCardContainer.setBackgroundColor(themeColors.mPrimaryColor);
         }
         mCallButtonsContainer.setBackgroundColor(themeColors.mPrimaryColor);
+        mCallSubject.setTextColor(themeColors.mPrimaryColor);
 
         mCurrentThemeColors = themeColors;
     }
diff --git a/src/com/android/incallui/CallCardPresenter.java b/src/com/android/incallui/CallCardPresenter.java
index c39d792..7c11b2d 100644
--- a/src/com/android/incallui/CallCardPresenter.java
+++ b/src/com/android/incallui/CallCardPresenter.java
@@ -66,7 +66,8 @@
     private static final String TAG = CallCardPresenter.class.getSimpleName();
     private static final long CALL_TIME_UPDATE_INTERVAL_MS = 1000;
 
-    private final EmergencyCallListener mEmergencyCallListener = ObjectFactory.newEmergencyCallListener();
+    private final EmergencyCallListener mEmergencyCallListener =
+            ObjectFactory.newEmergencyCallListener();
 
     private Call mPrimary;
     private Call mSecondary;
@@ -206,6 +207,7 @@
                 Call.areSameNumber(mPrimary, primary));
         final boolean secondaryChanged = !(Call.areSame(mSecondary, secondary) &&
                 Call.areSameNumber(mSecondary, secondary));
+        final boolean shouldShowCallSubject = shouldShowCallSubject(mPrimary);
 
         mSecondary = secondary;
         Call previousPrimary = mPrimary;
@@ -215,7 +217,8 @@
         // 1. Primary call changed.
         // 2. The call's ability to manage conference has changed.
         if (mPrimary != null && (primaryChanged ||
-                ui.isManageConferenceVisible() != shouldShowManageConference())) {
+                ui.isManageConferenceVisible() != shouldShowManageConference()) ||
+                ui.isCallSubjectVisible() != shouldShowCallSubject) {
             // primary call has changed
             if (previousPrimary != null) {
                 CallList.getInstance().removeCallUpdateListener(previousPrimary.getId(), this);
@@ -320,6 +323,19 @@
         updatePrimaryCallState();
     }
 
+    /**
+     * Handles a change to the last forwarding number by refreshing the primary call info.
+     */
+    @Override
+    public void onLastForwardedNumberChange() {
+        Log.v(this, "onLastForwardedNumberChange");
+
+        if (mPrimary == null) {
+            return;
+        }
+        updatePrimaryDisplayInfo();
+    }
+
     private String getSubscriptionNumber() {
         // If it's an emergency call, and they're not populating the callback number,
         // then try to fall back to the phone sub info (to hopefully get the SIM's
@@ -348,15 +364,23 @@
                     mPrimary.hasProperty(Details.PROPERTY_WIFI),
                     mPrimary.isConferenceCall());
 
-            boolean showHdAudioIndicator =
-                    isPrimaryCallActive() && mPrimary.hasProperty(Details.PROPERTY_HIGH_DEF_AUDIO);
-            getUi().showHdAudioIndicator(showHdAudioIndicator);
-
+            maybeShowHdAudioIcon();
             setCallbackNumber();
         }
     }
 
     /**
+     * Show the HD icon if the call is active and has {@link Details#PROPERTY_HIGH_DEF_AUDIO},
+     * except if the call has a last forwarded number (we will show that icon instead).
+     */
+    private void maybeShowHdAudioIcon() {
+        boolean showHdAudioIndicator =
+                isPrimaryCallActive() && mPrimary.hasProperty(Details.PROPERTY_HIGH_DEF_AUDIO) &&
+                TextUtils.isEmpty(mPrimary.getLastForwardedNumber());
+        getUi().showHdAudioIndicator(showHdAudioIndicator);
+    }
+
+    /**
      * Only show the conference call button if we can manage the conference.
      */
     private void maybeShowManageConferenceCallButton() {
@@ -584,20 +608,36 @@
             String name = getNameForCall(mPrimaryContactInfo);
             String number;
 
-            // If a child number is present, use it instead of the 2nd line.
             boolean isChildNumberShown = !TextUtils.isEmpty(mPrimary.getChildNumber());
-            if (isChildNumberShown) {
+            boolean isForwardedNumberShown = !TextUtils.isEmpty(mPrimary.getLastForwardedNumber());
+            boolean isCallSubjectShown = shouldShowCallSubject(mPrimary);
+
+            if (isCallSubjectShown) {
+                ui.setCallSubject(mPrimary.getCallSubject());
+            } else {
+                ui.setCallSubject(null);
+            }
+
+            if (isCallSubjectShown) {
+                number = null;
+            } else if (isChildNumberShown) {
                 number = mContext.getString(R.string.child_number, mPrimary.getChildNumber());
+            } else if (isForwardedNumberShown) {
+                // Use last forwarded number instead of second line, if present.
+                number = mPrimary.getLastForwardedNumber();
             } else {
                 number = getNumberForCall(mPrimaryContactInfo);
             }
 
+            ui.showForwardIndicator(isForwardedNumberShown);
+            maybeShowHdAudioIcon();
+
             boolean nameIsNumber = name != null && name.equals(mPrimaryContactInfo.number);
             ui.setPrimary(
                     number,
                     name,
                     nameIsNumber,
-                    isChildNumberShown ? null : mPrimaryContactInfo.label,
+                    isChildNumberShown || isCallSubjectShown ? null : mPrimaryContactInfo.label,
                     mPrimaryContactInfo.photo,
                     mPrimaryContactInfo.isSipCall);
         } else {
@@ -857,6 +897,24 @@
         }
     }
 
+    /**
+     * Determines whether the call subject should be visible on the UI.  For the call subject to be
+     * visible, the call has to be in an incoming or waiting state, and the subject must not be
+     * empty.
+     *
+     * @param call The call.
+     * @return {@code true} if the subject should be shown, {@code false} otherwise.
+     */
+    private boolean shouldShowCallSubject(Call call) {
+        if (call == null) {
+            return false;
+        }
+
+        boolean isIncomingOrWaiting = mPrimary.getState() == Call.State.INCOMING ||
+                mPrimary.getState() == Call.State.CALL_WAITING;
+        return isIncomingOrWaiting && !TextUtils.isEmpty(call.getCallSubject());
+    }
+
     public interface CallCardUi extends Ui {
         void setVisible(boolean on);
         void setCallCardVisible(boolean visible);
@@ -875,10 +933,13 @@
         void setPrimaryLabel(String label);
         void setEndCallButtonEnabled(boolean enabled, boolean animate);
         void setCallbackNumber(String number, boolean isEmergencyCalls);
+        void setCallSubject(String callSubject);
         void setProgressSpinnerVisible(boolean visible);
         void showHdAudioIndicator(boolean visible);
+        void showForwardIndicator(boolean visible);
         void showManageConferenceCallButton(boolean visible);
         boolean isManageConferenceVisible();
+        boolean isCallSubjectVisible();
         void animateForNewOutgoingCall();
         void sendAccessibilityAnnouncement();
     }
diff --git a/src/com/android/incallui/CallList.java b/src/com/android/incallui/CallList.java
index c0014bd..fbcc1cc 100644
--- a/src/com/android/incallui/CallList.java
+++ b/src/com/android/incallui/CallList.java
@@ -160,6 +160,20 @@
         }
     }
 
+    /**
+     * Called when a single call has changed session modification state.
+     *
+     * @param call The call.
+     */
+    public void onLastForwardedNumberChange(Call call) {
+        final List<CallUpdateListener> listeners = mCallUpdateListenerMap.get(call.getId());
+        if (listeners != null) {
+            for (CallUpdateListener listener : listeners) {
+                listener.onLastForwardedNumberChange();
+            }
+        }
+    }
+
     public void notifyCallUpdateListeners(Call call) {
         final List<CallUpdateListener> listeners = mCallUpdateListenerMap.get(call.getId());
         if (listeners != null) {
@@ -611,5 +625,10 @@
          * @param sessionModificationState The new session modification state.
          */
         public void onSessionModificationStateChange(int sessionModificationState);
+
+        /**
+         * Notifies of a change to the last forwarded number for a call.
+         */
+        public void onLastForwardedNumberChange();
     }
 }
diff --git a/src/com/android/incallui/StatusBarNotifier.java b/src/com/android/incallui/StatusBarNotifier.java
index 8df4520..e583434 100644
--- a/src/com/android/incallui/StatusBarNotifier.java
+++ b/src/com/android/incallui/StatusBarNotifier.java
@@ -40,6 +40,8 @@
 
 import com.google.common.base.Preconditions;
 
+import java.util.Objects;
+
 /**
  * This class adds Notifications to the status bar for the in-call experience.
  */
@@ -60,7 +62,7 @@
     private int mCurrentNotification = NOTIFICATION_NONE;
     private int mCallState = Call.State.INVALID;
     private int mSavedIcon = 0;
-    private int mSavedContent = 0;
+    private String mSavedContent = null;
     private Bitmap mSavedLargeIcon;
     private String mSavedContentTitle;
     private String mCallId = null;
@@ -206,7 +208,7 @@
         // Check if data has changed; if nothing is different, don't issue another notification.
         final int iconResId = getIconToDisplay(call);
         Bitmap largeIcon = getLargeIconToDisplay(contactInfo, call);
-        final int contentResId = getContentString(call);
+        final String content = getContentString(call);
         final String contentTitle = getContentTitle(contactInfo, call);
 
         final int notificationType;
@@ -218,7 +220,7 @@
             notificationType = NOTIFICATION_IN_CALL;
         }
 
-        if (!checkForChangeAndSaveData(iconResId, contentResId, largeIcon, contentTitle, state,
+        if (!checkForChangeAndSaveData(iconResId, content, largeIcon, contentTitle, state,
                 notificationType)) {
             return;
         }
@@ -244,7 +246,7 @@
         }
 
         // Set the content
-        builder.setContentText(mContext.getString(contentResId));
+        builder.setContentText(content);
         builder.setSmallIcon(iconResId);
         builder.setContentTitle(contentTitle);
         builder.setLargeIcon(largeIcon);
@@ -306,7 +308,7 @@
      * 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.
      */
-    private boolean checkForChangeAndSaveData(int icon, int content, Bitmap largeIcon,
+    private boolean checkForChangeAndSaveData(int icon, String content, Bitmap largeIcon,
             String contentTitle, int state, int notificationType) {
 
         // The two are different:
@@ -317,7 +319,7 @@
                 (contentTitle == null && mSavedContentTitle != null);
 
         // any change means we are definitely updating
-        boolean retval = (mSavedIcon != icon) || (mSavedContent != content) ||
+        boolean retval = (mSavedIcon != icon) || !Objects.equals(mSavedContent, content) ||
                 (mCallState != state) || (mSavedLargeIcon != largeIcon) ||
                 contentTitleChanged;
 
@@ -419,13 +421,20 @@
     /**
      * Returns the message to use with the notification.
      */
-    private int getContentString(Call call) {
+    private String getContentString(Call call) {
+        boolean isIncomingOrWaiting = call.getState() == Call.State.INCOMING ||
+                call.getState() == Call.State.CALL_WAITING;
+
+        if (isIncomingOrWaiting && !TextUtils.isEmpty(call.getCallSubject())) {
+            return call.getCallSubject();
+        }
+
         int resId = R.string.notification_ongoing_call;
         if (call.hasProperty(Details.PROPERTY_WIFI)) {
             resId = R.string.notification_ongoing_call_wifi;
         }
 
-        if (call.getState() == Call.State.INCOMING || call.getState() == Call.State.CALL_WAITING) {
+        if (isIncomingOrWaiting) {
             if (call.hasProperty(Details.PROPERTY_WIFI)) {
                 resId = R.string.notification_incoming_call_wifi;
             } else {
@@ -440,7 +449,7 @@
             resId = R.string.notification_requesting_video_call;
         }
 
-        return resId;
+        return mContext.getString(resId);
     }
 
     /**
@@ -637,4 +646,9 @@
             updateNotification(mInCallState, CallList.getInstance());
         }
     }
+
+    @Override
+    public void onLastForwardedNumberChange() {
+        // no-op
+    }
 }