auto import from //branches/cupcake/...@137197
The Android Open Source Project [Mon, 9 Mar 2009 18:52:14 +0000 (11:52 -0700)]
16 files changed:
Android.mk
res/drawable-land/ic_incoming_call_bluetooth.png [new file with mode: 0644]
res/drawable-land/ic_incoming_call_silent.png [new file with mode: 0644]
res/drawable/ic_incall_ongoing_bluetooth.png [new file with mode: 0644]
res/drawable/ic_incoming_call_bluetooth.png [new file with mode: 0644]
res/drawable/ic_incoming_call_silent.png [new file with mode: 0644]
res/drawable/incall_frame_bluetooth_short.9.png [new file with mode: 0644]
res/drawable/incall_frame_bluetooth_tall_land.9.png [new file with mode: 0644]
res/drawable/incall_frame_bluetooth_tall_port.9.png [new file with mode: 0644]
res/layout/call_card.xml
res/values/colors.xml
src/com/android/phone/CallCard.java
src/com/android/phone/CallNotifier.java
src/com/android/phone/InCallScreen.java
src/com/android/phone/NotificationMgr.java
src/com/android/phone/PhoneApp.java

index 8b80511..d4ce77b 100644 (file)
@@ -1,7 +1,7 @@
 LOCAL_PATH:= $(call my-dir)
 include $(CLEAR_VARS)
 
-LOCAL_MODULE_TAGS := user development
+LOCAL_MODULE_TAGS := user
 
 LOCAL_SRC_FILES := $(call all-subdir-java-files)
 LOCAL_SRC_FILES += \
diff --git a/res/drawable-land/ic_incoming_call_bluetooth.png b/res/drawable-land/ic_incoming_call_bluetooth.png
new file mode 100644 (file)
index 0000000..c076891
Binary files /dev/null and b/res/drawable-land/ic_incoming_call_bluetooth.png differ
diff --git a/res/drawable-land/ic_incoming_call_silent.png b/res/drawable-land/ic_incoming_call_silent.png
new file mode 100644 (file)
index 0000000..698b7d8
Binary files /dev/null and b/res/drawable-land/ic_incoming_call_silent.png differ
diff --git a/res/drawable/ic_incall_ongoing_bluetooth.png b/res/drawable/ic_incall_ongoing_bluetooth.png
new file mode 100644 (file)
index 0000000..8e02d22
Binary files /dev/null and b/res/drawable/ic_incall_ongoing_bluetooth.png differ
diff --git a/res/drawable/ic_incoming_call_bluetooth.png b/res/drawable/ic_incoming_call_bluetooth.png
new file mode 100644 (file)
index 0000000..4b317c1
Binary files /dev/null and b/res/drawable/ic_incoming_call_bluetooth.png differ
diff --git a/res/drawable/ic_incoming_call_silent.png b/res/drawable/ic_incoming_call_silent.png
new file mode 100644 (file)
index 0000000..0b57cda
Binary files /dev/null and b/res/drawable/ic_incoming_call_silent.png differ
diff --git a/res/drawable/incall_frame_bluetooth_short.9.png b/res/drawable/incall_frame_bluetooth_short.9.png
new file mode 100644 (file)
index 0000000..80e59bd
Binary files /dev/null and b/res/drawable/incall_frame_bluetooth_short.9.png differ
diff --git a/res/drawable/incall_frame_bluetooth_tall_land.9.png b/res/drawable/incall_frame_bluetooth_tall_land.9.png
new file mode 100644 (file)
index 0000000..8bfd2ec
Binary files /dev/null and b/res/drawable/incall_frame_bluetooth_tall_land.9.png differ
diff --git a/res/drawable/incall_frame_bluetooth_tall_port.9.png b/res/drawable/incall_frame_bluetooth_tall_port.9.png
new file mode 100644 (file)
index 0000000..70c0ee4
Binary files /dev/null and b/res/drawable/incall_frame_bluetooth_tall_port.9.png differ
index ded981d..5c4df8e 100644 (file)
                   android:gravity="center_vertical"
                   android:layout_marginTop="4dip"
                   >
-        <ImageView
+        <ImageView android:id="@+id/otherCallOngoingIcon"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:layout_marginLeft="6dip"
index 51b5a9c..13aff2a 100644 (file)
@@ -20,6 +20,7 @@
 
     <!-- In-call UI -->
     <color name="incall_textConnected">#99CE3F</color> <!-- green -->
