Show car mode notification in status bar.

The notification is an ongoing event and can be used to get out of car mode.
diff --git a/api/current.xml b/api/current.xml
index 444b002..5592759 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -38899,6 +38899,17 @@
  visibility="public"
 >
 </field>
+<field name="EXTRA_PHYSICAL_DOCK_STATE"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;android.intent.extra.PHYSICAL_DOCK_STATE&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="EXTRA_REMOTE_INTENT_TOKEN"
  type="java.lang.String"
  transient="false"
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index d31b25b..1b0437c 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -1815,11 +1815,18 @@
 
     /**
      * Broadcast Action:  A sticky broadcast indicating the phone was docked
-     * or undocked.  Includes the extra
-     * field {@link #EXTRA_DOCK_STATE}, containing the current dock state. It also
-     * includes the boolean extra field {@link #EXTRA_CAR_MODE_ENABLED}, indicating
-     * the state of the car mode.
-     * This is intended for monitoring the current dock state.
+     * or undocked.
+     *
+     * <p>The intent will have the following extra values:
+     * <ul>
+     *   <li><em>{@link #EXTRA_DOCK_STATE}</em> - the current dock
+     *       state, which depends on the state of the car mode.</li>
+     *   <li><em>{@link #EXTRA_PHYSICAL_DOCK_STATE}</em> - the physical dock
+     *       state.</li>
+     *   <li><em>{@link #EXTRA_CAR_MODE_ENABLED}</em> - a boolean indicating the
+     *       state of the car mode.</li>
+     * </ul>
+     * <p>This is intended for monitoring the current dock state.
      * To launch an activity from a dock state change, use {@link #CATEGORY_CAR_DOCK}
      * or {@link #CATEGORY_DESK_DOCK} instead.
      */
@@ -2154,6 +2161,16 @@
     public static final int EXTRA_DOCK_STATE_CAR = 2;
 
     /**
+     * Used as an int extra field in {@link android.content.Intent#ACTION_DOCK_EVENT}
+     * intents to request the physical dock state. Possible values are
+     * {@link android.content.Intent#EXTRA_DOCK_STATE_UNDOCKED},
+     * {@link android.content.Intent#EXTRA_DOCK_STATE_DESK}, or
+     * {@link android.content.Intent#EXTRA_DOCK_STATE_CAR}.
+     */
+    public static final String EXTRA_PHYSICAL_DOCK_STATE =
+            "android.intent.extra.PHYSICAL_DOCK_STATE";
+
+    /**
      * Used as an boolean extra field in {@link android.content.Intent#ACTION_DOCK_EVENT}
      * intents to indicate that the car mode is enabled or not.
      */
diff --git a/core/java/com/android/internal/app/DisableCarModeActivity.java b/core/java/com/android/internal/app/DisableCarModeActivity.java
new file mode 100644
index 0000000..95dc1f9
--- /dev/null
+++ b/core/java/com/android/internal/app/DisableCarModeActivity.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.app;
+
+import android.app.Activity;
+import android.app.IUiModeManager;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Log;
+
+public class DisableCarModeActivity extends Activity {
+    private static final String TAG = "DisableCarModeActivity";
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        try {
+            IUiModeManager uiModeManager = IUiModeManager.Stub.asInterface(
+                    ServiceManager.getService("uimode"));
+            uiModeManager.disableCarMode();
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to disable car mode", e);
+        }
+        finish();
+    }
+
+}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index a27d28f..333db05 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1252,6 +1252,10 @@
                 <category android:name="android.intent.category.DEFAULT" />
             </intent-filter>
         </activity>
+        <activity android:name="com.android.internal.app.DisableCarModeActivity"
+                android:theme="@style/Theme.NoDisplay"
+                android:excludeFromRecents="true">
+        </activity>
         <activity android:name="com.android.internal.app.RingtonePickerActivity"
                 android:theme="@style/Theme.Dialog.Alert"
                 android:excludeFromRecents="true"
diff --git a/core/res/res/drawable-hdpi/stat_notify_car_mode.png b/core/res/res/drawable-hdpi/stat_notify_car_mode.png
new file mode 100644
index 0000000..6c51b32
--- /dev/null
+++ b/core/res/res/drawable-hdpi/stat_notify_car_mode.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/stat_notify_car_mode.png b/core/res/res/drawable-mdpi/stat_notify_car_mode.png
new file mode 100644
index 0000000..c664244
--- /dev/null
+++ b/core/res/res/drawable-mdpi/stat_notify_car_mode.png
Binary files differ
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 6f34b4f..cdf38b9 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -2261,4 +2261,9 @@
     <!-- See TETHER_STOP_DIALOG.  If there was an error disconnect, this is the text. -->
     <string name="tether_stop_error_message">We\'ve encountered a problem turning off Tethering. Please try again.</string>
 
+    <!-- Strings for car mode notification -->
+    <!-- Shown when car mode is enabled -->
+    <string name="car_mode_disable_notification_title">Car mode enabled</string>
+    <string name="car_mode_disable_notification_message">Select to disable car mode.</string>
+
 </resources>
diff --git a/services/java/com/android/server/DockObserver.java b/services/java/com/android/server/DockObserver.java
index b566fb7e..4791718 100644
--- a/services/java/com/android/server/DockObserver.java
+++ b/services/java/com/android/server/DockObserver.java
@@ -22,8 +22,10 @@
 import android.app.IActivityManager;
 import android.app.IUiModeManager;
 import android.app.KeyguardManager;
