auto import from //branches/cupcake/...@132569
The Android Open Source Project [Fri, 20 Feb 2009 15:38:33 +0000 (07:38 -0800)]
src/com/android/phone/CallCard.java
src/com/android/phone/CallNotifier.java
src/com/android/phone/InCallScreen.java
src/com/android/phone/PhoneApp.java
src/com/android/phone/PhoneUtils.java

index eb2f197..9bec7ed 100644 (file)
@@ -198,18 +198,43 @@ public class CallCard extends FrameLayout
             // dialing, active, or holding, and no calls are ringing or waiting.
             updateForegroundCall(phone);
         } else {
-            // Presumably IDLE:  no phone activity
-            // TODO: Should we ever be in this state in the first place?
-            // (Is there ever any reason to draw the in-call screen
-            // if the phone is totally idle?)
-            // ==> Possibly during the "call ended" state, for 5 seconds
-            //     *after* a call ends...
-            // For now:
-            Log.w(LOG_TAG, "CallCard updateState: overall Phone state is " + state);
-            updateForegroundCall(phone);
+            // The phone state is IDLE!
+            //
+            // The most common reason for this is if a call just
+            // ended: the phone will be idle, but we *will* still
+            // have a call in the DISCONNECTED state:
+            Call fgCall = phone.getForegroundCall();
+            Call bgCall = phone.getBackgroundCall();
+            if ((fgCall.getState() == Call.State.DISCONNECTED)
+                || (bgCall.getState() == Call.State.DISCONNECTED)) {
+                // In this case, we want the main CallCard to display
+                // the "Call ended" state.  The normal "foreground call"
+                // code path handles that.
+                updateForegroundCall(phone);
+            } else {
+                // We don't have any DISCONNECTED calls, which means
+                // that the phone is *truly* idle.
+                //
+                // It's very rare to be on the InCallScreen at all in this
+                // state, but it can happen in some cases:
+                // - A stray onPhoneStateChanged() event came in to the
+                //   InCallScreen *after* it was dismissed.
+                // - We're allowed to be on the InCallScreen because
+                //   an MMI or USSD is running, but there's no actual "call"
+                //   to display.
+                // - We're displaying an error dialog to the user
+                //   (explaining why the call failed), so we need to stay on
+                //   the InCallScreen so that the dialog will be visible.
+                //
+                // In these cases, put the callcard into a sane but "blank" state:
+                updateNoCall(phone);
+            }
         }
     }
 
+    /**
+     * Updates the UI for the state where the phone is in use, but not ringing.
+     */
     private void updateForegroundCall(Phone phone) {
         if (DBG) log("updateForegroundCall()...");
 
@@ -235,6 +260,10 @@ public class CallCard extends FrameLayout
         displayOngoingCallStatus(phone, null);
     }
 
+    /**
+     * Updates the UI for the state where an incoming call is ringing (or
+     * call waiting), regardless of whether the phone's already offhook.
+     */
     private void updateRingingCall(Phone phone) {
         if (DBG) log("updateRingingCall()...");
 
@@ -248,14 +277,41 @@ public class CallCard extends FrameLayout
     }
 
     /**
+     * Updates the UI for the state where the phone is not in use.
+
+     * This is analogous to updateForegroundCall() and updateRingingCall(),
+
+     * but for the (uncommon) case where the phone is
+     * totally idle.  (See comments in updateState() above.)
+     *
+     * This puts the callcard into a sane but "blank" state.
+     */
+    private void updateNoCall(Phone phone) {
+        if (DBG) log("updateNoCall()...");
+
+        displayMainCallStatus(phone, null);
+        displayOnHoldCallStatus(phone, null);
+        displayOngoingCallStatus(phone, null);
+    }
+
+    /**
      * Updates the main block of caller info on the CallCard
      * (ie. the stuff in the mainCallCard block) based on the specified Call.
      */
     private void displayMainCallStatus(Phone phone, Call call) {
         if (DBG) log("displayMainCallStatus(phone " + phone
-                     + ", call " + call + ", state" + call.getState() + ")...");
+                     + ", call " + call + ")...");
+
+        if (call == null) {
+            // There's no call to display, presumably because the phone is idle.
+            mMainCallCard.setVisibility(View.GONE);
+            return;
+        }
+        mMainCallCard.setVisibility(View.VISIBLE);
 
         Call.State state = call.getState();