+    <color name="incall_textConnectedBluetooth">#54C1FF</color> <!-- blue -->
     <color name="incall_textEnded">#FF6072</color> <!-- red -->
     <color name="incall_textOnHold">#FF9524</color> <!-- orange -->
     
index 9bec7ed..42d6357 100644 (file)
@@ -52,6 +52,13 @@ public class CallCard extends FrameLayout
     private static final boolean DBG = false;
     private static final boolean PROFILE = true;
 
+    /**
+     * Reference to the InCallScreen activity that owns us.  This may be
+     * null if we haven't been initialized yet *or* after the InCallScreen
+     * activity has been destroyed.
+     */
+    private InCallScreen mInCallScreen;
+
     // Top-level subviews of the CallCard
     private ViewGroup mMainCallCard;
     private ViewGroup mOtherCallOngoingInfoArea;
@@ -66,6 +73,7 @@ public class CallCard extends FrameLayout
 
     // Text colors, used with the lower title and "other call" info areas
     private int mTextColorConnected;
+    private int mTextColorConnectedBluetooth;
     private int mTextColorEnded;
     private int mTextColorOnHold;
 
@@ -75,6 +83,7 @@ public class CallCard extends FrameLayout
     private TextView mLabel;
 
     // "Other call" info area
+    private ImageView mOtherCallOngoingIcon;
     private TextView mOtherCallOngoingName;
     private TextView mOtherCallOngoingStatus;
     private TextView mOtherCallOnHoldName;
@@ -120,6 +129,10 @@ public class CallCard extends FrameLayout
         mPhotoTracker = new ContactsAsyncHelper.ImageTracker();
     }
 
