Launch incoming call UI via notification, not with startActivity()

This is step 2 toward the new notification-based incoming call UI, which
will allow for special behavior if a call comes in while a
non-interruptable ("immersive") activity is in the foreground.

Previously, we'd explicitly call PhoneApp.displayCallScreen() as the very
last step of the "incoming call" sequence in CallNotifier.java.  Now, we
instead post a notification that includes a "fullScreenIntent" field,
which the framework will automagically launch as long as we're *not* in
the middle of an immersive activity.

(Note that we specify a non-null fullScreenIntent only while the phone is
ringing, so the regular "ongoing call" notification is unchanged.  We also
set the notification's FLAG_HIGH_PRIORITY bit if the phone is ringing.)

Next: implement UI for the case where we *are* in an immersive activity.

Other refactoring/cleanup in this change:

- Replaced PhoneUtils.showIncomingCallUi() with the new
  CallNotifier.showIncomingCall() method.  The functionality is the same
  *except* that the new version puts up a notification instead of
  explicitly calling startActivity() to bring up the InCallScreen.

  (I moved the code from PhoneUtils to CallNotifier since it's only ever
  called from CallNotifier methods.)

- Update NotificationMgr to use the new stat_sys_phone_call_ringing
  resource for the "incoming call" notification.  (That's still a
  placeholder icon, though.)

- Remove the totally obsolete PhoneApp.handleInCallOrRinging() method

Tested: incoming calls in both immersive and non-immersive activities.
  (Note that there's currently no way to actually *answer* the call if
  you're in an immersive activity, though.)

Bug: 2768760

Change-Id: If33a64d7c2fba3a4eb1ecab7db8f03f7593ca5c5
diff --git a/src/com/android/phone/CallNotifier.java b/src/com/android/phone/CallNotifier.java
index cff3801..1977ca1 100755
--- a/src/com/android/phone/CallNotifier.java
+++ b/src/com/android/phone/CallNotifier.java
@@ -27,12 +27,14 @@
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneBase;
 
+import android.app.ActivityManagerNative;
 import android.content.Context;
 import android.media.AudioManager;
 import android.media.ToneGenerator;
 import android.os.AsyncResult;
 import android.os.Handler;
 import android.os.Message;
+import android.os.RemoteException;
 import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.os.Vibrator;
@@ -442,7 +444,7 @@
             // Obtain a partial wake lock to make sure the CPU doesn't go to
             // sleep before we finish bringing up the InCallScreen.
             // (This will be upgraded soon to a full wake lock; see
-            // PhoneUtils.showIncomingCallUi().)
+            // showIncomingCall().)
             if (VDBG) log("Holding wake lock on new incoming connection.");
             mApplication.requestWakeState(PhoneApp.WakeState.PARTIAL);
 
@@ -458,8 +460,8 @@
                     mCallWaitingTonePlayer.start();
                 }
                 // in this case, just fall through like before, and call
-                // PhoneUtils.showIncomingCallUi
-                PhoneUtils.showIncomingCallUi();
+                // showIncomingCall().
+                showIncomingCall();
             }
         }
 
@@ -519,8 +521,8 @@
                 sendEmptyMessageDelayed(RINGER_CUSTOM_RINGTONE_QUERY_TIMEOUT,
                         RINGTONE_QUERY_WAIT_TIME);
             }
