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();