+    void setInCallScreenInstance(InCallScreen inCallScreen) {
+        mInCallScreen = inCallScreen;
+    }
+
     void reset() {
         if (DBG) log("reset()...");
 
@@ -127,7 +140,7 @@ public class CallCard extends FrameLayout
 
         // default to show ACTIVE call style, with empty title and status text
         showCallConnected();
-        mUpperTitle.setText("");
+        setUpperTitle("");
     }
 
     public void onTickForCallTimeElapsed(long timeElapsed) {
@@ -162,6 +175,8 @@ public class CallCard extends FrameLayout
 
         // Text colors
         mTextColorConnected = getResources().getColor(R.color.incall_textConnected);
+        mTextColorConnectedBluetooth =
+                getResources().getColor(R.color.incall_textConnectedBluetooth);
         mTextColorEnded = getResources().getColor(R.color.incall_textEnded);
         mTextColorOnHold = getResources().getColor(R.color.incall_textOnHold);
 
@@ -172,6 +187,7 @@ public class CallCard extends FrameLayout
         mLabel = (TextView) findViewById(R.id.label);
 
         // "Other call" info area
+        mOtherCallOngoingIcon = (ImageView) findViewById(R.id.otherCallOngoingIcon);
         mOtherCallOngoingName = (TextView) findViewById(R.id.otherCallOngoingName);
         mOtherCallOngoingStatus = (TextView) findViewById(R.id.otherCallOngoingStatus);
         mOtherCallOnHoldName = (TextView) findViewById(R.id.otherCallOnHoldName);
@@ -314,16 +330,30 @@ public class CallCard extends FrameLayout
 
         int callCardBackgroundResid = 0;
 
-        // Background frame resources are different between portrait/landscape:
+        // Background frame resources are different between portrait/landscape.
+        // TODO: Don't do this manually.  Instead let the resource system do
+        // it: just move the *_land assets over to the res/drawable-land
+        // directory (but with the same filename as the corresponding
+        // portrait asset.)
         boolean landscapeMode = InCallScreen.ConfigurationHelper.isLandscape();
 
+        // Background images are also different if Bluetooth is active.
+        final boolean bluetoothActive = PhoneApp.getInstance().showBluetoothIndication();
+
         switch (state) {
             case ACTIVE:
                 showCallConnected();
 
-                callCardBackgroundResid =
-                        landscapeMode ? R.drawable.incall_frame_connected_tall_land
-                        : R.drawable.incall_frame_connected_tall_port;
+                if (bluetoothActive) {
+                    callCardBackgroundResid =
+                            landscapeMode ? R.drawable.incall_frame_bluetooth_tall_land
+                            : R.drawable.incall_frame_bluetooth_tall_port;
+                } else {
+                    callCardBackgroundResid =
+                            landscapeMode ? R.drawable.incall_frame_connected_tall_land
+                            : R.drawable.incall_frame_connected_tall_port;
+                }
+
 
                 // update timer field
                 if (DBG) log("displayMainCallStatus: start periodicUpdateTimer");
@@ -362,9 +392,15 @@ public class CallCard extends FrameLayout
             case ALERTING:
                 showCallConnecting();
 
-                callCardBackgroundResid =
-                        landscapeMode ? R.drawable.incall_frame_normal_tall_land
-                        : R.drawable.incall_frame_normal_tall_port;
+                if (bluetoothActive) {
+                    callCardBackgroundResid =
+                            landscapeMode ? R.drawable.incall_frame_bluetooth_tall_land
+                            : R.drawable.incall_frame_bluetooth_tall_port;
+                } else {
+                    callCardBackgroundResid =
+                            landscapeMode ? R.drawable.incall_frame_normal_tall_land
+                            : R.drawable.incall_frame_normal_tall_port;
+                }
 
                 // Stop getting timer ticks from a previous call
                 mCallTime.cancelTimer();
@@ -375,9 +411,15 @@ public class CallCard extends FrameLayout
             case WAITING:
                 showCallIncoming();
 
-                callCardBackgroundResid =
-                        landscapeMode ? R.drawable.incall_frame_normal_tall_land
-                        : R.drawable.incall_frame_normal_tall_port;
+                if (bluetoothActive) {
+                    callCardBackgroundResid =
+                            landscapeMode ? R.drawable.incall_frame_bluetooth_tall_land
+                            : R.drawable.incall_frame_bluetooth_tall_port;
+                } else {
+                    callCardBackgroundResid =
+                            landscapeMode ? R.drawable.incall_frame_normal_tall_land
+                            : R.drawable.incall_frame_normal_tall_port;
+                }
 
                 // Stop getting timer ticks from a previous call
                 mCallTime.cancelTimer();
@@ -533,11 +575,18 @@ public class CallCard extends FrameLayout
         if (state == Call.State.ACTIVE) {
             // Use the "lower title" (in green).
             mLowerTitleViewGroup.setVisibility(View.VISIBLE);
-            mLowerTitleIcon.setImageResource(R.drawable.ic_incall_ongoing);
+
+            final boolean bluetoothActive = PhoneApp.getInstance().showBluetoothIndication();
+            int ongoingCallIcon = bluetoothActive ? R.drawable.ic_incall_ongoing_bluetooth
+                    : R.drawable.ic_incall_ongoing;
+            mLowerTitleIcon.setImageResource(ongoingCallIcon);
+
             mLowerTitle.setText(cardTitle);
-            mLowerTitle.setTextColor(mTextColorConnected);
-            mElapsedTime.setTextColor(mTextColorConnected);
-            mUpperTitle.setText("");
+
+            int textColor = bluetoothActive ? mTextColorConnectedBluetooth : mTextColorConnected;
+            mLowerTitle.setTextColor(textColor);
+            mElapsedTime.setTextColor(textColor);
+            setUpperTitle("");
         } else if (state == Call.State.DISCONNECTED) {
             // Use the "lower title" (in red).
             // TODO: We may not *always* want to use the lower title for
@@ -551,10 +600,10 @@ public class CallCard extends FrameLayout
             mLowerTitle.setText(cardTitle);
             mLowerTitle.setTextColor(mTextColorEnded);
             mElapsedTime.setTextColor(mTextColorEnded);
-            mUpperTitle.setText("");
+            setUpperTitle("");
         } else {
-            // All other states use the "upper title":
-            mUpperTitle.setText(cardTitle);
+            // All other states (DIALING, INCOMING, etc.) use the "upper title":
+            setUpperTitle(cardTitle, state);
             mLowerTitleViewGroup.setVisibility(View.INVISIBLE);
         }
 
@@ -727,11 +776,24 @@ public class CallCard extends FrameLayout
 
                 mOtherCallOngoingName.setText(name);
 
-                // The call here is always "ongoing", so use the green "connected" frame
-                // and green text color:
-                setOngoingInfoAreaBackgroundResource(R.drawable.incall_frame_connected_short);
-                mOtherCallOngoingName.setTextColor(mTextColorConnected);
-                mOtherCallOngoingStatus.setTextColor(mTextColorConnected);
+                // This is an "ongoing" call: we normally use the green
+                // background frame and text color, but we use blue
+                // instead if bluetooth is in use.
+                boolean bluetoothActive = PhoneApp.getInstance().showBluetoothIndication();
+
+                int ongoingCallBackground =
+                        bluetoothActive ? R.drawable.incall_frame_bluetooth_short
+                        : R.drawable.incall_frame_connected_short;
+                setOngoingInfoAreaBackgroundResource(ongoingCallBackground);
+
+                int ongoingCallIcon = bluetoothActive ? R.drawable.ic_incall_ongoing_bluetooth
+                        : R.drawable.ic_incall_ongoing;
+                mOtherCallOngoingIcon.setImageResource(ongoingCallIcon);
+
+                int textColor = bluetoothActive ? mTextColorConnectedBluetooth
+                        : mTextColorConnected;
+                mOtherCallOngoingName.setTextColor(textColor);
+                mOtherCallOngoingStatus.setTextColor(textColor);
 
                 mOtherCallOngoingInfoArea.setVisibility(View.VISIBLE);
 
@@ -1194,6 +1256,33 @@ public class CallCard extends FrameLayout
         vg.setLayoutParams(lp);
     }
 
+    /**
+     * Sets the CallCard "upper title" to a plain string, with no icon.
+     */
+    private void setUpperTitle(String title) {
+        mUpperTitle.setText(title);
+        mUpperTitle.setCompoundDrawables(null, null, null, null);
+    }
+
+    /**
+     * Sets the CallCard "upper title".  Also, depending on the passed-in
+     * Call state, possibly display an icon along with the title.
+     */
+    private void setUpperTitle(String title, Call.State state) {
+        mUpperTitle.setText(title);
+
+        int bluetoothIconId = 0;
+        if (((state == Call.State.INCOMING) || (state == Call.State.WAITING))
+                && PhoneApp.getInstance().showBluetoothIndication()) {
+            // Display the special bluetooth icon also, if this is an incoming
+            // call and the audio will be routed to bluetooth.
+            bluetoothIconId = R.drawable.ic_incoming_call_bluetooth;
+        }
+
+        mUpperTitle.setCompoundDrawablesWithIntrinsicBounds(bluetoothIconId, 0, 0, 0);
+        if (bluetoothIconId != 0) mUpperTitle.setCompoundDrawablePadding(5);
+    }
+
 
     // Debugging / testing code
 
index 2458f3b..bf217e4 100644 (file)
@@ -400,6 +400,10 @@ public class CallNotifier extends Handler
         NotificationMgr.getDefault().getStatusBarMgr()
                 .enableNotificationAlerts(state == Phone.State.IDLE);
 
+        // Have the PhoneApp recompute its mShowBluetoothIndication
+        // flag based on the (new) telephony state.
+        mApplication.updateBluetoothIndication();
+
         if (state == Phone.State.OFFHOOK) {
             PhoneUtils.setAudioControlState(PhoneUtils.AUDIO_OFFHOOK);
             if (DBG) log("onPhoneStateChanged: OFF HOOK");
index 2966801..1eafda7 100644 (file)
@@ -22,7 +22,6 @@ import com.android.internal.telephony.CallerInfoAsyncQuery;
 import com.android.internal.telephony.Connection;
 import com.android.internal.telephony.MmiCode;
 import com.android.internal.telephony.Phone;
-import android.widget.SlidingDrawer;
 
 import android.app.Activity;
 import android.app.AlertDialog;
@@ -66,6 +65,7 @@ import android.widget.Chronometer;
 import android.widget.EditText;
 import android.widget.ImageButton;
 import android.widget.LinearLayout;
+import android.widget.SlidingDrawer;
 import android.widget.TextView;
 import android.widget.Toast;
 
@@ -139,6 +139,7 @@ public class InCallScreen extends Activity
     private static final int DISMISS_MENU = 111;
     private static final int ALLOW_SCREEN_ON = 112;
     private static final int TOUCH_LOCK_TIMER = 113;
+    private static final int BLUETOOTH_STATE_CHANGED = 114;
 
 
     // High-level "modes" of the in-call UI.
@@ -353,6 +354,16 @@ public class InCallScreen extends Activity
                     if (DBG) log("TOUCH_LOCK_TIMER...");
                     touchLockTimerExpired();
                     break;
+
+                case BLUETOOTH_STATE_CHANGED:
+                    if (DBG) log("BLUETOOTH_STATE_CHANGED...");
+                    // The bluetooth headset state changed, so some UI
+                    // elements may need to update.  (There's no need to
+                    // look up the current state here, since any UI
+                    // elements that care about the bluetooth state get it
+                    // directly from PhoneApp.showBluetoothIndication().)
+                    updateScreen();
+                    break;
             }
         }
     };