-import android.app.StatusBarManager;
+import android.app.Notification;
+import android.app.NotificationManager;
 import android.app.PendingIntent;
+import android.app.StatusBarManager;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
 import android.content.ActivityNotFoundException;
@@ -55,6 +57,8 @@
 import android.text.format.Time;
 import android.util.Log;
 
+import com.android.internal.R;
+import com.android.internal.app.DisableCarModeActivity;
 import com.android.internal.widget.LockPatternUtils;
 
 import java.io.FileNotFoundException;
@@ -101,6 +105,7 @@
     private final Context mContext;
 
     private PowerManagerService mPowerManager;
+    private NotificationManager mNotificationManager;
 
     private KeyguardManager.KeyguardLock mKeyguardLock;
     private boolean mKeyguardDisabled;
@@ -125,7 +130,8 @@
 
             // Launch a dock activity
             String category;
-            if (mCarModeEnabled || mDockState == Intent.EXTRA_DOCK_STATE_CAR) {
+            if (mCarModeEnabled) {
+                // Only launch car home when car mode is enabled.
                 category = Intent.CATEGORY_CAR_DOCK;
             } else if (mDockState == Intent.EXTRA_DOCK_STATE_DESK) {
                 category = Intent.CATEGORY_DESK_DOCK;
@@ -332,9 +338,13 @@
                         if (mCarModeEnabled && mDockState != Intent.EXTRA_DOCK_STATE_CAR) {
                             // Pretend to be in DOCK_STATE_CAR.
                             intent.putExtra(Intent.EXTRA_DOCK_STATE, Intent.EXTRA_DOCK_STATE_CAR);
+                        } else if (!mCarModeEnabled && mDockState == Intent.EXTRA_DOCK_STATE_CAR) {
+                            // Pretend to be in DOCK_STATE_UNDOCKED.
+                            intent.putExtra(Intent.EXTRA_DOCK_STATE, Intent.EXTRA_DOCK_STATE_UNDOCKED);
                         } else {
                             intent.putExtra(Intent.EXTRA_DOCK_STATE, mDockState);
                         }
+                        intent.putExtra(Intent.EXTRA_PHYSICAL_DOCK_STATE, mDockState);
                         intent.putExtra(Intent.EXTRA_CAR_MODE_ENABLED, mCarModeEnabled);
 
                         // Check if this is Bluetooth Dock
@@ -462,6 +472,52 @@
         }
     };
 
+    private void adjustStatusBarCarMode() {
+        if (mStatusBarManager == null) {
+            mStatusBarManager = (StatusBarManager) mContext.getSystemService(Context.STATUS_BAR_SERVICE);
+        }
+
+        // Fear not: StatusBarService manages a list of requests to disable
+        // features of the status bar; these are ORed together to form the
+        // active disabled list. So if (for example) the device is locked and
+        // the status bar should be totally disabled, the calls below will
+        // have no effect until the device is unlocked.
+        if (mStatusBarManager != null) {
+            long ident = Binder.clearCallingIdentity();
+            mStatusBarManager.disable(mCarModeEnabled
+                ? StatusBarManager.DISABLE_NOTIFICATION_TICKER
+                : StatusBarManager.DISABLE_NONE);
+            Binder.restoreCallingIdentity(ident);
+        }
+
+        if (mNotificationManager == null) {
+            mNotificationManager = (NotificationManager)
+                    mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+        }
+
+        if (mNotificationManager != null) {
+            long ident = Binder.clearCallingIdentity();
+            if (mCarModeEnabled) {
+                Intent carModeOffIntent = new Intent(mContext, DisableCarModeActivity.class);
+
+                Notification n = new Notification();
+                n.icon = R.drawable.stat_notify_car_mode;
+                n.defaults = Notification.DEFAULT_LIGHTS;
+                n.flags = Notification.FLAG_ONGOING_EVENT;
+                n.when = 0;
+                n.setLatestEventInfo(
+                        mContext,
+                        mContext.getString(R.string.car_mode_disable_notification_title),
+                        mContext.getString(R.string.car_mode_disable_notification_message),
+                        PendingIntent.getActivity(mContext, 0, carModeOffIntent, 0));
+                mNotificationManager.notify(0, n);
+            } else {
+                mNotificationManager.cancel(0);
+            }
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
     private void setCarMode(boolean enabled) throws RemoteException {
         mCarModeEnabled = enabled;
         if (enabled) {
@@ -475,23 +531,7 @@
             setMode(Configuration.UI_MODE_TYPE_NORMAL,
                     Configuration.UI_MODE_NIGHT_UNDEFINED);
         }
-
-        if (mStatusBarManager == null) {
-            mStatusBarManager = (StatusBarManager) mContext.getSystemService(Context.STATUS_BAR_SERVICE);
-        }
-
-        // Fear not: StatusBarService manages a list of requests to disable
-        // features of the status bar; these are ORed together to form the
-        // active disabled list. So if (for example) the device is locked and
-        // the status bar should be totally disabled, the calls below will
-        // have no effect until the device is unlocked.
-        if (mStatusBarManager != null) {
-            long ident = Binder.clearCallingIdentity();
-            mStatusBarManager.disable(enabled 
-                ? StatusBarManager.DISABLE_NOTIFICATION_TICKER
-                : StatusBarManager.DISABLE_NONE);
-            Binder.restoreCallingIdentity(ident);
-        }
+        adjustStatusBarCarMode();
     }
 
     private void setMode(int modeType, int modeNight) throws RemoteException {