-            // calls to PhoneUtils.showIncomingCallUi will come after the
-            // queries are complete (or timeout).
+            // The call to showIncomingCall() will happen after the
+            // queries are complete (or time out).
         } else {
             // This should never happen; its the case where an incoming call
             // arrives at the same time that the query is still being run,
@@ -532,15 +534,14 @@
             mRinger.ring();
 
             // in this case, just fall through like before, and call
-            // PhoneUtils.showIncomingCallUi
-            PhoneUtils.showIncomingCallUi();
+            // showIncomingCall().
+            showIncomingCall();
         }
     }
 
     /**
      * Performs the final steps of the onNewRingingConnection sequence:
-     * starts the ringer, and launches the InCallScreen to show the
-     * "incoming call" UI.
+     * starts the ringer, and brings up the "incoming call" UI.
      *
      * Normally, this is called when the CallerInfo query completes (see
      * onQueryComplete()).  In this case, onQueryComplete() has already
@@ -593,20 +594,96 @@
         if (VDBG) log("RINGING... (onCustomRingQueryComplete)");
         mRinger.ring();
 
-        // ...and show the InCallScreen.
-        PhoneUtils.showIncomingCallUi();
+        // ...and display the incoming call to the user:
+        showIncomingCall();
     }
 
     private void onUnknownConnectionAppeared(AsyncResult r) {
         Phone.State state = mPhone.getState();
 
         if (state == Phone.State.OFFHOOK) {
-            // basically do onPhoneStateChanged + displayCallScreen
+            // basically do onPhoneStateChanged + display the incoming call UI
             onPhoneStateChanged(r);
-            PhoneUtils.showIncomingCallUi();
+            showIncomingCall();
         }
     }
 
+    /**
+     * Informs the user about a new incoming call.
+     *
+     * In most cases this means "bring up the full-screen incoming call
+     * UI".  However, if an immersive activity is running, the system
+     * NotificationManager will instead pop up a small notification window
+     * on top of the activity.
+     *
+     * Watch out: be sure to call this method only once per incoming call,
+     * or otherwise we may end up launching the InCallScreen multiple
+     * times (which can lead to slow responsiveness and/or visible
+     * glitches.)
+     *
+     * Note this method handles only the onscreen UI for incoming calls;
+     * the ringer and/or vibrator are started separately (see the various
+     * calls to Ringer.ring() in this class.)
+     *
+     * @see NotificationMgr.updateInCallNotification()
+     */
+    private void showIncomingCall() {
+        if (DBG) log("showIncomingCall()...");
+
+        // Before bringing up the "incoming call" UI, force any system
+        // dialogs (like "recent tasks" or the power dialog) to close first.
+        try {
+            ActivityManagerNative.getDefault().closeSystemDialogs("call");
+        } catch (RemoteException e) {
+        }
+
+        // Go directly to the in-call screen.
+        // (No need to do anything special if we're already on the in-call
+        // screen; it'll notice the phone state change and update itself.)
+
+        // But first, grab a full wake lock.  We do this here, before we
+        // even fire off the InCallScreen intent, to make sure the
+        // ActivityManager doesn't try to pause the InCallScreen as soon
+        // as it comes up.  (See bug 1648751.)
+        //
+        // And since the InCallScreen isn't visible yet (we haven't even
+        // fired off the intent yet), we DON'T want the screen to actually
+        // come on right now.  So *before* acquiring the wake lock we need
+        // to call preventScreenOn(), which tells the PowerManager that
+        // the screen should stay off even if someone's holding a full
+        // wake lock.  (This prevents any flicker during the "incoming
+        // call" sequence.  The corresponding preventScreenOn(false) call
+        // will come from the InCallScreen when it's finally ready to be
+        // displayed.)
+        //
+        // TODO: this is all a temporary workaround.  The real fix is to add
+        // an Activity attribute saying "this Activity wants to wake up the
+        // phone when it's displayed"; that way the ActivityManager could
+        // manage the wake locks *and* arrange for the screen to come on at
+        // the exact moment that the InCallScreen is ready to be displayed.
+        // (See bug 1648751.)
+        //
+        // TODO: also, we should probably *not* do any of this if the
+        // screen is already on(!)
+
+        mApplication.preventScreenOn(true);
+        mApplication.requestWakeState(PhoneApp.WakeState.FULL);
+
+        // Post the "incoming call" notification.  This will usually take
+        // us straight to the incoming call screen (thanks to the
+        // notification's "fullScreenIntent" field), but if an immersive
+        // activity is running it'll just appear as a notification.
+        NotificationMgr.getDefault().updateInCallNotification();
+    }
+
+    /**
+     * Updates the phone UI in response to phone state changes.
+     *
+     * Watch out: certain state changes are actually handled by their own
+     * specific methods:
+     *   - see onNewRingingConnection() for new incoming calls
+     *   - see onDisconnect() for calls being hung up or disconnected
+     */
     private void onPhoneStateChanged(AsyncResult r) {
         Phone.State state = mPhone.getState();
         if (VDBG) log("onPhoneStateChanged: state = " + state);
@@ -1557,9 +1634,10 @@
         mApplication.cdmaPhoneCallState.setCurrentCallState(
                 CdmaPhoneCallState.PhoneCallState.SINGLE_ACTIVE);
 
-        // Start the InCallScreen Activity if its not on foreground
+        // Display the incoming call to the user if the InCallScreen isn't
+        // already in the foreground.
         if (!mApplication.isShowingCallScreen()) {
-            PhoneUtils.showIncomingCallUi();
+            showIncomingCall();
         }
 
         // Start timer for CW display
diff --git a/src/com/android/phone/InCallScreen.java b/src/com/android/phone/InCallScreen.java
index 2981c85..4817cc1 100755
--- a/src/com/android/phone/InCallScreen.java
+++ b/src/com/android/phone/InCallScreen.java
@@ -784,7 +784,7 @@
             // If the phone is ringing, we *should* already be holding a
             // full wake lock (which we would have acquired before
             // firing off the intent that brought us here; see
-            // PhoneUtils.showIncomingCallUi().)
+            // CallNotifier.showIncomingCall().)
             //
             // We also called preventScreenOn(true) at that point, to
             // avoid cosmetic glitches while we were being launched.
diff --git a/src/com/android/phone/NotificationMgr.java b/src/com/android/phone/NotificationMgr.java
index 4d70a88..cdb5791 100644
--- a/src/com/android/phone/NotificationMgr.java
+++ b/src/com/android/phone/NotificationMgr.java
@@ -511,9 +511,7 @@
 
         if (hasRingingCall) {
             // There's an incoming ringing call.
-            // TODO: still need artwork for a "ringing" variant of stat_sys_phone_call.
-            // For now, just use the standard green "in call" icon.
-            resId = android.R.drawable.stat_sys_phone_call;
+            resId = com.android.internal.R.drawable.stat_sys_phone_call_ringing;
         } else if (!hasActiveCall && hasHoldingCall) {
             // There's only one call, and it's on hold.
             if (enhancedVoicePrivacy) {
@@ -667,6 +665,25 @@
         // line), and maybe even when the user swaps calls (ie. if we only
         // show info here for the "current active call".)
 
+        // Activate a couple of special Notification features if an
+        // incoming call is ringing:
+        if (hasRingingCall) {
+            if (DBG) log("- Using hi-pri notification for ringing call!");
+
+            // This is a high-priority event that should be shown even if
+            // the status bar is hidden.
+            notification.flags |= Notification.FLAG_HIGH_PRIORITY;
+
+            // In most cases, we actually want to launch the incoming call
+            // UI at this point (rather than just 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".
+            notification.fullScreenIntent =
+                    PendingIntent.getActivity(mContext, 0,
+                                              PhoneApp.createInCallIntent(), 0);
+        }
+
         if (DBG) log("Notifying IN_CALL_NOTIFICATION: " + notification);
         mNotificationMgr.notify(IN_CALL_NOTIFICATION,
                                 notification);
diff --git a/src/com/android/phone/PhoneApp.java b/src/com/android/phone/PhoneApp.java
index 742e666..2106463 100755
--- a/src/com/android/phone/PhoneApp.java
+++ b/src/com/android/phone/PhoneApp.java
@@ -635,35 +635,12 @@
     /**
      * Starts the InCallScreen Activity.
      */
-    void displayCallScreen() {
+    private void displayCallScreen() {
         if (VDBG) Log.d(LOG_TAG, "displayCallScreen()...");
         startActivity(createInCallIntent());
         Profiler.callScreenRequested();
     }
 
-    /**
-     * Helper function to check for one special feature of the CALL key:
-     * Normally, when the phone is idle, CALL takes you to the call log
-     * (see the handler for KEYCODE_CALL in PhoneWindow.onKeyUp().)
-     * But if the phone is in use (either off-hook or ringing) we instead
-     * handle the CALL button by taking you to the in-call UI.
-     *
-     * @return true if we intercepted the CALL keypress (i.e. the phone
-     *              was in use)
-     *
-     * @see DialerActivity#onCreate
-     */
-    boolean handleInCallOrRinging() {
-        if (phone.getState() != Phone.State.IDLE) {
-            // Phone is OFFHOOK or RINGING.
-            if (DBG) Log.v(LOG_TAG,
-                           "handleInCallOrRinging: show call screen");
-            displayCallScreen();
-            return true;
-        }
-        return false;
-    }
-
     boolean isSimPinEnabled() {
         return mIsSimPinEnabled;
     }
diff --git a/src/com/android/phone/PhoneUtils.java b/src/com/android/phone/PhoneUtils.java
index aab89ff..00ce76b 100755
--- a/src/com/android/phone/PhoneUtils.java
+++ b/src/com/android/phone/PhoneUtils.java
@@ -16,7 +16,6 @@
 
 package com.android.phone;
 
-import android.app.ActivityManagerNative;
 import android.app.AlertDialog;
 import android.app.Dialog;
 import android.app.ProgressDialog;
@@ -476,7 +475,7 @@
      * </pre>
      * @param app The phone instance.
      */
-    static private void updateCdmaCallStateOnNewOutgoingCall(PhoneApp app) {
+    private static void updateCdmaCallStateOnNewOutgoingCall(PhoneApp app) {
         if (app.cdmaPhoneCallState.getCurrentCallState() ==
             CdmaPhoneCallState.PhoneCallState.IDLE) {
             // This is the first outgoing call. Set the Phone Call State to ACTIVE
@@ -1607,58 +1606,6 @@
         PhoneApp.getInstance().startActivity(intent);
     }
 
-    /**
-     * Brings up the UI used to handle an incoming call.
-     *
-     * Originally, this brought up an IncomingCallPanel instance
-     * (which was a subclass of Dialog) on top of whatever app
-     * was currently running.  Now, we take you directly to the
-     * in-call screen, whose CallCard automatically does the right
-     * thing if there's a Call that's currently ringing.
-     */
-    static void showIncomingCallUi() {
-        if (DBG) log("showIncomingCallUi()...");
-        PhoneApp app = PhoneApp.getInstance();
-
-        // Before bringing up the "incoming call" UI, force any system
-        // dialogs (like "recent tasks" or the power dialog) to close first.
-        try {
-            ActivityManagerNative.getDefault().closeSystemDialogs("call");
-        } catch (RemoteException e) {
-        }
-
-        // Go directly to the in-call screen.
-        // (No need to do anything special if we're already on the in-call
-        // screen; it'll notice the phone state change and update itself.)
-
-        // But first, grab a full wake lock.  We do this here, before we
-        // even fire off the InCallScreen intent, to make sure the
-        // ActivityManager doesn't try to pause the InCallScreen as soon
-        // as it comes up.  (See bug 1648751.)
-        //
-        // And since the InCallScreen isn't visible yet (we haven't even
-        // fired off the intent yet), we DON'T want the screen to actually
-        // come on right now.  So *before* acquiring the wake lock we need
-        // to call preventScreenOn(), which tells the PowerManager that
-        // the screen should stay off even if someone's holding a full
-        // wake lock.  (This prevents any flicker during the "incoming
-        // call" sequence.  The corresponding preventScreenOn(false) call
-        // will come from the InCallScreen when it's finally ready to be
-        // displayed.)
-        //
-        // TODO: this is all a temporary workaround.  The real fix is to add
-        // an Activity attribute saying "this Activity wants to wake up the
-        // phone when it's displayed"; that way the ActivityManager could
-        // manage the wake locks *and* arrange for the screen to come on at
-        // the exact moment that the InCallScreen is ready to be displayed.
-        // (See bug 1648751.)
-        app.preventScreenOn(true);
-        app.requestWakeState(PhoneApp.WakeState.FULL);
-
-        // Fire off the InCallScreen intent.
-        app.displayCallScreen();
-    }
-
     static void turnOnSpeaker(Context context, boolean flag, boolean store) {
         if (DBG) log("turnOnSpeaker(flag=" + flag + ", store=" + store + ")...");
         AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
@@ -2284,7 +2231,7 @@
 
         if (PhoneApp.mDockState == Intent.EXTRA_DOCK_STATE_DESK ||
                 PhoneApp.mDockState == Intent.EXTRA_DOCK_STATE_CAR) {
-            if (DBG) log("activateSpeakerIfDocked(): Phone in a dock -> may need to turn on speaker.");
+            if (DBG) log("activateSpeakerIfDocked(): In a dock -> may need to turn on speaker.");
             PhoneApp app = PhoneApp.getInstance();
             BluetoothHandsfree bthf = app.getBluetoothHandsfree();