@@ -489,8 +500,7 @@ public class InCallScreen extends Activity
         // the in-call screen.
         NotificationMgr.getDefault().getStatusBarMgr().enableExpandedView(false);
 
-        // Register for headset plug events (so we can update the onscreen
-        // UI when the headset state changes.)
+        // Listen for broadcast intents that might affect the onscreen UI.
         registerReceiver(mReceiver, new IntentFilter(Intent.ACTION_HEADSET_PLUG));
 
         // Check for any failures that happened during onCreate() or onNewIntent().
@@ -676,8 +686,8 @@ public class InCallScreen extends Activity
         // Re-enable the status bar (which we disabled in onResume().)
         NotificationMgr.getDefault().getStatusBarMgr().enableExpandedView(true);
 
-        // Unregister for headset plug events.  (These events only affect
-        // the in-call menu, so we only care about them while we're in the
+        // Unregister for broadcast intents.  (These affect the visible UI
+        // of the InCallScreen, so we only care about them while we're in the
         // foreground.)
         unregisterReceiver(mReceiver);
 
@@ -721,11 +731,14 @@ public class InCallScreen extends Activity
         PhoneApp app = PhoneApp.getInstance();
         app.setInCallScreenInstance(null);
 
-        // Also clear out the InCallMenu's reference to us (which lets it
-        // know we've been destroyed).
+        // Clear out the InCallScreen references in various helper objects
+        // (to let them know we've been destroyed).
         if (mInCallMenu != null) {
             mInCallMenu.clearInCallScreenReference();
         }