+        if (DBG) log("  - call.state: " + call.getState());
+
         int callCardBackgroundResid = 0;
 
         // Background frame resources are different between portrait/landscape:
@@ -329,8 +385,18 @@ public class CallCard extends FrameLayout
                 break;
 
             case IDLE:
-                // The "main CallCard" should never display an idle call!
+                // The "main CallCard" should never be trying to display
+                // an idle call!  In updateState(), if the phone is idle,
+                // we call updateNoCall(), which means that we shouldn't
+                // have passed a call into this method at all.
                 Log.w(LOG_TAG, "displayMainCallStatus: IDLE call in the main call card!");
+
+                // (It is possible, though, that we had a valid call which
+                // became idle *after* the check in updateState() but
+                // before we get here...  So continue the best we can,
+                // with whatever (stale) info we can get from the
+                // passed-in Call object.)
+
                 break;
 
             default:
index 817c81e..4f527f8 100644 (file)
@@ -242,9 +242,10 @@ public class CallNotifier extends Handler
             }
         }
 
-        // Obtain the wake lock. Since the keepScreenOn() call tracks the state
-        // of the wake lock, it is ok to make the call here as well as in
-        // InCallScreen.onPhoneStateChanged().
+        // 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().)
         if (DBG) log("Holding wake lock on new incoming connection.");
         mApplication.requestWakeState(PhoneApp.WakeState.PARTIAL);
 
@@ -344,8 +345,7 @@ public class CallNotifier extends Handler
         if (state == Phone.State.OFFHOOK) {
             // basically do onPhoneStateChanged + displayCallScreen
             onPhoneStateChanged(r);
-            PhoneApp app = PhoneApp.getInstance();
-            app.displayCallScreen();
+            PhoneUtils.showIncomingCallUi();
         }
     }
 
@@ -506,14 +506,14 @@ public class CallNotifier extends Handler
         }
 
         if (c != null) {
-            String number = c.getAddress();
-            boolean isPrivateNumber = false; // TODO: need API for isPrivate()
-            long date = c.getCreateTime();
-            long duration = c.getDurationMillis();
-            Connection.DisconnectCause cause = c.getDisconnectCause();
+            final String number = c.getAddress();
+            final boolean isPrivateNumber = false; // TODO: need API for isPrivate()
+            final long date = c.getCreateTime();
+            final long duration = c.getDurationMillis();
+            final Connection.DisconnectCause cause = c.getDisconnectCause();
 
             // Set the "type" to be displayed in the call log (see constants in CallLog.Calls)
-            int callLogType;
+            final int callLogType;
             if (c.isIncoming()) {
                 callLogType = (cause == Connection.DisconnectCause.INCOMING_MISSED ?
                                CallLog.Calls.MISSED_TYPE :
@@ -526,14 +526,23 @@ public class CallNotifier extends Handler
             // get the callerinfo object and then log the call with it.
             {
                 Object o = c.getUserData();
-                CallerInfo ci;
+                final CallerInfo ci;
                 if ((o == null) || (o instanceof CallerInfo)){
                     ci = (CallerInfo) o;
                 } else {
                     ci = ((PhoneUtils.CallerInfoToken) o).currentInfo;
                 }
-                Calls.addCall(ci, mApplication, number, isPrivateNumber,
-                        callLogType, date, (int) duration / 1000);
+
+                // Watch out: Calls.addCall() hits the Contacts database,
+                // so we shouldn't call it from the main thread.
+                Thread t = new Thread() {
+                        public void run() {
+                            Calls.addCall(ci, mApplication, number, isPrivateNumber,
+                                          callLogType, date, (int) duration / 1000);
+                            // if (DBG) log("onDisconnect helper thread: Calls.addCall() done.");
+                        }
+                    };
+                t.start();
             }
 
             if (callLogType == CallLog.Calls.MISSED_TYPE) {
index ba7b02d..e96a2bb 100644 (file)
@@ -493,7 +493,7 @@ public class InCallScreen extends Activity
         // UI when the headset state changes.)
         registerReceiver(mReceiver, new IntentFilter(Intent.ACTION_HEADSET_PLUG));
 
-        // Check for any failures that happened during onCreate().
+        // Check for any failures that happened during onCreate() or onNewIntent().
         if (DBG) log("- onResume: mInCallInitialStatus = " + mInCallInitialStatus);
         if (mInCallInitialStatus != InCallInitStatus.SUCCESS) {
             if (DBG) log("- onResume: failure during startup: " + mInCallInitialStatus);
@@ -502,8 +502,12 @@ public class InCallScreen extends Activity
             // something more specific to let the user deal with the
             // problem.
             handleStartupError(mInCallInitialStatus);
+
+            // But it *is* OK to continue with the rest of onResume(),
+            // since any further setup steps (like updateScreen() and the
+            // CallCard setup) will fall back to a "blank" state if the
+            // phone isn't in use.
             mInCallInitialStatus = InCallInitStatus.SUCCESS;
-            return;
         }
 
         // Set the volume control handler while we are in the foreground.
@@ -527,12 +531,11 @@ public class InCallScreen extends Activity
         InCallInitStatus status = syncWithPhoneState();
         if (status != InCallInitStatus.SUCCESS) {
             if (DBG) log("- syncWithPhoneState failed! status = " + status);
-            // Couldn't update the UI, presumably because the phone is
-            // totally not in use.  We shouldn't even be in the in-call UI
-            // in the first place, so bail out:
-            if (DBG) log("- onResume: bailing out...");
-            finish();
-            return;
+            // Couldn't update the UI, presumably because the phone is totally
+            // idle.  But don't finish() immediately, since we might still
+            // have an error dialog up that the user needs to see.
+            // (And in that case, the error dialog is responsible for calling
+            // finish() when the user dismisses it.)
         }
 
         if (ENABLE_PHONE_UI_EVENT_LOGGING) {
@@ -548,17 +551,16 @@ public class InCallScreen extends Activity
         // But we need to do something special if we're coming
         // to the foreground while an incoming call is ringing:
         if (mPhone.getState() == Phone.State.RINGING) {
-            // We do want the screen to be on while the phone is ringing,
-            // but ONLY AFTER the InCallScreen has a chance to draw
-            // itself.  So here in onResume(), we use the special
-            // preventScreenOn() API to tell the PowerManager that the
-            // screen should be off even if someone's holding a full wake
-            // lock.  This prevents any flicker during the "incoming call"
-            // sequence.
-            app.preventScreenOn(true);
-
-            // And post an ALLOW_SCREEN_ON message to (eventually) undo
-            // the above preventScreenOn(true) call.
+            // 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().)
+            //
+            // We also called preventScreenOn(true) at that point, to
+            // avoid cosmetic glitches while we were being launched.
+            // So now we need to post an ALLOW_SCREEN_ON message to
+            // (eventually) undo the prior preventScreenOn(true) call.
+            //
             // (In principle we shouldn't do this until after our first
             // layout/draw pass.  But in practice, the delay caused by
             // simply waiting for the end of the message queue is long
@@ -572,7 +574,12 @@ public class InCallScreen extends Activity
             // probably by having the PowerManager and ActivityManager
             // work together to let apps request that the screen on/off
             // state be synchronized with the Activity lifecycle.
+            // (See bug 1648751.)
         } else {
+            // The phone isn't ringing; this is either an outgoing call, or
+            // we're returning to a call in progress.  There *shouldn't* be
+            // any prior preventScreenOn(true) call that we need to undo,
+            // but let's do this just to be safe:
             app.preventScreenOn(false);
         }
         app.updateWakeState();
@@ -1210,6 +1217,9 @@ public class InCallScreen extends Activity
                     return true;
                 }
                 break;
+            case KeyEvent.KEYCODE_MUTE:
+                PhoneUtils.setMute(mPhone, !PhoneUtils.getMute(mPhone));
+                return true;
         }
 
         if (event.getRepeatCount() == 0 && handleDialerKeyDown(keyCode, event)) {
@@ -1300,8 +1310,13 @@ public class InCallScreen extends Activity
     private void onPhoneStateChanged(AsyncResult r) {
         if (DBG) log("onPhoneStateChanged()...");
 
-        // TODO: we probably shouldn't do *anything* here if we're not the
-        // foreground activity!
+        // There's nothing to do here if we're not the foreground activity.
+        // (When we *do* eventually come to the foreground, we'll do a
+        // full update then.)
+        if (!mIsForegroundActivity) {
+            if (DBG) log("onPhoneStateChanged: Activity not in foreground! Bailing out...");
+            return;
+        }
 
         updateScreen();
 
@@ -1437,9 +1452,12 @@ public class InCallScreen extends Activity
             // CallCard.getCallFailedString()).
             updateScreen();
 
-            // If the Phone *is* totally idle now, display the "Call
-            // ended" state.
-            if (currentlyIdle) {
+            // Display the special "Call ended" state when the phone is idle
+            // but there's still a call in the DISCONNECTED state:
+            if (currentlyIdle
+                && ((mForegroundCall.getState() == Call.State.DISCONNECTED)
+                    || (mBackgroundCall.getState() == Call.State.DISCONNECTED))) {
+                if (DBG) log("- onDisconnect: switching to 'Call ended' state...");
                 setInCallScreenMode(InCallScreenMode.CALL_ENDED);
             }
 
@@ -2323,7 +2341,6 @@ public class InCallScreen extends Activity
 
         // The hint is hidden only when there's no menu at all,
         // which only happens in a few specific cases:
-
         if (mInCallScreenMode == InCallScreenMode.CALL_ENDED) {
             // The "Call ended" state.
             hintVisible = false;
@@ -2331,6 +2348,10 @@ public class InCallScreen extends Activity
             // An incoming call where you *don't* have the option to
             // "answer & end" or "answer & hold".
             hintVisible = false;
+        } else if (!phoneIsInUse()) {
+            // Or if the phone is totally idle (like if an error dialog
+            // is up, or an MMI is running.)
+            hintVisible = false;
         }
         int hintVisibility = (hintVisible) ? View.VISIBLE : View.GONE;
 
@@ -2356,6 +2377,8 @@ public class InCallScreen extends Activity
     /**
      * Brings up UI to handle the various error conditions that
      * can occur when first initializing the in-call UI.
+     * This is called from onResume() if we encountered
+     * an error while processing our initial Intent.
      *
      * @param status one of the InCallInitStatus error codes.
      */
index d6867b6..d8cba10 100644 (file)
@@ -197,16 +197,6 @@ public class PhoneApp extends Application {
                     ndpPanel.show();
                     break;
 
-                case EVENT_BLUETOOTH_HEADSET_CONNECTED:
-                    Toast.makeText(PhoneApp.this,
-                            getResources().getString(R.string.bluetooth_headset_connected),
-                            Toast.LENGTH_SHORT).show();
-                    break;
-                case EVENT_BLUETOOTH_HEADSET_DISCONNECTED:
-                    Toast.makeText(PhoneApp.this,
-                            getResources().getString(R.string.bluetooth_headset_disconnected),
-                            Toast.LENGTH_SHORT).show();
-                    break;
                 case EVENT_DATA_DISCONNECTED:
                     Intent roaming = new Intent();
                     roaming.setClass(PhoneApp.getInstance(),  DataRoamingReenable.class);
index 935e36f..c0e1192 100644 (file)
@@ -1125,6 +1125,32 @@ public class PhoneUtils {
         // 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();
     }