+        if (mCallCard != null) {
+            mCallCard.setInCallScreenInstance(null);
+        }
 
         // Make sure that the dialer session is over and done with.
         // 1. In Landscape mode, we stop the tone generator directly
@@ -903,6 +916,7 @@ public class InCallScreen extends Activity
                 mInCallPanel);
         mCallCard = (CallCard) callCardLayout.findViewById(R.id.callCard);
         if (DBG) log("  - mCallCard = " + mCallCard);
+        mCallCard.setInCallScreenInstance(this);
         mCallCard.reset();
 
         // Menu Button hint
@@ -1785,6 +1799,7 @@ public class InCallScreen extends Activity
         boolean updateSuccessful = false;
         if (DBG) log("syncWithPhoneState()...");
         if (DBG) PhoneUtils.dumpCallState(mPhone);
+        // if (DBG) dumpBluetoothState();
 
         // Make sure the Phone is "in use".  (If not, we shouldn't be on
         // this screen in the first place.)
@@ -1913,6 +1928,25 @@ public class InCallScreen extends Activity
             case PhoneUtils.CALL_STATUS_DIALED:
                 if (DBG) log("placeCall: PhoneUtils.placeCall() succeeded for regular call '"
                              + number + "'.");
+
+                // Any time we initiate a call, force the DTMF dialpad to
+                // close.  (We want to make sure the user can see the regular
+                // in-call UI while the new call is dialing, and when it
+                // first gets connected.)
+                mDialer.closeDialer(false);  // no "closing" animation
+
+                // Also, in case a previous call was already active (i.e. if
+                // we just did "Add call"), clear out the "history" of DTMF
+                // digits you typed, to make sure it doesn't persist from the
+                // previous call to the new call.
+                // TODO: it would be more precise to do this when the actual
+                // phone state change happens (i.e. when a new foreground
+                // call appears and the previous call moves to the
+                // background), but the InCallScreen doesn't keep enough
+                // state right now to notice that specific transition in
+                // onPhoneStateChanged().
+                mDialer.clearDigits();
+
                 return InCallInitStatus.SUCCESS;
             case PhoneUtils.CALL_STATUS_DIALED_MMI:
                 if (DBG) log("placeCall: specified number was an MMI code: '" + number + "'.");
@@ -3420,6 +3454,44 @@ public class InCallScreen extends Activity
         return false;
     }
 
+    /**
+     * Posts a message to our handler saying to update the onscreen UI
+     * based on a bluetooth headset state change.
+     */
+    /* package */ void updateBluetoothIndication() {
+        if (DBG) log("updateBluetoothIndication()...");
+        // No need to look at the current state here; any UI elements that
+        // care about the bluetooth state (i.e. the CallCard) get
+        // the necessary state directly from PhoneApp.showBluetoothIndication().
+        mHandler.removeMessages(BLUETOOTH_STATE_CHANGED);
+        mHandler.sendEmptyMessage(BLUETOOTH_STATE_CHANGED);
+    }
+
+    private void dumpBluetoothState() {
+        log("============== dumpBluetoothState() =============");
+        log("= isBluetoothAvailable: " + isBluetoothAvailable());
+        log("= isBluetoothAudioConnected: " + isBluetoothAudioConnected());
+        log("= isBluetoothAudioConnectedOrPending: " + isBluetoothAudioConnectedOrPending());
+        log("= PhoneApp.showBluetoothIndication: "
+            + PhoneApp.getInstance().showBluetoothIndication());
+        log("=");
+        if (mBluetoothHandsfree != null) {
+            log("= BluetoothHandsfree.isAudioOn: " + mBluetoothHandsfree.isAudioOn());
+            if (mBluetoothHeadset != null) {
+                String headsetAddress = mBluetoothHeadset.getHeadsetAddress();
+                log("= BluetoothHeadset.getHeadsetAddress: " + headsetAddress);
+                if (headsetAddress != null) {
+                    log("= BluetoothHeadset.isConnected: "
+                        + mBluetoothHeadset.isConnected(headsetAddress));
+                }
+            } else {
+                log("= mBluetoothHeadset is null");
+            }
+        } else {
+            log("= mBluetoothHandsfree is null; device is not BT capable");
+        }
+    }
+
     /* package */ void connectBluetoothAudio() {
         if (DBG) log("connectBluetoothAudio()...");
         if (mBluetoothHandsfree != null) {
index 886fc27..52fc835 100644 (file)
@@ -475,12 +475,21 @@ public class NotificationMgr implements CallerInfoAsyncQuery.OnQueryCompleteList
         final boolean hasActiveCall = !mPhone.getForegroundCall().isIdle();
         final boolean hasHoldingCall = !mPhone.getBackgroundCall().isIdle();
 
-        // Display the regular "in-call" icon in the status bar, except if
-        // there's only one call, and it's on hold (in which case we use the
-        // "on hold" icon.)
-        int resId = (!hasActiveCall && hasHoldingCall)
-                ? android.R.drawable.stat_sys_phone_call_on_hold
-                : android.R.drawable.stat_sys_phone_call;
+        // Display the appropriate "in-call" icon in the status bar,
+        // which depends on the current phone and/or bluetooth state.
+        int resId = android.R.drawable.stat_sys_phone_call;
+        if (!hasActiveCall && hasHoldingCall) {
+            // There's only one call, and it's on hold.  Use the "on hold"
+            // icon.
+            resId = android.R.drawable.stat_sys_phone_call_on_hold;
+        } else if (PhoneApp.getInstance().showBluetoothIndication()) {
+            // Bluetooth is active.  Show the blue-colored "in call" icon.
+
+            // TODO: In a followup CL, add this resource to public.xml
+            // (at which point it'll become
+            // android.R.drawable.stat_sys_phone_call_bluetooth.)
+            resId = com.android.internal.R.drawable.stat_sys_phone_call_bluetooth;
+        }
 
         // Note we can't just bail out now if (resId == mInCallResId),
         // since even if the status icon hasn't changed, some *other*
index d8cba10..2708718 100644 (file)
@@ -66,11 +66,10 @@ public class PhoneApp extends Application {
     private static final int EVENT_SIM_ABSENT = 1;
     private static final int EVENT_SIM_LOCKED = 2;
     private static final int EVENT_SIM_NETWORK_LOCKED = 3;
-    private static final int EVENT_BLUETOOTH_HEADSET_CONNECTED = 4;
-    private static final int EVENT_BLUETOOTH_HEADSET_DISCONNECTED = 5;
     private static final int EVENT_DATA_DISCONNECTED = 6;
     private static final int EVENT_WIRED_HEADSET_PLUG = 7;
     private static final int EVENT_SIM_STATE_CHANGED = 8;
+    private static final int EVENT_UPDATE_INCALL_NOTIFICATION = 9;
 
     // The MMI codes are also used by the InCallScreen.
     public static final int MMI_INITIATE = 10;
@@ -114,6 +113,8 @@ public class PhoneApp extends Application {
     Ringer ringer;
     BluetoothHandsfree mBtHandsfree;
     PhoneInterfaceManager phoneMgr;
+    int mBluetoothHeadsetState = BluetoothHeadset.STATE_ERROR;
+    boolean mShowBluetoothIndication = false;
 
     // The InCallScreen instance (or null if the InCallScreen hasn't been
     // created yet.)
@@ -197,6 +198,15 @@ public class PhoneApp extends Application {
                     ndpPanel.show();
                     break;
 
+                case EVENT_UPDATE_INCALL_NOTIFICATION:
+                    // Tell the NotificationMgr to update the "ongoing
+                    // call" icon in the status bar, if necessary.
+                    // Currently, this is triggered by a bluetooth headset
+                    // state change (since the status bar icon needs to
+                    // turn blue when bluetooth is active.)
+                    NotificationMgr.getDefault().updateInCallNotification();
+                    break;
+
                 case EVENT_DATA_DISCONNECTED:
                     Intent roaming = new Intent();
                     roaming.setClass(PhoneApp.getInstance(),  DataRoamingReenable.class);
@@ -822,6 +832,85 @@ public class PhoneApp extends Application {
         return mIsHeadsetPlugged;
     }
 
+    /**
+     * @return true if the onscreen UI should currently be showing the
+     * special "bluetooth is active" indication in a couple of places (in
+     * which UI elements turn blue and/or show the bluetooth logo.)
+     *
+     * This depends on the BluetoothHeadset state *and* the current
+     * telephony state; see shouldShowBluetoothIndication().
+     *
+     * @see CallCard
+     * @see NotificationMgr.updateInCallNotification
+     */
+    /* package */ boolean showBluetoothIndication() {
+        return mShowBluetoothIndication;
+    }
+
+    /**
+     * Recomputes the mShowBluetoothIndication flag based on the current
+     * bluetooth state and current telephony state.
+     *
+     * This needs to be called any time the bluetooth headset state or the
+     * telephony state changes.
+     */
+    /* package */ void updateBluetoothIndication() {
+        mShowBluetoothIndication = shouldShowBluetoothIndication(mBluetoothHeadsetState,
+                                                                 phone);
+    }
+
+    /**
+     * UI policy helper function for the couple of places in the UI that
+     * have some way of indicating that "bluetooth is in use."
+     *
+     * @return true if the onscreen UI should indicate that "bluetooth is in use",
+     *         based on the specified bluetooth headset state, and the
+     *         current state of the phone.
+     * @see showBluetoothIndication()
+     */
+    private static boolean shouldShowBluetoothIndication(int bluetoothState,
+                                                         Phone phone) {
+        // We want the UI to indicate that "bluetooth is in use" in two
+        // slightly different cases:
+        //
+        // (a) The obvious case: if a bluetooth headset is currently in
+        //     use for an ongoing call.
+        //
+        // (b) The not-so-obvious case: if an incoming call is ringing,
+        //     and we expect that audio *will* be routed to a bluetooth
+        //     headset once the call is answered.
+
+        switch (phone.getState()) {
+            case OFFHOOK:
+                // This covers normal active calls, and also the case if
+                // the foreground call is DIALING or ALERTING.  (If
+                // DIALING or ALERTING, audio *is* routed to the headset
+                // while the other end is ringing, so we just check for
+                // the bluetooth headset PLAYING state.  We don't need to
+                // do any tricks like we do below for the RINGING state.)
+
+                // TODO: we really need to know whether or not the headset
+                // is getting audio routed to it, rather than just whether
+                // or not the headset is connected.  So we really want to
+                // check for the "PLAYING" state here.  (But
+                // BluetoothHeadset doesn't have the concept of a
+                // "PLAYING" state yet; see bug 1695249.)
+                // For now:
+                return (bluetoothState == BluetoothHeadset.STATE_CONNECTED);
+
+            case RINGING:
+                // If an incoming call is ringing, we're *not* yet routing
+                // audio to the headset (since there's no in-call audio
+                // yet!)  In this case, if a bluetooth headset is
+                // connected at all, we assume that it'll become active
+                // once the user answers the phone.
+                return (bluetoothState == BluetoothHeadset.STATE_CONNECTED);
+
+            default:  // Presumably IDLE
+                return false;
+        }
+    }
+
 
     /**
      * Receiver for misc intent broadcasts the Phone app cares about.
@@ -835,19 +924,19 @@ public class PhoneApp extends Application {
                         System.AIRPLANE_MODE_ON, 0) == 0;
                 phone.setRadioPower(enabled);
             } else if (action.equals(BluetoothIntent.HEADSET_STATE_CHANGED_ACTION)) {
-                int state = intent.getIntExtra(BluetoothIntent.HEADSET_STATE,
-                        BluetoothHeadset.STATE_ERROR);
-                int prevState = intent.getIntExtra(BluetoothIntent.HEADSET_PREVIOUS_STATE,
-                        BluetoothHeadset.STATE_ERROR);
-                if (state == BluetoothHeadset.STATE_CONNECTED) {
-                    mHandler.sendMessage(
-                            mHandler.obtainMessage(EVENT_BLUETOOTH_HEADSET_CONNECTED, 0));
-                } else if (state == BluetoothHeadset.STATE_DISCONNECTED) {
-                    if (prevState == BluetoothHeadset.STATE_CONNECTED) {
-                        mHandler.sendMessage(
-                                mHandler.obtainMessage(EVENT_BLUETOOTH_HEADSET_DISCONNECTED, 0));
-                    }
-                }
+                mBluetoothHeadsetState = intent.getIntExtra(BluetoothIntent.HEADSET_STATE,
+                                                            BluetoothHeadset.STATE_ERROR);
+                if (DBG) Log.d(LOG_TAG, "mReceiver: HEADSET_STATE_CHANGED_ACTION");
+                if (DBG) Log.d(LOG_TAG, "==> new state: " + mBluetoothHeadsetState);
+
+                // First, recompute the mShowBluetoothIndication flag based on
+                // the (new) bluetooth state and current telephony state.
+                updateBluetoothIndication();
+
+                // Then, post Handler messages to the various components that
+                // might need to update their UI based on the new state.
+                if (isShowingCallScreen()) mInCallScreen.updateBluetoothIndication();
+                mHandler.sendEmptyMessage(EVENT_UPDATE_INCALL_NOTIFICATION);
             } else if (action.equals(TelephonyIntents.ACTION_ANY_DATA_CONNECTION_STATE_CHANGED)) {
                 if ("DISCONNECTED".equals(intent.getStringExtra(Phone.STATE_KEY))) {
                     String reason = intent.getStringExtra(Phone.STATE_CHANGE_REASON_KEY);
@@ -857,14 +946,12 @@ public class PhoneApp extends Application {
                 }
             } else if (action.equals(Intent.ACTION_HEADSET_PLUG)) {
                 if (DBG) Log.d(LOG_TAG, "mReceiver: ACTION_HEADSET_PLUG");
-                if (DBG) Log.d(LOG_TAG, "==> intent: " + intent);
                 if (DBG) Log.d(LOG_TAG, "    state: " + intent.getIntExtra("state", 0));
                 if (DBG) Log.d(LOG_TAG, "    name: " + intent.getStringExtra("name"));
                 mIsHeadsetPlugged = (intent.getIntExtra("state", 0) == 1);
                 mHandler.sendMessage(mHandler.obtainMessage(EVENT_WIRED_HEADSET_PLUG, 0));
             } else if (action.equals(Intent.ACTION_BATTERY_LOW)) {
                 if (DBG) Log.d(LOG_TAG, "mReceiver: ACTION_BATTERY_LOW");
-                if (DBG) Log.d(LOG_TAG, "==> intent: " + intent);
                 notifier.sendBatteryLow();  // Play a warning tone if in-call
             } else if ((action.equals(TelephonyIntents.ACTION_SIM_STATE_CHANGED)) &&
                     (mPUKEntryActivity != null)) {