Merge "More multi-user methods in PM" into jb-mr1-dev
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index 2814301..4347e75 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -40,6 +40,28 @@
     private final Object mLock = new Object();
     private final SparseArray<Display> mDisplays = new SparseArray<Display>();
 
+    /**
+     * Broadcast receiver that indicates when the Wifi display status changes.
+     * <p>
+     * The status is provided as a {@link WifiDisplayStatus} object in the
+     * {@link #EXTRA_WIFI_DISPLAY_STATUS} extra.
+     * </p><p>
+     * This broadcast is only sent to registered receivers with the
+     * {@link android.Manifest.permission#CONFIGURE_WIFI_DISPLAY} permission and can
+     * only be sent by the system.
+     * </p>
+     * @hide
+     */
+    public static final String ACTION_WIFI_DISPLAY_STATUS_CHANGED =
+            "android.hardware.display.action.WIFI_DISPLAY_STATUS_CHANGED";
+
+    /**
+     * Contains a {@link WifiDisplayStatus} object.
+     * @hide
+     */
+    public static final String EXTRA_WIFI_DISPLAY_STATUS =
+            "android.hardware.display.extra.WIFI_DISPLAY_STATUS";
+
     /** @hide */
     public DisplayManager(Context context) {
         mContext = context;
@@ -127,6 +149,47 @@
     }
 
     /**
+     * Initiates a fresh scan of availble Wifi displays.
+     * The results are sent as a {@link #ACTION_WIFI_DISPLAY_STATUS_CHANGED} broadcast.
+     * @hide
+     */
+    public void scanWifiDisplays() {
+        mGlobal.scanWifiDisplays();
+    }
+
+    /**
+     * Connects to a Wifi display.
+     * The results are sent as a {@link #ACTION_WIFI_DISPLAY_STATUS_CHANGED} broadcast.
+     *
+     * @param deviceAddress The MAC address of the device to which we should connect.
+     * @hide
+     */
+    public void connectWifiDisplay(String deviceAddress) {
+        mGlobal.connectWifiDisplay(deviceAddress);
+    }
+
+    /**
+     * Disconnects from the current Wifi display.
+     * The results are sent as a {@link #ACTION_WIFI_DISPLAY_STATUS_CHANGED} broadcast.
+     * @hide
+     */
+    public void disconnectWifiDisplay() {
+        mGlobal.disconnectWifiDisplay();
+    }
+
+    /**
+     * Gets the current Wifi display status.
+     * Watch for changes in the status by registering a broadcast receiver for
+     * {@link #ACTION_WIFI_DISPLAY_STATUS_CHANGED}.
+     *
+     * @return The current Wifi display status.
+     * @hide
+     */
+    public WifiDisplayStatus getWifiDisplayStatus() {
+        return mGlobal.getWifiDisplayStatus();
+    }
+
+    /**
      * Listens for changes in available display devices.
      */
     public interface DisplayListener {
diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java
index 4077964..14b5440 100644
--- a/core/java/android/hardware/display/DisplayManagerGlobal.java
+++ b/core/java/android/hardware/display/DisplayManagerGlobal.java
@@ -253,6 +253,43 @@
         }
     }
 
+    public void scanWifiDisplays() {
+        try {
+            mDm.scanWifiDisplays();
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to scan for Wifi displays.", ex);
+        }
+    }
+
+    public void connectWifiDisplay(String deviceAddress) {
+        if (deviceAddress == null) {
+            throw new IllegalArgumentException("deviceAddress must not be null");
+        }
+
+        try {
+            mDm.connectWifiDisplay(deviceAddress);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to connect to Wifi display " + deviceAddress + ".", ex);
+        }
+    }
+
+    public void disconnectWifiDisplay() {
+        try {
+            mDm.disconnectWifiDisplay();
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to disconnect from Wifi display.", ex);
+        }
+    }
+
+    public WifiDisplayStatus getWifiDisplayStatus() {
+        try {
+            return mDm.getWifiDisplayStatus();
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to get Wifi display status.", ex);
+            return new WifiDisplayStatus();
+        }
+    }
+
     private final class DisplayManagerCallback extends IDisplayManagerCallback.Stub {
         @Override
         public void onDisplayEvent(int displayId, int event) {
diff --git a/core/java/android/hardware/display/IDisplayManager.aidl b/core/java/android/hardware/display/IDisplayManager.aidl
index d802aa1..36a9a7f 100644
--- a/core/java/android/hardware/display/IDisplayManager.aidl
+++ b/core/java/android/hardware/display/IDisplayManager.aidl
@@ -17,6 +17,8 @@
 package android.hardware.display;
 
 import android.hardware.display.IDisplayManagerCallback;
+import android.hardware.display.WifiDisplay;
+import android.hardware.display.WifiDisplayStatus;
 import android.view.DisplayInfo;
 
 /** @hide */
@@ -25,4 +27,16 @@
     int[] getDisplayIds();
 
     void registerCallback(in IDisplayManagerCallback callback);
+
+    // Requires CONFIGURE_WIFI_DISPLAY permission.
+    void scanWifiDisplays();
+
+    // Requires CONFIGURE_WIFI_DISPLAY permission.
+    void connectWifiDisplay(String address);
+
+    // Requires CONFIGURE_WIFI_DISPLAY permission.
+    void disconnectWifiDisplay();
+
+    // Requires CONFIGURE_WIFI_DISPLAY permission.
+    WifiDisplayStatus getWifiDisplayStatus();
 }
diff --git a/core/java/android/hardware/display/WifiDisplay.aidl b/core/java/android/hardware/display/WifiDisplay.aidl
new file mode 100644
index 0000000..7733075
--- /dev/null
+++ b/core/java/android/hardware/display/WifiDisplay.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2012 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 android.hardware.display;
+
+parcelable WifiDisplay;
diff --git a/core/java/android/hardware/display/WifiDisplay.java b/core/java/android/hardware/display/WifiDisplay.java
new file mode 100644
index 0000000..e51e97e
--- /dev/null
+++ b/core/java/android/hardware/display/WifiDisplay.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2012 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 android.hardware.display;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Describes the properties of a Wifi display.
+ * <p>
+ * This object is immutable.
+ * </p>
+ *
+ * @hide
+ */
+public final class WifiDisplay implements Parcelable {
+    private final String mDeviceAddress;
+    private final String mDeviceName;
+
+    public static final WifiDisplay[] EMPTY_ARRAY = new WifiDisplay[0];
+
+    public static final Creator<WifiDisplay> CREATOR = new Creator<WifiDisplay>() {
+        public WifiDisplay createFromParcel(Parcel in) {
+            String deviceAddress = in.readString();
+            String deviceName = in.readString();
+            return new WifiDisplay(deviceAddress, deviceName);
+        }
+
+        public WifiDisplay[] newArray(int size) {
+            return size == 0 ? EMPTY_ARRAY : new WifiDisplay[size];
+        }
+    };
+
+    public WifiDisplay(String deviceAddress, String deviceName) {
+        if (deviceAddress == null) {
+            throw new IllegalArgumentException("deviceAddress must not be null");
+        }
+        if (deviceName == null) {
+            throw new IllegalArgumentException("deviceName must not be null");
+        }
+
+        mDeviceAddress = deviceAddress;
+        mDeviceName = deviceName;
+    }
+
+    /**
+     * Gets the MAC address of the Wifi display device.
+     */
+    public String getDeviceAddress() {
+        return mDeviceAddress;
+    }
+
+    /**
+     * Gets the name of the Wifi display device.
+     */
+    public String getDeviceName() {
+        return mDeviceName;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        return o instanceof WifiDisplay && equals((WifiDisplay)o);
+    }
+
+    public boolean equals(WifiDisplay other) {
+        return other != null
+                && mDeviceAddress.equals(other.mDeviceAddress)
+                && mDeviceName.equals(other.mDeviceName);
+    }
+
+    @Override
+    public int hashCode() {
+        // The address on its own should be sufficiently unique for hashing purposes.
+        return mDeviceAddress.hashCode();
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeString(mDeviceAddress);
+        dest.writeString(mDeviceName);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    // For debugging purposes only.
+    @Override
+    public String toString() {
+        return mDeviceName + " (" + mDeviceAddress + ")";
+    }
+}
diff --git a/core/java/android/hardware/display/WifiDisplayStatus.aidl b/core/java/android/hardware/display/WifiDisplayStatus.aidl
new file mode 100644
index 0000000..35c633e
--- /dev/null
+++ b/core/java/android/hardware/display/WifiDisplayStatus.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2012 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 android.hardware.display;
+
+parcelable WifiDisplayStatus;
diff --git a/core/java/android/hardware/display/WifiDisplayStatus.java b/core/java/android/hardware/display/WifiDisplayStatus.java
new file mode 100644
index 0000000..542d1b3
--- /dev/null
+++ b/core/java/android/hardware/display/WifiDisplayStatus.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2012 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 android.hardware.display;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Arrays;
+
+/**
+ * Describes the current global state of Wifi display connectivity, including the
+ * currently connected display and all known displays.
+ * <p>
+ * This object is immutable.
+ * </p>
+ *
+ * @hide
+ */
+public final class WifiDisplayStatus implements Parcelable {
+    private final boolean mEnabled;
+    private final WifiDisplay mConnectedDisplay;
+    private final WifiDisplay[] mKnownDisplays;
+    private final boolean mScanInProgress;
+    private final boolean mConnectionInProgress;
+
+    public static final Creator<WifiDisplayStatus> CREATOR = new Creator<WifiDisplayStatus>() {
+        public WifiDisplayStatus createFromParcel(Parcel in) {
+            boolean enabled = (in.readInt() != 0);
+
+            WifiDisplay connectedDisplay = null;
+            if (in.readInt() != 0) {
+                connectedDisplay = WifiDisplay.CREATOR.createFromParcel(in);
+            }
+
+            WifiDisplay[] knownDisplays = WifiDisplay.CREATOR.newArray(in.readInt());
+            for (int i = 0; i < knownDisplays.length; i++) {
+                knownDisplays[i] = WifiDisplay.CREATOR.createFromParcel(in);
+            }
+
+            boolean scanInProgress = (in.readInt() != 0);
+            boolean connectionInProgress = (in.readInt() != 0);
+
+            return new WifiDisplayStatus(enabled, connectedDisplay, knownDisplays,
+                    scanInProgress, connectionInProgress);
+        }
+
+        public WifiDisplayStatus[] newArray(int size) {
+            return new WifiDisplayStatus[size];
+        }
+    };
+
+    public WifiDisplayStatus() {
+        this(false, null, WifiDisplay.EMPTY_ARRAY, false, false);
+    }
+
+    public WifiDisplayStatus(boolean enabled,
+            WifiDisplay connectedDisplay, WifiDisplay[] knownDisplays,
+            boolean scanInProgress, boolean connectionInProgress) {
+        if (knownDisplays == null) {
+            throw new IllegalArgumentException("knownDisplays must not be null");
+        }
+
+        mEnabled = enabled;
+        mConnectedDisplay = connectedDisplay;
+        mKnownDisplays = knownDisplays;
+        mScanInProgress = scanInProgress;
+        mConnectionInProgress = connectionInProgress;
+    }
+
+    /**
+     * Returns true if the Wifi display feature is enabled and available for use.
+     * <p>
+     * The value of this property reflects whether Wifi and Wifi P2P functions
+     * are enabled.  Enablement is not directly controllable by the user at this
+     * time, except indirectly such as by turning off Wifi altogether.
+     * </p>
+     */
+    public boolean isEnabled() {
+        return mEnabled;
+    }
+
+    /**
+     * Gets the currently connected Wifi display or null if none.
+     */
+    public WifiDisplay getConnectedDisplay() {
+        return mConnectedDisplay;
+    }
+
+    /**
+     * Gets the list of all known Wifi displays, never null.
+     */
+    public WifiDisplay[] getKnownDisplays() {
+        return mKnownDisplays;
+    }
+
+    /**
+     * Returns true if there is currently a Wifi display scan in progress.
+     */
+    public boolean isScanInProgress() {
+        return mScanInProgress;
+    }
+
+    /**
+     * Returns true if there is currently a Wifi display connection in progress.
+     */
+    public boolean isConnectionInProgress() {
+        return mConnectionInProgress;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mEnabled ? 1 : 0);
+
+        if (mConnectedDisplay != null) {
+            dest.writeInt(1);
+            mConnectedDisplay.writeToParcel(dest, flags);
+        } else {
+            dest.writeInt(0);
+        }
+
+        dest.writeInt(mKnownDisplays.length);
+        for (WifiDisplay display : mKnownDisplays) {
+            display.writeToParcel(dest, flags);
+        }
+
+        dest.writeInt(mScanInProgress ? 1 : 0);
+        dest.writeInt(mConnectionInProgress ? 1 : 0);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    // For debugging purposes only.
+    @Override
+    public String toString() {
+        return "WifiDisplayStatus{enabled=" + mEnabled
+                + ", connectedDisplay=" + mConnectedDisplay
+                + ", knownDisplays=" + Arrays.toString(mKnownDisplays)
+                + ", scanInProgress=" + mScanInProgress
+                + ", connectionInProgress=" + mConnectionInProgress
+                + "}";
+    }
+}
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index a1901a5..cc2c002 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -331,6 +331,15 @@
     }
 
     /**
+     * Returns true if the screen auto-brightness adjustment setting should
+     * be available in the UI.  This setting is experimental and disabled by default.
+     * @hide
+     */
+    public static boolean useScreenAutoBrightnessAdjustmentFeature() {
+        return SystemProperties.getBoolean("persist.power.useautobrightadj", false);
+    }
+
+    /**
      * Creates a new wake lock with the specified level and flags.
      * <p>
      * The {@code levelAndFlags} parameter specifies a wake lock level and optional flags
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 23a412f..560021d 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -108,6 +108,8 @@
     <protected-broadcast
         android:name="android.bluetooth.pan.profile.action.CONNECTION_STATE_CHANGED" />
 
+    <protected-broadcast android:name="android.hardware.display.action.WIFI_DISPLAY_STATUS_CHANGED" />
+
     <protected-broadcast android:name="android.hardware.usb.action.USB_STATE" />
     <protected-broadcast android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED" />
     <protected-broadcast android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED" />
@@ -1446,6 +1448,22 @@
         android:description="@string/permdesc_readFrameBuffer"
         android:protectionLevel="signature|system" />
 
+    <!-- Allows an application to configure and connect to Wifi displays
+         @hide -->
+    <permission android:name="android.permission.CONFIGURE_WIFI_DISPLAY"
+        android:label="@string/permlab_configureWifiDisplay"
+        android:description="@string/permdesc_configureWifiDisplay"
+        android:protectionLevel="signature" />
+
+    <!-- Allows an application to control low-level features of Wifi displays
+         such as opening an RTSP socket.  This permission should only be used
+         by the display manager.
+         @hide -->
+    <permission android:name="android.permission.CONTROL_WIFI_DISPLAY"
+        android:label="@string/permlab_controlWifiDisplay"
+        android:description="@string/permdesc_controlWifiDisplay"
+        android:protectionLevel="signature" />
+
     <!-- Required to be able to disable the device (very dangerous!). -->
     <permission android:name="android.permission.BRICK"
         android:label="@string/permlab_brick"
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 9a8e712..139715c 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -486,7 +486,6 @@
   <java-symbol type="string" name="display_manager_hdmi_display_name" />
   <java-symbol type="string" name="display_manager_overlay_display_name" />
   <java-symbol type="string" name="display_manager_overlay_display_title" />
-  <java-symbol type="string" name="display_manager_wifi_display_name" />
   <java-symbol type="string" name="double_tap_toast" />
   <java-symbol type="string" name="elapsed_time_short_format_h_mm_ss" />
   <java-symbol type="string" name="elapsed_time_short_format_mm_ss" />
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 1e7e9fb..f989e4e 100755
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1191,6 +1191,16 @@
     <string name="permdesc_readFrameBuffer">Allows the app to read the content of the frame buffer.</string>
 
     <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_configureWifiDisplay">configure Wifi displays</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_configureWifiDisplay">Allows the app to configure and connect to Wifi displays.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_controlWifiDisplay">control Wifi displays</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_controlWifiDisplay">Allows the app to control low-level features of Wifi displays.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permlab_modifyAudioSettings">change your audio settings</string>
     <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permdesc_modifyAudioSettings">Allows the app to modify global audio settings such as volume and which speaker is used for output.</string>
@@ -3678,9 +3688,6 @@
     <!-- Title text to show within the overlay.  [CHAR LIMIT=50] -->
     <string name="display_manager_overlay_display_title"><xliff:g id="name">%1$s</xliff:g>: <xliff:g id="width">%2$d</xliff:g>x<xliff:g id="height">%3$d</xliff:g>, <xliff:g id="dpi">%4$d</xliff:g> dpi</string>
 
-    <!-- Name of a wifi display.  [CHAR LIMIT=50] -->
-    <string name="display_manager_wifi_display_name">Wifi display: <xliff:g id="device">%1$s</xliff:g></string>
-
     <!-- Keyguard strings -->
     <!-- Label shown on emergency call button in keyguard -->
     <string name="kg_emergency_call_label">Emergency call</string>
diff --git a/graphics/java/android/renderscript/ScriptIntrinsicColorMatrix.java b/graphics/java/android/renderscript/ScriptIntrinsicColorMatrix.java
index dce1939..8cac28e 100644
--- a/graphics/java/android/renderscript/ScriptIntrinsicColorMatrix.java
+++ b/graphics/java/android/renderscript/ScriptIntrinsicColorMatrix.java
@@ -80,7 +80,7 @@
     }
 
     /**
-     * Set a color matrix to convert from RGB to luminace. The alpha channel
+     * Set a color matrix to convert from RGB to luminance. The alpha channel
      * will be a copy.
      *
      */
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 13800a6..3dfb638 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -16,6 +16,8 @@
     <uses-permission android:name="android.permission.REMOTE_AUDIO_PLAYBACK" />
 
     <uses-permission android:name="android.permission.MANAGE_USERS" />
+    <uses-permission android:name="android.permission.READ_PROFILE" />
+    <uses-permission android:name="android.permission.READ_CONTACTS" />
 
     <!-- Networking and telephony -->
     <uses-permission android:name="android.permission.BLUETOOTH" />
diff --git a/packages/SystemUI/res/drawable-hdpi/ic_qs_airplane_enabled.png b/packages/SystemUI/res/drawable-hdpi/ic_qs_airplane_enabled.png
new file mode 100644
index 0000000..c47f70a
--- /dev/null
+++ b/packages/SystemUI/res/drawable-hdpi/ic_qs_airplane_enabled.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/ic_qs_airplane_normal.png b/packages/SystemUI/res/drawable-hdpi/ic_qs_airplane_normal.png
new file mode 100644
index 0000000..c87e162
--- /dev/null
+++ b/packages/SystemUI/res/drawable-hdpi/ic_qs_airplane_normal.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/ic_qs_bluetooth_enabled.png b/packages/SystemUI/res/drawable-hdpi/ic_qs_bluetooth_enabled.png
new file mode 100644
index 0000000..8baece6
--- /dev/null
+++ b/packages/SystemUI/res/drawable-hdpi/ic_qs_bluetooth_enabled.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/ic_qs_bluetooth_normal.png b/packages/SystemUI/res/drawable-hdpi/ic_qs_bluetooth_normal.png
new file mode 100644
index 0000000..03f8b9c
--- /dev/null
+++ b/packages/SystemUI/res/drawable-hdpi/ic_qs_bluetooth_normal.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/ic_qs_rssi_enabled.png b/packages/SystemUI/res/drawable-hdpi/ic_qs_rssi_enabled.png
new file mode 100644
index 0000000..8348455
--- /dev/null
+++ b/packages/SystemUI/res/drawable-hdpi/ic_qs_rssi_enabled.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/ic_qs_rssi_normal.png b/packages/SystemUI/res/drawable-hdpi/ic_qs_rssi_normal.png
new file mode 100644
index 0000000..0dfcfd2
--- /dev/null
+++ b/packages/SystemUI/res/drawable-hdpi/ic_qs_rssi_normal.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/ic_qs_wifi_enabled.png b/packages/SystemUI/res/drawable-hdpi/ic_qs_wifi_enabled.png
new file mode 100644
index 0000000..0276e42
--- /dev/null
+++ b/packages/SystemUI/res/drawable-hdpi/ic_qs_wifi_enabled.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/ic_qs_wifi_normal.png b/packages/SystemUI/res/drawable-hdpi/ic_qs_wifi_normal.png
new file mode 100644
index 0000000..112279a
--- /dev/null
+++ b/packages/SystemUI/res/drawable-hdpi/ic_qs_wifi_normal.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/ic_qs_airplane_enabled.png b/packages/SystemUI/res/drawable-mdpi/ic_qs_airplane_enabled.png
new file mode 100644
index 0000000..06ed0a8
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/ic_qs_airplane_enabled.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/ic_qs_airplane_normal.png b/packages/SystemUI/res/drawable-mdpi/ic_qs_airplane_normal.png
new file mode 100644
index 0000000..1c83e5b
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/ic_qs_airplane_normal.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/ic_qs_bluetooth_enabled.png b/packages/SystemUI/res/drawable-mdpi/ic_qs_bluetooth_enabled.png
new file mode 100644
index 0000000..9cff183
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/ic_qs_bluetooth_enabled.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/ic_qs_bluetooth_normal.png b/packages/SystemUI/res/drawable-mdpi/ic_qs_bluetooth_normal.png
new file mode 100644
index 0000000..1e6c564
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/ic_qs_bluetooth_normal.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/ic_qs_rssi_enabled.png b/packages/SystemUI/res/drawable-mdpi/ic_qs_rssi_enabled.png
new file mode 100644
index 0000000..afe6e98
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/ic_qs_rssi_enabled.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/ic_qs_rssi_normal.png b/packages/SystemUI/res/drawable-mdpi/ic_qs_rssi_normal.png
new file mode 100644
index 0000000..3bec266
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/ic_qs_rssi_normal.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/ic_qs_wifi_enabled.png b/packages/SystemUI/res/drawable-mdpi/ic_qs_wifi_enabled.png
new file mode 100644
index 0000000..6c94754
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/ic_qs_wifi_enabled.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/ic_qs_wifi_normal.png b/packages/SystemUI/res/drawable-mdpi/ic_qs_wifi_normal.png
new file mode 100644
index 0000000..d6b47fc
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/ic_qs_wifi_normal.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xhdpi/ic_qs_airplane_enabled.png b/packages/SystemUI/res/drawable-xhdpi/ic_qs_airplane_enabled.png
new file mode 100644
index 0000000..7ee9290
--- /dev/null
+++ b/packages/SystemUI/res/drawable-xhdpi/ic_qs_airplane_enabled.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xhdpi/ic_qs_airplane_normal.png b/packages/SystemUI/res/drawable-xhdpi/ic_qs_airplane_normal.png
new file mode 100644
index 0000000..41d7498
--- /dev/null
+++ b/packages/SystemUI/res/drawable-xhdpi/ic_qs_airplane_normal.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xhdpi/ic_qs_bluetooth_enabled.png b/packages/SystemUI/res/drawable-xhdpi/ic_qs_bluetooth_enabled.png
new file mode 100644
index 0000000..8811e62
--- /dev/null
+++ b/packages/SystemUI/res/drawable-xhdpi/ic_qs_bluetooth_enabled.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xhdpi/ic_qs_bluetooth_normal.png b/packages/SystemUI/res/drawable-xhdpi/ic_qs_bluetooth_normal.png
new file mode 100644
index 0000000..0026596
--- /dev/null
+++ b/packages/SystemUI/res/drawable-xhdpi/ic_qs_bluetooth_normal.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xhdpi/ic_qs_rssi_enabled.png b/packages/SystemUI/res/drawable-xhdpi/ic_qs_rssi_enabled.png
new file mode 100644
index 0000000..4a2789d
--- /dev/null
+++ b/packages/SystemUI/res/drawable-xhdpi/ic_qs_rssi_enabled.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xhdpi/ic_qs_rssi_normal.png b/packages/SystemUI/res/drawable-xhdpi/ic_qs_rssi_normal.png
new file mode 100644
index 0000000..ee4b21f
--- /dev/null
+++ b/packages/SystemUI/res/drawable-xhdpi/ic_qs_rssi_normal.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xhdpi/ic_qs_wifi_enabled.png b/packages/SystemUI/res/drawable-xhdpi/ic_qs_wifi_enabled.png
new file mode 100644
index 0000000..114ee29
--- /dev/null
+++ b/packages/SystemUI/res/drawable-xhdpi/ic_qs_wifi_enabled.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xhdpi/ic_qs_wifi_normal.png b/packages/SystemUI/res/drawable-xhdpi/ic_qs_wifi_normal.png
new file mode 100644
index 0000000..0719b21
--- /dev/null
+++ b/packages/SystemUI/res/drawable-xhdpi/ic_qs_wifi_normal.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable/ic_qs_battery.xml b/packages/SystemUI/res/drawable/ic_qs_battery.xml
new file mode 100644
index 0000000..4e2a265
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_qs_battery.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2012 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.
+-->
+<clip
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:drawable="@drawable/stat_sys_battery_100"
+    android:clipOrientation="vertical"
+    android:gravity="bottom" />
diff --git a/packages/SystemUI/res/layout/quick_settings.xml b/packages/SystemUI/res/layout/quick_settings.xml
index 8c6258a..d89f279 100644
--- a/packages/SystemUI/res/layout/quick_settings.xml
+++ b/packages/SystemUI/res/layout/quick_settings.xml
@@ -19,14 +19,18 @@
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:id="@+id/settings_panel"
-    android:background="#80000080"
+    android:background="@drawable/notification_panel_bg"
     >
-    <ImageView
+    <!-- TODO: Put into ScrollView -->
+    <com.android.systemui.statusbar.phone.QuickSettingsContainerView
+        android:id="@+id/quick_settings_container"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:scaleType="centerInside"
-        android:src="@drawable/qs_coming_soon"
-        android:padding="4dp"
+        android:paddingBottom="@dimen/quick_settings_container_padding"
+        android:paddingLeft="@dimen/quick_settings_container_padding"
+        android:paddingRight="@dimen/quick_settings_container_padding"
+        android:animateLayoutChanges="true"
+        android:columnCount="@integer/quick_settings_num_columns"
         />
     <LinearLayout android:id="@+id/handle"
         android:layout_width="match_parent"
diff --git a/packages/SystemUI/res/layout/quick_settings_tile.xml b/packages/SystemUI/res/layout/quick_settings_tile.xml
new file mode 100644
index 0000000..a571393
--- /dev/null
+++ b/packages/SystemUI/res/layout/quick_settings_tile.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2012 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.
+-->
+<com.android.systemui.statusbar.phone.QuickSettingsTileView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="wrap_content"
+    android:layout_height="@dimen/quick_settings_cell_height"
+    android:background="#1B1D1B" />
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/quick_settings_tile_airplane.xml b/packages/SystemUI/res/layout/quick_settings_tile_airplane.xml
new file mode 100644
index 0000000..3e3a9c2
--- /dev/null
+++ b/packages/SystemUI/res/layout/quick_settings_tile_airplane.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2012 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.
+-->
+<TextView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/airplane_mode_textview"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:layout_gravity="center"
+    android:gravity="center"
+    android:text="@string/quick_settings_airplane_mode_label"
+    android:textAppearance="@style/TextAppearance.QuickSettings.TileView"
+    />
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/quick_settings_tile_battery.xml b/packages/SystemUI/res/layout/quick_settings_tile_battery.xml
new file mode 100644
index 0000000..680a1bb
--- /dev/null
+++ b/packages/SystemUI/res/layout/quick_settings_tile_battery.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2012 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.
+-->
+<TextView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/battery_textview"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:layout_gravity="center"
+    android:gravity="center"
+    android:drawableTop="@drawable/ic_qs_battery"
+    android:text="@string/quick_settings_battery_label"
+    android:textAppearance="@style/TextAppearance.QuickSettings.TileView"
+    />
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/quick_settings_tile_bluetooth.xml b/packages/SystemUI/res/layout/quick_settings_tile_bluetooth.xml
new file mode 100644
index 0000000..4472484
--- /dev/null
+++ b/packages/SystemUI/res/layout/quick_settings_tile_bluetooth.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2012 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.
+-->
+<TextView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/bluetooth_textview"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:layout_gravity="center"
+    android:gravity="center"
+    android:text="@string/quick_settings_bluetooth_label"
+    android:textAppearance="@style/TextAppearance.QuickSettings.TileView"
+    />
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/quick_settings_tile_brightness.xml b/packages/SystemUI/res/layout/quick_settings_tile_brightness.xml
new file mode 100644
index 0000000..216930d
--- /dev/null
+++ b/packages/SystemUI/res/layout/quick_settings_tile_brightness.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2012 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.
+-->
+<TextView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:layout_gravity="center"
+    android:gravity="center"
+    android:drawableTop="@drawable/ic_sysbar_brightness"
+    android:text="@string/quick_settings_brightness_label"
+    android:textAppearance="@style/TextAppearance.QuickSettings.TileView"
+    />
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/quick_settings_tile_ime.xml b/packages/SystemUI/res/layout/quick_settings_tile_ime.xml
new file mode 100644
index 0000000..93db6db
--- /dev/null
+++ b/packages/SystemUI/res/layout/quick_settings_tile_ime.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2012 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.
+-->
+<TextView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:gravity="center"
+    android:text="@string/quick_settings_ime_label"
+    android:singleLine="true"
+    />
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/quick_settings_tile_location.xml b/packages/SystemUI/res/layout/quick_settings_tile_location.xml
new file mode 100644
index 0000000..1a40642
--- /dev/null
+++ b/packages/SystemUI/res/layout/quick_settings_tile_location.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2012 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.
+-->
+<TextView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/location_textview"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:layout_gravity="center"
+    android:gravity="center"
+    android:drawableTop="@drawable/stat_sys_gps_acquiring"
+    android:text="@string/quick_settings_location_label"
+    android:textAppearance="@style/TextAppearance.QuickSettings.TileView"
+    />
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/quick_settings_tile_media.xml b/packages/SystemUI/res/layout/quick_settings_tile_media.xml
new file mode 100644
index 0000000..0810d02
--- /dev/null
+++ b/packages/SystemUI/res/layout/quick_settings_tile_media.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2012 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.
+-->
+<TextView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:gravity="center"
+    android:text="@string/quick_settings_media_device_label"
+    android:singleLine="true"
+    />
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/quick_settings_tile_rssi.xml b/packages/SystemUI/res/layout/quick_settings_tile_rssi.xml
new file mode 100644
index 0000000..3e541cb
--- /dev/null
+++ b/packages/SystemUI/res/layout/quick_settings_tile_rssi.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2012 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.
+-->
+<TextView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/rssi_textview"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:layout_gravity="center"
+    android:gravity="center"
+    android:text="@string/quick_settings_rssi_label"
+    android:textAppearance="@style/TextAppearance.QuickSettings.TileView"
+    />
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/quick_settings_tile_settings.xml b/packages/SystemUI/res/layout/quick_settings_tile_settings.xml
new file mode 100644
index 0000000..1c2f827
--- /dev/null
+++ b/packages/SystemUI/res/layout/quick_settings_tile_settings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2012 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.
+-->
+<TextView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:layout_gravity="center"
+    android:gravity="center"
+    android:drawableTop="@drawable/ic_notify_quicksettings"
+    android:text="@string/quick_settings_settings_label"
+    android:textAppearance="@style/TextAppearance.QuickSettings.TileView"
+    />
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/quick_settings_tile_time.xml b/packages/SystemUI/res/layout/quick_settings_tile_time.xml
new file mode 100644
index 0000000..ab0c52d
--- /dev/null
+++ b/packages/SystemUI/res/layout/quick_settings_tile_time.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2012 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.
+-->
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:layout_gravity="center"
+    android:orientation="vertical">
+    <com.android.systemui.statusbar.policy.Clock
+        android:textAppearance="@style/TextAppearance.QuickSettings.Clock"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_horizontal"
+        android:singleLine="true"
+        />
+    <com.android.systemui.statusbar.policy.QuickSettingsDateView
+        android:textAppearance="@style/TextAppearance.QuickSettings.Date"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_horizontal"
+        />
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/quick_settings_tile_user.xml b/packages/SystemUI/res/layout/quick_settings_tile_user.xml
new file mode 100644
index 0000000..8edc978
--- /dev/null
+++ b/packages/SystemUI/res/layout/quick_settings_tile_user.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2012 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.
+-->
+<TextView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/user_textview"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:layout_gravity="center_horizontal|bottom"
+    android:gravity="center"
+    android:text="@string/quick_settings_user_label"
+    android:textAppearance="@style/TextAppearance.QuickSettings.TileView"
+    android:background="#33000000"
+    />
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/quick_settings_tile_wifi.xml b/packages/SystemUI/res/layout/quick_settings_tile_wifi.xml
new file mode 100644
index 0000000..42eb45a
--- /dev/null
+++ b/packages/SystemUI/res/layout/quick_settings_tile_wifi.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2012 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.
+-->
+<TextView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/wifi_textview"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:layout_gravity="center"
+    android:gravity="center"
+    android:text="@string/quick_settings_wifi_label"
+    android:textAppearance="@style/TextAppearance.QuickSettings.TileView"
+    />
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/quick_settings_tile_wifi_display.xml b/packages/SystemUI/res/layout/quick_settings_tile_wifi_display.xml
new file mode 100644
index 0000000..3a330d8
--- /dev/null
+++ b/packages/SystemUI/res/layout/quick_settings_tile_wifi_display.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2012 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.
+-->
+<TextView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:gravity="center"
+    android:text="@string/quick_settings_wifi_display_label"
+    android:singleLine="true"
+    android:ellipsize="marquee"
+    android:fadingEdge="horizontal"
+    />
\ No newline at end of file
diff --git a/packages/SystemUI/res/values-land/config.xml b/packages/SystemUI/res/values-land/config.xml
index bbae18d..6476d88 100644
--- a/packages/SystemUI/res/values-land/config.xml
+++ b/packages/SystemUI/res/values-land/config.xml
@@ -23,5 +23,11 @@
     <!-- Whether we're using the tablet-optimized recents interface (we use this
      value at runtime for some things) -->
     <integer name="status_bar_recents_bg_gradient_degrees">90</integer>
+
+    <!-- The number of columns in the QuickSettings -->
+    <integer name="quick_settings_num_columns">6</integer>
+
+    <!-- The number of columns that the top level tiles span in the QuickSettings -->
+    <integer name="quick_settings_user_time_settings_tile_span">2</integer>
 </resources>
 
diff --git a/packages/SystemUI/res/values-land/dimens.xml b/packages/SystemUI/res/values-land/dimens.xml
index e7c8b1f..ab71371 100644
--- a/packages/SystemUI/res/values-land/dimens.xml
+++ b/packages/SystemUI/res/values-land/dimens.xml
@@ -37,4 +37,7 @@
     <!-- Where to place the app icon over the thumbnail -->
     <dimen name="status_bar_recents_app_icon_left_margin">8dp</dimen>
     <dimen name="status_bar_recents_app_icon_top_margin">8dp</dimen>
+
+    <!-- The fixed height of each tile -->
+    <dimen name="quick_settings_cell_height">100dp</dimen>
 </resources>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 34e58a3..734e68c 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -81,5 +81,11 @@
 
     <!-- Min alpha % that recent items will fade to while being dismissed -->
     <integer name="config_recent_item_min_alpha">3</integer>
+
+    <!-- The number of columns in the QuickSettings -->
+    <integer name="quick_settings_num_columns">3</integer>
+
+    <!-- The number of columns that the top level tiles span in the QuickSettings -->
+    <integer name="quick_settings_user_time_settings_tile_span">1</integer>
 </resources>
 
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 0d7cdb1..63ce2a9 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -174,4 +174,14 @@
 
     <!-- The distance you can pull a notificaiton before it pops open -->
     <dimen name="one_finger_pop_limit">32dp</dimen>
+
+    <!-- The amount of padding around the QuickSettings tiles -->
+    <dimen name="quick_settings_container_padding">12dp</dimen>
+
+    <!-- The fixed height of each tile -->
+    <dimen name="quick_settings_cell_height">110dp</dimen>
+
+    <!-- The padding between each tile within the QuickSettings layout -->
+    <dimen name="quick_settings_cell_gap">5dp</dimen>
+
 </resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 43070c6..b83e71c 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -401,4 +401,34 @@
     <!-- Name of the launcher shortcut icon that allows dreams to be started immediately [CHAR LIMIT=20] -->
     <string name="start_dreams">Start dreams</string>
 
+    <!-- QuickSettings: Airplane mode [CHAR LIMIT=NONE] -->
+    <string name="quick_settings_airplane_mode_label">Airplane mode</string>
+    <!-- QuickSettings: Battery [CHAR LIMIT=NONE] -->
+    <string name="quick_settings_battery_label">Battery</string>
+    <!-- QuickSettings: Bluetooth [CHAR LIMIT=NONE] -->
+    <string name="quick_settings_bluetooth_label">Bluetooth</string>
+    <!-- QuickSettings: Brightness [CHAR LIMIT=NONE] -->
+    <string name="quick_settings_brightness_label">Brightness</string>
+    <!-- QuickSettings: IME [CHAR LIMIT=NONE] -->
+    <string name="quick_settings_ime_label">IME</string>
+    <!-- QuickSettings: Location [CHAR LIMIT=NONE] -->
+    <string name="quick_settings_location_label">Location in use</string>
+    <!-- QuickSettings: Media device [CHAR LIMIT=NONE] -->
+    <string name="quick_settings_media_device_label">Media device</string>
+    <!-- QuickSettings: RSSI [CHAR LIMIT=NONE] -->
+    <string name="quick_settings_rssi_label">RSSI</string>
+    <!-- QuickSettings: RSSI (No network) [CHAR LIMIT=NONE] -->
+    <string name="quick_settings_rssi_emergency_only">Emergency Calls Only</string>
+    <!-- QuickSettings: Settings [CHAR LIMIT=NONE] -->
+    <string name="quick_settings_settings_label">Settings</string>
+    <!-- QuickSettings: Time [CHAR LIMIT=NONE] -->
+    <string name="quick_settings_time_label">Time</string>
+    <!-- QuickSettings: User [CHAR LIMIT=NONE] -->
+    <string name="quick_settings_user_label">Me</string>
+    <!-- QuickSettings: Wifi [CHAR LIMIT=NONE] -->
+    <string name="quick_settings_wifi_label">Wifi</string>
+    <!-- QuickSettings: Wifi (No network) [CHAR LIMIT=NONE] -->
+    <string name="quick_settings_wifi_no_network">No Network</string>
+    <!-- QuickSettings: Wifi display [CHAR LIMIT=NONE] -->
+    <string name="quick_settings_wifi_display_label">Wifi display</string>
 </resources>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 2564003..34bd627 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -70,6 +70,28 @@
     <style name="TextAppearance.StatusBar.Expanded.Network.EmergencyOnly">
     </style>
 
+    <style name="TextAppearance" />
+    <style name="TextAppearance.QuickSettings" />
+
+    <style name="TextAppearance.QuickSettings.TileView">
+        <item name="android:padding">8dp</item>
+        <item name="android:textSize">13dp</item>
+        <item name="android:textStyle">normal</item>
+        <item name="android:textColor">#ff8d908c</item>
+        <item name="android:singleLine">true</item>
+        <item name="android:ellipsize">marquee</item>
+        <item name="android:fadingEdge">horizontal</item>
+    </style>
+
+    <style name="TextAppearance.QuickSettings.Clock" parent="@style/TextAppearance.QuickSettings.TileView">
+        <item name="android:textSize">24dp</item>
+        <item name="android:textColor">@android:color/holo_blue_light</item>
+    </style>
+
+    <style name="TextAppearance.QuickSettings.Date" parent="@style/TextAppearance.QuickSettings.TileView">
+        <item name="android:textSize">14dp</item>
+    </style>
+
     <style name="Animation" />
 
     <style name="Animation.ShirtPocketPanel">
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
index 2f551e1..bee63ee 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
@@ -12,6 +12,10 @@
 import android.widget.FrameLayout;
 
 import com.android.systemui.R;
+import com.android.systemui.statusbar.policy.BatteryController;
+import com.android.systemui.statusbar.policy.BluetoothController;
+import com.android.systemui.statusbar.policy.LocationController;
+import com.android.systemui.statusbar.policy.NetworkController;
 
 public class PanelView extends FrameLayout {
     public static final boolean DEBUG = false;
@@ -356,6 +360,11 @@
         mBar = panelBar;
     }
 
+    public void setup(NetworkController network, BluetoothController bt, BatteryController batt,
+            LocationController location) {
+        // To be implemented by classes extending PanelView
+    }
+
     public void collapse() {
         // TODO: abort animation or ongoing touch
         if (mExpandedHeight > 0) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index 5646c55..a5d4a8e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -19,11 +19,10 @@
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
+import android.animation.LayoutTransition;
 import android.animation.ObjectAnimator;
 import android.app.ActivityManager;
 import android.app.ActivityManagerNative;
-import android.app.Dialog;
-import android.app.KeyguardManager;
 import android.app.Notification;
 import android.app.PendingIntent;
 import android.app.StatusBarManager;
@@ -31,7 +30,6 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.graphics.Canvas;
 import android.graphics.ColorFilter;
@@ -54,13 +52,11 @@
 import android.util.Slog;
 import android.view.Display;
 import android.view.Gravity;
-import android.view.IWindowManager;
-import android.view.KeyEvent;
+import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.VelocityTracker;
 import android.view.View;
 import android.view.ViewGroup;
-import android.view.WindowManagerGlobal;
 import android.view.ViewGroup.LayoutParams;
 import android.view.WindowManager;
 import android.view.animation.AccelerateInterpolator;
@@ -76,7 +72,6 @@
 import com.android.internal.statusbar.StatusBarIcon;
 import com.android.internal.statusbar.StatusBarNotification;
 import com.android.systemui.R;
-import com.android.systemui.recent.RecentTasksLoader;
 import com.android.systemui.statusbar.BaseStatusBar;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.GestureRecorder;
@@ -86,6 +81,7 @@
 import com.android.systemui.statusbar.SignalClusterView;
 import com.android.systemui.statusbar.StatusBarIconView;
 import com.android.systemui.statusbar.policy.BatteryController;
+import com.android.systemui.statusbar.policy.BluetoothController;
 import com.android.systemui.statusbar.policy.DateView;
 import com.android.systemui.statusbar.policy.IntruderAlertView;
 import com.android.systemui.statusbar.policy.LocationController;
@@ -139,6 +135,7 @@
     PhoneStatusBarPolicy mIconPolicy;
 
     // These are no longer handled by the policy, because we need custom strategies for them
+    BluetoothController mBluetoothController;
     BatteryController mBatteryController;
     LocationController mLocationController;
     NetworkController mNetworkController;
@@ -317,9 +314,6 @@
                   View.STATUS_BAR_DISABLE_NOTIFICATION_TICKER
                 | (mNotificationPanelIsFullScreenWidth ? 0 : View.STATUS_BAR_DISABLE_SYSTEM_INFO));
 
-        // quick settings (WIP)
-        mSettingsPanel = (PanelView) mStatusBarWindow.findViewById(R.id.settings_panel);
-
         if (!ActivityManager.isHighEndGfx()) {
             mStatusBarWindow.setBackground(null);
             mNotificationPanel.setBackground(new FastColorDrawable(context.getResources().getColor(
@@ -394,9 +388,11 @@
         mBatteryController = new BatteryController(mContext);
         mBatteryController.addIconView((ImageView)mStatusBarView.findViewById(R.id.battery));
         mNetworkController = new NetworkController(mContext);
+        mBluetoothController = new BluetoothController(mContext);
         final SignalClusterView signalCluster =
                 (SignalClusterView)mStatusBarView.findViewById(R.id.signal_cluster);
 
+
         mNetworkController.addSignalCluster(signalCluster);
         signalCluster.setNetworkController(mNetworkController);
 
@@ -434,6 +430,12 @@
             });
         }
 
+        // Quick Settings (WIP)
+        mSettingsPanel = (PanelView) mStatusBarWindow.findViewById(R.id.settings_panel);
+        mSettingsPanel.setBar(mStatusBarView);
+        mSettingsPanel.setup(mNetworkController, mBluetoothController, mBatteryController,
+                mLocationController);
+
 //        final ImageView wimaxRSSI =
 //                (ImageView)sb.findViewById(R.id.wimax_signal);
 //        if (wimaxRSSI != null) {
@@ -1844,6 +1846,10 @@
         if (mClearButton instanceof TextView) {
             ((TextView)mClearButton).setText(context.getText(R.string.status_bar_clear_all_button));
         }
+
+        // Update the QuickSettings container
+        ((SettingsPanelView) mSettingsPanel).updateResources();
+
         loadDimens();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettings.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettings.java
new file mode 100644
index 0000000..7a0e4d6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettings.java
@@ -0,0 +1,582 @@
+/*
+ * Copyright (C) 2012 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.systemui.statusbar.phone;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothAdapter.BluetoothStateChangeCallback;
+import android.content.Context;
+import android.content.CursorLoader;
+import android.content.Intent;
+import android.content.Loader;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.graphics.drawable.ClipDrawable;
+import android.net.Uri;
+import android.provider.ContactsContract;
+import android.provider.Settings;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+
+import com.android.systemui.R;
+import com.android.systemui.statusbar.phone.QuickSettingsModel.State;
+import com.android.systemui.statusbar.policy.BatteryController;
+import com.android.systemui.statusbar.policy.BluetoothController;
+import com.android.systemui.statusbar.policy.LocationController;
+import com.android.systemui.statusbar.policy.NetworkController;
+
+class QuickSettingsModel implements BluetoothStateChangeCallback,
+        NetworkController.NetworkSignalChangedCallback,
+        BatteryController.BatteryStateChangeCallback,
+        LocationController.LocationGpsStateChangeCallback {
+
+    /** Represents the state of a given attribute. */
+    static class State {
+        int iconId;
+        String label;
+        boolean enabled;
+    }
+    static class BatteryState extends State {
+        int batteryLevel;
+        boolean pluggedIn;
+    }
+
+    /** The callback to update a given tile. */
+    interface RefreshCallback {
+        public void refreshView(QuickSettingsTileView view, State state);
+    }
+
+    private Context mContext;
+
+    private QuickSettingsTileView mUserTile;
+    private RefreshCallback mUserCallback;
+    private State mUserState = new State();
+
+    private QuickSettingsTileView mAirplaneModeTile;
+    private RefreshCallback mAirplaneModeCallback;
+    private State mAirplaneModeState = new State();
+
+    private QuickSettingsTileView mWifiTile;
+    private RefreshCallback mWifiCallback;
+    private State mWifiState = new State();
+
+    private QuickSettingsTileView mRSSITile;
+    private RefreshCallback mRSSICallback;
+    private State mRSSIState = new State();
+
+    private QuickSettingsTileView mBluetoothTile;
+    private RefreshCallback mBluetoothCallback;
+    private State mBluetoothState = new State();
+
+    private QuickSettingsTileView mBatteryTile;
+    private RefreshCallback mBatteryCallback;
+    private BatteryState mBatteryState = new BatteryState();
+
+    private QuickSettingsTileView mLocationTile;
+    private RefreshCallback mLocationCallback;
+    private State mLocationState = new State();
+
+    public QuickSettingsModel(Context context) {
+        mContext = context;
+    }
+
+    // User
+    void addUserTile(QuickSettingsTileView view, RefreshCallback cb) {
+        mUserTile = view;
+        mUserCallback = cb;
+        mUserCallback.refreshView(mUserTile, mUserState);
+    }
+    void setUserTileInfo(String name) {
+        mUserState.label = name;
+        mUserCallback.refreshView(mUserTile, mUserState);
+    }
+
+    // Airplane Mode
+    void addAirplaneModeTile(QuickSettingsTileView view, RefreshCallback cb) {
+        mAirplaneModeTile = view;
+        mAirplaneModeTile.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                if (mAirplaneModeState.enabled) {
+                    setAirplaneModeState(false);
+                } else {
+                    setAirplaneModeState(true);
+                }
+            }
+        });
+        mAirplaneModeCallback = cb;
+        mAirplaneModeCallback.refreshView(mAirplaneModeTile, mAirplaneModeState);
+    }
+    private void setAirplaneModeState(boolean enabled) {
+        // TODO: Sets the view to be "awaiting" if not already awaiting
+
+        // Change the system setting
+        Settings.System.putInt(mContext.getContentResolver(), Settings.System.AIRPLANE_MODE_ON,
+                                enabled ? 1 : 0);
+
+        // TODO: Update the UI to reflect system setting
+        // mCheckBoxPref.setChecked(enabled);
+
+        // Post the intent
+        Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+        intent.putExtra("state", enabled);
+        mContext.sendBroadcast(intent);
+    }
+    // NetworkSignalChanged callback
+    @Override
+    public void onAirplaneModeChanged(boolean enabled) {
+        // TODO: If view is in awaiting state, disable
+        Resources r = mContext.getResources();
+        mAirplaneModeState.enabled = enabled;
+        mAirplaneModeState.iconId = (enabled ?
+                R.drawable.ic_qs_airplane_enabled :
+                R.drawable.ic_qs_airplane_normal);
+        mAirplaneModeCallback.refreshView(mAirplaneModeTile, mAirplaneModeState);
+    }
+
+    // Wifi
+    void addWifiTile(QuickSettingsTileView view, RefreshCallback cb) {
+        mWifiTile = view;
+        mWifiCallback = cb;
+        mWifiCallback.refreshView(mWifiTile, mWifiState);
+    }
+    // NetworkSignalChanged callback
+    @Override
+    public void onWifiSignalChanged(boolean enabled, String description) {
+        // TODO: If view is in awaiting state, disable
+        Resources r = mContext.getResources();
+        // TODO: Check if wifi is enabled
+        mWifiState.enabled = enabled;
+        mWifiState.iconId = (enabled ?
+                R.drawable.ic_qs_wifi_enabled :
+                R.drawable.ic_qs_wifi_normal);
+        mWifiState.label = (enabled ?
+                description :
+                r.getString(R.string.quick_settings_wifi_no_network));
+        mWifiCallback.refreshView(mWifiTile, mWifiState);
+    }
+
+    // RSSI
+    void addRSSITile(QuickSettingsTileView view, RefreshCallback cb) {
+        mRSSITile = view;
+        mRSSICallback = cb;
+        mRSSICallback.refreshView(mRSSITile, mRSSIState);
+    }
+    private void setRSSIState(boolean enabled) {
+        // TODO: Set RSSI enabled
+        // TODO: Sets the view to be "awaiting" if not already awaiting
+    }
+    // NetworkSignalChanged callback
+    @Override
+    public void onMobileDataSignalChanged(boolean enabled, String description) {
+        // TODO: If view is in awaiting state, disable
+        Resources r = mContext.getResources();
+        // TODO: Check if RSSI is enabled
+        mRSSIState.enabled = enabled;
+        mRSSIState.iconId = (enabled ?
+                R.drawable.ic_qs_rssi_enabled :
+                R.drawable.ic_qs_rssi_normal);
+        mRSSIState.label = (enabled ?
+                description :
+                r.getString(R.string.quick_settings_rssi_emergency_only));
+        mRSSICallback.refreshView(mRSSITile, mRSSIState);
+    }
+
+    // Bluetooth
+    void addBluetoothTile(QuickSettingsTileView view, RefreshCallback cb) {
+        mBluetoothTile = view;
+        mBluetoothTile.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                if (mBluetoothState.enabled) {
+                    setBluetoothState(false);
+                } else {
+                    setBluetoothState(true);
+                }
+            }
+        });
+        mBluetoothCallback = cb;
+
+        final BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+        onBluetoothStateChange(adapter.isEnabled());
+    }
+    private void setBluetoothState(boolean enabled) {
+        // TODO: Sets the view to be "awaiting" if not already awaiting
+        final BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+        if (adapter != null) {
+            if (enabled) {
+                adapter.enable();
+            } else {
+                adapter.disable();
+            }
+        }
+    }
+    // BluetoothController callback
+    @Override
+    public void onBluetoothStateChange(boolean on) {
+        // TODO: If view is in awaiting state, disable
+        Resources r = mContext.getResources();
+        mBluetoothState.enabled = on;
+        if (on) {
+            mBluetoothState.iconId = R.drawable.ic_qs_bluetooth_enabled;
+        } else {
+            mBluetoothState.iconId = R.drawable.ic_qs_bluetooth_normal;
+        }
+        mBluetoothCallback.refreshView(mBluetoothTile, mBluetoothState);
+    }
+
+    // Battery
+    void addBatteryTile(QuickSettingsTileView view, RefreshCallback cb) {
+        mBatteryTile = view;
+        mBatteryCallback = cb;
+        mBatteryCallback.refreshView(mBatteryTile, mBatteryState);
+    }
+    // BatteryController callback
+    @Override
+    public void onBatteryLevelChanged(int level, boolean pluggedIn) {
+        mBatteryState.batteryLevel = level;
+        mBatteryState.pluggedIn = pluggedIn;
+        mBatteryCallback.refreshView(mBatteryTile, mBatteryState);
+    }
+
+    // Location
+    void addLocationTile(QuickSettingsTileView view, RefreshCallback cb) {
+        mLocationTile = view;
+        mLocationCallback = cb;
+        mLocationCallback.refreshView(mLocationTile, mLocationState);
+        disableLocationTile();
+    }
+    private void enableLocationTile() {
+        mLocationTile.setVisibility(View.VISIBLE);
+    }
+    private void disableLocationTile() {
+        mLocationTile.setVisibility(View.GONE);
+    }
+    // LocationController callback
+    @Override
+    public void onLocationGpsStateChanged(boolean inUse, String description) {
+        if (inUse) {
+            mLocationState.enabled = inUse;
+            mLocationState.label = description;
+            mLocationCallback.refreshView(mLocationTile, mLocationState);
+            enableLocationTile();
+        } else {
+            disableLocationTile();
+        }
+    }
+
+}
+
+/**
+ *
+ */
+class QuickSettings {
+
+    private Context mContext;
+    private PanelBar mBar;
+    private QuickSettingsModel mModel;
+    private QuickSettingsContainerView mContainerView;
+
+    private CursorLoader mUserInfoLoader;
+
+    // The set of QuickSettingsTiles that have dynamic spans (and need to be updated on
+    // configuration change)
+    private final ArrayList<QuickSettingsTileView> mDynamicSpannedTiles =
+            new ArrayList<QuickSettingsTileView>();
+
+    public QuickSettings(Context context, QuickSettingsContainerView container) {
+        mContext = context;
+        mModel = new QuickSettingsModel(context);
+        mContainerView = container;
+
+        setupQuickSettings();
+        updateResources();
+    }
+
+    void setBar(PanelBar bar) {
+        mBar = bar;
+    }
+
+    void setup(NetworkController networkController, BluetoothController bluetoothController,
+            BatteryController batteryController, LocationController locationController) {
+        networkController.addNetworkSignalChangedCallback(mModel);
+        bluetoothController.addStateChangedCallback(mModel);
+        batteryController.addStateChangedCallback(mModel);
+        locationController.addStateChangedCallback(mModel);
+    }
+
+    private void queryForUserInformation() {
+        Uri userContactUri = Uri.withAppendedPath(
+            ContactsContract.Profile.CONTENT_URI,
+            ContactsContract.Contacts.Data.CONTENT_DIRECTORY);
+
+        String[] selectArgs = {
+            ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME,
+            ContactsContract.CommonDataKinds.Photo.PHOTO
+        };
+        String where = String.format("(%s = ? OR %s = ?) AND %s IS NULL",
+            ContactsContract.Contacts.Data.MIMETYPE,
+            ContactsContract.Contacts.Data.MIMETYPE,
+            ContactsContract.RawContacts.ACCOUNT_TYPE);
+        String[] whereArgs = {
+            ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE,
+            ContactsContract.CommonDataKinds.Photo.CONTENT_ITEM_TYPE
+        };
+
+        mUserInfoLoader = new CursorLoader(mContext, userContactUri, selectArgs, where, whereArgs,
+                null);
+        mUserInfoLoader.registerListener(0,
+                new Loader.OnLoadCompleteListener<Cursor>() {
+                    @Override
+                    public void onLoadComplete(Loader<Cursor> loader,
+                            Cursor cursor) {
+                        if (cursor.moveToFirst()) {
+                            String name = cursor.getString(0); // DISPLAY_NAME
+                            mModel.setUserTileInfo(name);
+                            /*
+                            byte[] photoData = cursor.getBlob(0);
+                            Bitmap b =
+                                BitmapFactory.decodeByteArray(photoData, 0, photoData.length);
+                             */
+                        }
+                        mUserInfoLoader.stopLoading();
+                    }
+                });
+        mUserInfoLoader.startLoading();
+    }
+
+    private void setupQuickSettings() {
+        // Setup the tiles that we are going to be showing (including the temporary ones)
+        LayoutInflater inflater = LayoutInflater.from(mContext);
+
+        addUserTiles(mContainerView, inflater);
+        addSystemTiles(mContainerView, inflater);
+        addTemporaryTiles(mContainerView, inflater);
+
+        queryForUserInformation();
+    }
+
+    private void addUserTiles(ViewGroup parent, LayoutInflater inflater) {
+        QuickSettingsTileView userTile = (QuickSettingsTileView)
+                inflater.inflate(R.layout.quick_settings_tile, parent, false);
+        userTile.setContent(R.layout.quick_settings_tile_user, inflater);
+        mModel.addUserTile(userTile, new QuickSettingsModel.RefreshCallback() {
+            @Override
+            public void refreshView(QuickSettingsTileView view, State state) {
+                TextView tv = (TextView) view.findViewById(R.id.user_textview);
+                tv.setText(state.label);
+            }
+        });
+        parent.addView(userTile);
+        mDynamicSpannedTiles.add(userTile);
+
+        // Time tile
+        QuickSettingsTileView timeTile = (QuickSettingsTileView)
+                inflater.inflate(R.layout.quick_settings_tile, parent, false);
+        timeTile.setContent(R.layout.quick_settings_tile_time, inflater);
+        parent.addView(timeTile);
+        mDynamicSpannedTiles.add(timeTile);
+
+        // Settings tile
+        QuickSettingsTileView settingsTile = (QuickSettingsTileView)
+                inflater.inflate(R.layout.quick_settings_tile, parent, false);
+        settingsTile.setContent(R.layout.quick_settings_tile_settings, inflater);
+        settingsTile.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                Intent intent = new Intent(android.provider.Settings.ACTION_SETTINGS);
+                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+                mContext.startActivity(intent);
+                mBar.collapseAllPanels(true);
+            }
+        });
+        parent.addView(settingsTile);
+        mDynamicSpannedTiles.add(settingsTile);
+    }
+
+    private void addSystemTiles(ViewGroup parent, LayoutInflater inflater) {
+        // Wi-fi
+        QuickSettingsTileView wifiTile = (QuickSettingsTileView)
+                inflater.inflate(R.layout.quick_settings_tile, parent, false);
+        wifiTile.setContent(R.layout.quick_settings_tile_wifi, inflater);
+        wifiTile.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                Intent intent = new Intent(android.provider.Settings.ACTION_WIFI_SETTINGS);
+                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+                mContext.startActivity(intent);
+                mBar.collapseAllPanels(true);
+            }
+        });
+        mModel.addWifiTile(wifiTile, new QuickSettingsModel.RefreshCallback() {
+            @Override
+            public void refreshView(QuickSettingsTileView view, State state) {
+                TextView tv = (TextView) view.findViewById(R.id.wifi_textview);
+                tv.setCompoundDrawablesRelativeWithIntrinsicBounds(0, state.iconId, 0, 0);
+                tv.setText(state.label);
+            }
+        });
+        parent.addView(wifiTile);
+
+        // RSSI
+        QuickSettingsTileView rssiTile = (QuickSettingsTileView)
+                inflater.inflate(R.layout.quick_settings_tile, parent, false);
+        rssiTile.setContent(R.layout.quick_settings_tile_rssi, inflater);
+        rssiTile.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                Intent intent = new Intent(android.provider.Settings.ACTION_WIRELESS_SETTINGS);
+                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+                mContext.startActivity(intent);
+                mBar.collapseAllPanels(true);
+            }
+        });
+        mModel.addRSSITile(rssiTile, new QuickSettingsModel.RefreshCallback() {
+            @Override
+            public void refreshView(QuickSettingsTileView view, State state) {
+                TextView tv = (TextView) view.findViewById(R.id.rssi_textview);
+                tv.setCompoundDrawablesRelativeWithIntrinsicBounds(0, state.iconId, 0, 0);
+                tv.setText(state.label);
+            }
+        });
+        parent.addView(rssiTile);
+
+        // Battery
+        QuickSettingsTileView batteryTile = (QuickSettingsTileView)
+                inflater.inflate(R.layout.quick_settings_tile, parent, false);
+        batteryTile.setContent(R.layout.quick_settings_tile_battery, inflater);
+        batteryTile.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                Intent intent = new Intent(Intent.ACTION_POWER_USAGE_SUMMARY);
+                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+                mContext.startActivity(intent);
+                mBar.collapseAllPanels(true);
+            }
+        });
+        mModel.addBatteryTile(batteryTile, new QuickSettingsModel.RefreshCallback() {
+            @Override
+            public void refreshView(QuickSettingsTileView view, State state) {
+                QuickSettingsModel.BatteryState batteryState =
+                        (QuickSettingsModel.BatteryState) state;
+                TextView tv = (TextView) view.findViewById(R.id.battery_textview);
+                ClipDrawable drawable = (ClipDrawable) tv.getCompoundDrawables()[1];
+                drawable.setLevel((int) (10000 * (batteryState.batteryLevel / 100.0f)));
+                // TODO: use format string
+                tv.setText(batteryState.batteryLevel + "%");
+            }
+        });
+        parent.addView(batteryTile);
+
+        // Airplane Mode
+        QuickSettingsTileView airplaneTile = (QuickSettingsTileView)
+                inflater.inflate(R.layout.quick_settings_tile, parent, false);
+        airplaneTile.setContent(R.layout.quick_settings_tile_airplane, inflater);
+        mModel.addAirplaneModeTile(airplaneTile, new QuickSettingsModel.RefreshCallback() {
+            @Override
+            public void refreshView(QuickSettingsTileView view, State state) {
+                TextView tv = (TextView) view.findViewById(R.id.airplane_mode_textview);
+                tv.setCompoundDrawablesRelativeWithIntrinsicBounds(0, state.iconId, 0, 0);
+            }
+        });
+        parent.addView(airplaneTile);
+
+        // Bluetooth
+        QuickSettingsTileView bluetoothTile = (QuickSettingsTileView)
+                inflater.inflate(R.layout.quick_settings_tile, parent, false);
+        bluetoothTile.setContent(R.layout.quick_settings_tile_bluetooth, inflater);
+        mModel.addBluetoothTile(bluetoothTile, new QuickSettingsModel.RefreshCallback() {
+            @Override
+            public void refreshView(QuickSettingsTileView view, State state) {
+                TextView tv = (TextView) view.findViewById(R.id.bluetooth_textview);
+                tv.setCompoundDrawablesRelativeWithIntrinsicBounds(0, state.iconId, 0, 0);
+            }
+        });
+        parent.addView(bluetoothTile);
+
+        // Brightness
+        QuickSettingsTileView brightnessTile = (QuickSettingsTileView)
+                inflater.inflate(R.layout.quick_settings_tile, parent, false);
+        brightnessTile.setContent(R.layout.quick_settings_tile_brightness, inflater);
+        brightnessTile.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                Intent intent = new Intent(android.provider.Settings.ACTION_DISPLAY_SETTINGS);
+                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+                mContext.startActivity(intent);
+                mBar.collapseAllPanels(true);
+            }
+        });
+        parent.addView(brightnessTile);
+    }
+
+    private void addTemporaryTiles(final ViewGroup parent, final LayoutInflater inflater) {
+        // Location
+        QuickSettingsTileView locationTile = (QuickSettingsTileView)
+                inflater.inflate(R.layout.quick_settings_tile, parent, false);
+        locationTile.setContent(R.layout.quick_settings_tile_location, inflater);
+        locationTile.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                Intent intent =
+                        new Intent(android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS);
+                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+                mContext.startActivity(intent);
+                mBar.collapseAllPanels(true);
+            }
+        });
+        mModel.addLocationTile(locationTile, new QuickSettingsModel.RefreshCallback() {
+            @Override
+            public void refreshView(QuickSettingsTileView view, State state) {
+                TextView tv = (TextView) view.findViewById(R.id.location_textview);
+                tv.setText(state.label);
+            }
+        });
+        parent.addView(locationTile);
+
+        /*
+        QuickSettingsTileView mediaTile = (QuickSettingsTileView)
+                inflater.inflate(R.layout.quick_settings_tile, parent, false);
+        mediaTile.setContent(R.layout.quick_settings_tile_media, inflater);
+        parent.addView(mediaTile);
+        QuickSettingsTileView imeTile = (QuickSettingsTileView)
+                inflater.inflate(R.layout.quick_settings_tile, parent, false);
+        imeTile.setContent(R.layout.quick_settings_tile_ime, inflater);
+        imeTile.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                parent.removeViewAt(0);
+            }
+        });
+        parent.addView(imeTile);
+        */
+    }
+
+    void updateResources() {
+        Resources r = mContext.getResources();
+
+        // Update the User, Time, and Settings tiles spans, and reset everything else
+        int span = r.getInteger(R.integer.quick_settings_user_time_settings_tile_span);
+        for (QuickSettingsTileView v : mDynamicSpannedTiles) {
+            v.setColumnSpan(span);
+        }
+        mContainerView.requestLayout();
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettingsContainerView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettingsContainerView.java
new file mode 100644
index 0000000..105ceb1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettingsContainerView.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2012 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.systemui.statusbar.phone;
+
+import android.animation.LayoutTransition;
+import android.content.Context;
+import android.content.res.Resources;
+import android.util.AttributeSet;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+
+import com.android.systemui.R;
+
+/**
+ *
+ */
+class QuickSettingsContainerView extends FrameLayout {
+
+    // The number of columns in the QuickSettings grid
+    private int mNumColumns;
+
+    // The gap between tiles in the QuickSettings grid
+    private float mCellGap;
+
+    public QuickSettingsContainerView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+
+        updateResources();
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+
+        // TODO: Setup the layout transitions
+        LayoutTransition transitions = getLayoutTransition();
+    }
+
+    void updateResources() {
+        Resources r = getContext().getResources();
+        mCellGap = r.getDimension(R.dimen.quick_settings_cell_gap);
+        mNumColumns = r.getInteger(R.integer.quick_settings_num_columns);
+        requestLayout();
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        // Calculate the cell width dynamically
+        int width = MeasureSpec.getSize(widthMeasureSpec);
+        int height = MeasureSpec.getSize(heightMeasureSpec);
+        int availableWidth = (int) (width - getPaddingLeft() - getPaddingRight() -
+                (mNumColumns - 1) * mCellGap);
+        float cellWidth = availableWidth / mNumColumns;
+
+        // Update each of the children's widths accordingly to the cell width
+        int N = getChildCount();
+        int cellHeight = 0;
+        int cursor = 0;
+        for (int i = 0; i < N; ++i) {
+            // Update the child's width
+            QuickSettingsTileView v = (QuickSettingsTileView) getChildAt(i);
+            ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) v.getLayoutParams();
+            int colSpan = v.getColumnSpan();
+            lp.width = (int) ((colSpan * cellWidth) + (colSpan - 1) * mCellGap);
+
+            // Measure the child
+            v.setMinimumWidth(lp.width);
+            v.setMinimumHeight(lp.height);
+            int newWidthSpec = MeasureSpec.makeMeasureSpec(lp.width, MeasureSpec.AT_MOST);
+            int newHeightSpec = MeasureSpec.makeMeasureSpec(lp.height, MeasureSpec.AT_MOST);
+            v.measure(newWidthSpec, newHeightSpec);
+
+            // Save the cell height
+            if (cellHeight <= 0) {
+                cellHeight = v.getMeasuredHeight();
+            }
+            cursor += colSpan;
+        }
+
+        // Set the measured dimensions.  We always fill the tray width, but wrap to the height of
+        // all the tiles.
+        int numRows = (int) Math.ceil((float) cursor / mNumColumns);
+        int newHeight = (int) ((numRows * cellHeight) + ((numRows - 1) * mCellGap)) +
+                getPaddingTop() + getPaddingBottom();
+        setMeasuredDimension(width, newHeight);
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        int N = getChildCount();
+        int x = getPaddingLeft();
+        int y = getPaddingTop();
+        int cursor = 0;
+        for (int i = 0; i < N; ++i) {
+            QuickSettingsTileView v = (QuickSettingsTileView) getChildAt(i);
+            ViewGroup.LayoutParams lp = (ViewGroup.LayoutParams) v.getLayoutParams();
+            if (v.getVisibility() != GONE) {
+                int col = cursor % mNumColumns;
+                int colSpan = v.getColumnSpan();
+                int row = (int) (cursor / mNumColumns);
+
+                // Push the item to the next row if it can't fit on this one
+                if ((col + colSpan) > mNumColumns) {
+                    x = getPaddingLeft();
+                    y += lp.height + mCellGap;
+                    row++;
+                }
+
+                // Layout the container
+                v.layout(x, y, x + lp.width, y + lp.height);
+
+                // Offset the position by the cell gap or reset the position and cursor when we
+                // reach the end of the row
+                cursor += v.getColumnSpan();
+                if (cursor < (((row + 1) * mNumColumns))) {
+                    x += lp.width + mCellGap;
+                } else {
+                    x = getPaddingLeft();
+                    y += lp.height + mCellGap;
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettingsTileView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettingsTileView.java
new file mode 100644
index 0000000..8f5cde6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettingsTileView.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2012 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.systemui.statusbar.phone;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.widget.FrameLayout;
+
+/**
+ *
+ */
+class QuickSettingsTileView extends FrameLayout {
+
+    private int mColSpan;
+    private int mRowSpan;
+    private int mCellWidth;
+
+    public QuickSettingsTileView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+
+        mColSpan = 1;
+        mRowSpan = 1;
+    }
+
+    void setColumnSpan(int span) {
+        mColSpan = span;
+    }
+
+    int getColumnSpan() {
+        return mColSpan;
+    }
+
+    void setContent(int layoutId, LayoutInflater inflater) {
+        inflater.inflate(layoutId, this);
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SettingsPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SettingsPanelView.java
index fb1528f..f896d57 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SettingsPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SettingsPanelView.java
@@ -16,15 +16,70 @@
 
 package com.android.systemui.statusbar.phone;
 
+import android.animation.LayoutTransition;
 import android.content.Context;
+import android.content.Intent;
+import android.content.res.Resources;
 import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.systemui.R;
+import com.android.systemui.statusbar.BaseStatusBar;
+import com.android.systemui.statusbar.policy.BatteryController;
+import com.android.systemui.statusbar.policy.BluetoothController;
+import com.android.systemui.statusbar.policy.LocationController;
+import com.android.systemui.statusbar.policy.NetworkController;
 
 public class SettingsPanelView extends PanelView {
+
+    private QuickSettings mQS;
+    private QuickSettingsContainerView mQSContainer;
+
     public SettingsPanelView(Context context, AttributeSet attrs) {
         super(context, attrs);
     }
 
     @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+
+        mQSContainer = (QuickSettingsContainerView) findViewById(R.id.quick_settings_container);
+        mQS = new QuickSettings(getContext(), mQSContainer);
+    }
+
+    @Override
+    public void setBar(PanelBar panelBar) {
+        super.setBar(panelBar);
+
+        if (mQS != null) {
+            mQS.setBar(panelBar);
+        }
+    }
+
+    @Override
+    public void setup(NetworkController networkController, BluetoothController bluetoothController,
+            BatteryController batteryController, LocationController locationController) {
+        super.setup(networkController, bluetoothController, batteryController, locationController);
+
+        if (mQS != null) {
+            mQS.setup(networkController, bluetoothController, batteryController,
+                    locationController);
+        }
+    }
+
+    void updateResources() {
+        if (mQS != null) {
+            mQS.updateResources();
+        }
+        if (mQSContainer != null) {
+            mQSContainer.updateResources();
+        }
+        requestLayout();
+    }
+
+    @Override
     public void fling(float vel, boolean always) {
         ((PhoneStatusBarView) mBar).mBar.getGestureRecorder().tag(
             "fling " + ((vel > 0) ? "open" : "closed"),
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
index ff418c4..7f9bcac 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
@@ -18,6 +18,7 @@
 
 import java.util.ArrayList;
 
+import android.bluetooth.BluetoothAdapter.BluetoothStateChangeCallback;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -36,6 +37,13 @@
     private ArrayList<ImageView> mIconViews = new ArrayList<ImageView>();
     private ArrayList<TextView> mLabelViews = new ArrayList<TextView>();
 
+    private ArrayList<BatteryStateChangeCallback> mChangeCallbacks =
+            new ArrayList<BatteryStateChangeCallback>();
+
+    public interface BatteryStateChangeCallback {
+        public void onBatteryLevelChanged(int level, boolean pluggedIn);
+    }
+
     public BatteryController(Context context) {
         mContext = context;
 
@@ -52,6 +60,10 @@
         mLabelViews.add(v);
     }
 
+    public void addStateChangedCallback(BatteryStateChangeCallback cb) {
+        mChangeCallbacks.add(cb);
+    }
+
     public void onReceive(Context context, Intent intent) {
         final String action = intent.getAction();
         if (action.equals(Intent.ACTION_BATTERY_CHANGED)) {
@@ -73,6 +85,10 @@
                 v.setText(mContext.getString(R.string.status_bar_settings_battery_meter_format,
                         level));
             }
+
+            for (BatteryStateChangeCallback cb : mChangeCallbacks) {
+                cb.onBatteryLevelChanged(level, plugged);
+            }
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothController.java
index 603808e..e517dde 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothController.java
@@ -16,9 +16,8 @@
 
 package com.android.systemui.statusbar.policy;
 
-import java.util.ArrayList;
-
 import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothAdapter.BluetoothStateChangeCallback;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -26,6 +25,8 @@
 import android.view.View;
 import android.widget.ImageView;
 
+import java.util.ArrayList;
+
 import com.android.systemui.R;
 
 public class BluetoothController extends BroadcastReceiver {
@@ -38,6 +39,9 @@
     private int mContentDescriptionId = 0;
     private boolean mEnabled = false;
 
+    private ArrayList<BluetoothStateChangeCallback> mChangeCallbacks =
+            new ArrayList<BluetoothStateChangeCallback>();
+
     public BluetoothController(Context context) {
         mContext = context;
 
@@ -58,6 +62,10 @@
         mIconViews.add(v);
     }
 
+    public void addStateChangedCallback(BluetoothStateChangeCallback cb) {
+        mChangeCallbacks.add(cb);
+    }
+
     @Override
     public void onReceive(Context context, Intent intent) {
         final String action = intent.getAction();
@@ -98,5 +106,8 @@
                     ? null
                     : mContext.getString(mContentDescriptionId));
         }
+        for (BluetoothStateChangeCallback cb : mChangeCallbacks) {
+            cb.onBluetoothStateChange(mEnabled);
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DateView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DateView.java
index c19550b..640dcca 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DateView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DateView.java
@@ -30,7 +30,7 @@
 
 import java.util.Date;
 
-public final class DateView extends TextView {
+public class DateView extends TextView {
     private static final String TAG = "DateView";
 
     private boolean mAttachedToWindow;
@@ -86,7 +86,7 @@
         return 0;
     }
 
-    private final void updateClock() {
+    protected void updateClock() {
         final Context context = getContext();
         Date now = new Date();
         CharSequence dow = DateFormat.format("EEEE", now);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationController.java
index bec5d72..4bf03e7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationController.java
@@ -37,6 +37,7 @@
 import com.android.internal.statusbar.StatusBarNotification;
 
 import com.android.systemui.R;
+import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback;
 
 public class LocationController extends BroadcastReceiver {
     private static final String TAG = "StatusBar.LocationController";
@@ -47,6 +48,13 @@
 
     private INotificationManager mNotificationService;
 
+    private ArrayList<LocationGpsStateChangeCallback> mChangeCallbacks =
+            new ArrayList<LocationGpsStateChangeCallback>();
+
+    public interface LocationGpsStateChangeCallback {
+        public void onLocationGpsStateChanged(boolean inUse, String description);
+    }
+
     public LocationController(Context context) {
         mContext = context;
 
@@ -60,6 +68,10 @@
         mNotificationService = nm.getService();
     }
 
+    public void addStateChangedCallback(LocationGpsStateChangeCallback cb) {
+        mChangeCallbacks.add(cb);
+    }
+
     @Override
     public void onReceive(Context context, Intent intent) {
         final String action = intent.getAction();
@@ -89,12 +101,14 @@
             if (visible) {
                 Intent gpsIntent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
                 gpsIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
                 PendingIntent pendingIntent = PendingIntent.getActivityAsUser(context, 0,
                         gpsIntent, 0, null, UserHandle.CURRENT);
+                String text = mContext.getText(textResId).toString();
 
                 Notification n = new Notification.Builder(mContext)
                     .setSmallIcon(iconId)
-                    .setContentTitle(mContext.getText(textResId))
+                    .setContentTitle(text)
                     .setOngoing(true)
                     .setContentIntent(pendingIntent)
                     .getNotification();
@@ -117,6 +131,10 @@
                 mNotificationService.cancelNotificationWithTag(
                         mContext.getPackageName(), null,
                         GPS_NOTIFICATION_ID, UserHandle.USER_CURRENT);
+
+                for (LocationGpsStateChangeCallback cb : mChangeCallbacks) {
+                    cb.onLocationGpsStateChanged(false, null);
+                }
             }
         } catch (android.os.RemoteException ex) {
             // well, it was worth a shot
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
index d94c6b2..23f27e7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
@@ -148,6 +148,8 @@
     ArrayList<TextView> mWifiLabelViews = new ArrayList<TextView>();
     ArrayList<TextView> mEmergencyLabelViews = new ArrayList<TextView>();
     ArrayList<SignalCluster> mSignalClusters = new ArrayList<SignalCluster>();
+    ArrayList<NetworkSignalChangedCallback> mSignalsChangedCallbacks =
+            new ArrayList<NetworkSignalChangedCallback>();
     int mLastPhoneSignalIconId = -1;
     int mLastDataDirectionIconId = -1;
     int mLastDataDirectionOverlayIconId = -1;
@@ -172,6 +174,12 @@
         void setIsAirplaneMode(boolean is, int airplaneIcon);
     }
 
+    public interface NetworkSignalChangedCallback {
+        void onWifiSignalChanged(boolean enabled, String description);
+        void onMobileDataSignalChanged(boolean enabled, String description);
+        void onAirplaneModeChanged(boolean enabled);
+    }
+
     /**
      * Construct this controller object and register for updates.
      */
@@ -299,6 +307,11 @@
         refreshSignalCluster(cluster);
     }
 
+    public void addNetworkSignalChangedCallback(NetworkSignalChangedCallback cb) {
+        mSignalsChangedCallbacks.add(cb);
+        notifySignalsChangedCallbacks(cb);
+    }
+
     public void refreshSignalCluster(SignalCluster cluster) {
         cluster.setWifiIndicators(
                 // only show wifi in the cluster if connected or if wifi-only
@@ -329,6 +342,27 @@
         cluster.setIsAirplaneMode(mAirplaneMode, mAirplaneIconId);
     }
 
+    void notifySignalsChangedCallbacks(NetworkSignalChangedCallback cb) {
+        // only show wifi in the cluster if connected or if wifi-only
+        boolean wifiEnabled = mWifiEnabled && (mWifiConnected || !mHasMobileDataFeature);
+        String wifiDesc = wifiEnabled ?
+                mWifiSsid : null;
+        cb.onWifiSignalChanged(wifiEnabled, wifiDesc);
+
+        if (isEmergencyOnly()) {
+            cb.onMobileDataSignalChanged(false, null);
+        } else {
+            if (mIsWimaxEnabled && mWimaxConnected) {
+                // wimax is special
+                cb.onMobileDataSignalChanged(true, mNetworkName);
+            } else {
+                // normal mobile data
+                cb.onMobileDataSignalChanged(mHasMobileDataFeature, mNetworkName);
+            }
+        }
+        cb.onAirplaneModeChanged(mAirplaneMode);
+    }
+
     public void setStackedMode(boolean stacked) {
         mDataAndWifiStacked = true;
     }
@@ -1124,6 +1158,9 @@
             for (SignalCluster cluster : mSignalClusters) {
                 refreshSignalCluster(cluster);
             }
+            for (NetworkSignalChangedCallback cb : mSignalsChangedCallbacks) {
+                notifySignalsChangedCallbacks(cb);
+            }
         }
 
         if (mLastAirplaneMode != mAirplaneMode) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/QuickSettingsDateView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/QuickSettingsDateView.java
new file mode 100644
index 0000000..c52f94b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/QuickSettingsDateView.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2008 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.systemui.statusbar.policy;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.text.format.DateFormat;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewParent;
+import android.widget.TextView;
+
+import com.android.systemui.R;
+
+import java.util.Date;
+
+public final class QuickSettingsDateView extends DateView {
+
+    public QuickSettingsDateView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    protected void updateClock() {
+        final Context context = getContext();
+        Date now = new Date();
+        CharSequence dow = DateFormat.format("MMM d, yyyy", now);
+        setText(dow);
+    }
+}
diff --git a/services/input/InputReader.cpp b/services/input/InputReader.cpp
index 6d63998..513dc13 100644
--- a/services/input/InputReader.cpp
+++ b/services/input/InputReader.cpp
@@ -203,34 +203,18 @@
 
 // --- InputReaderConfiguration ---
 
-bool InputReaderConfiguration::getDisplayInfo(int32_t displayId, bool external,
-        int32_t* width, int32_t* height, int32_t* orientation) const {
-    if (displayId == 0) {
-        const DisplayInfo& info = external ? mExternalDisplay : mInternalDisplay;
-        if (info.width > 0 && info.height > 0) {
-            if (width) {
-                *width = info.width;
-            }
-            if (height) {
-                *height = info.height;
-            }
-            if (orientation) {
-                *orientation = info.orientation;
-            }
-            return true;
-        }
+bool InputReaderConfiguration::getDisplayInfo(bool external, DisplayViewport* outViewport) const {
+    const DisplayViewport& viewport = external ? mExternalDisplay : mInternalDisplay;
+    if (viewport.displayId >= 0) {
+        *outViewport = viewport;
+        return true;
     }
     return false;
 }
 
-void InputReaderConfiguration::setDisplayInfo(int32_t displayId, bool external,
-        int32_t width, int32_t height, int32_t orientation) {
-    if (displayId == 0) {
-        DisplayInfo& info = external ? mExternalDisplay : mInternalDisplay;
-        info.width = width;
-        info.height = height;
-        info.orientation = orientation;
-    }
+void InputReaderConfiguration::setDisplayInfo(bool external, const DisplayViewport& viewport) {
+    DisplayViewport& v = external ? mExternalDisplay : mInternalDisplay;
+    v = viewport;
 }
 
 
@@ -2001,9 +1985,11 @@
     }
 
     if (!changes || (changes & InputReaderConfiguration::CHANGE_DISPLAY_INFO)) {
-        if (mParameters.orientationAware && mParameters.associatedDisplayId >= 0) {
-            if (!config->getDisplayInfo(mParameters.associatedDisplayId,
-                        false /*external*/, NULL, NULL, &mOrientation)) {
+        if (mParameters.orientationAware && mParameters.hasAssociatedDisplay) {
+            DisplayViewport v;
+            if (config->getDisplayInfo(false /*external*/, &v)) {
+                mOrientation = v.orientation;
+            } else {
                 mOrientation = DISPLAY_ORIENTATION_0;
             }
         } else {
@@ -2017,16 +2003,16 @@
     getDevice()->getConfiguration().tryGetProperty(String8("keyboard.orientationAware"),
             mParameters.orientationAware);
 
-    mParameters.associatedDisplayId = -1;
+    mParameters.hasAssociatedDisplay = false;
     if (mParameters.orientationAware) {
-        mParameters.associatedDisplayId = 0;
+        mParameters.hasAssociatedDisplay = true;
     }
 }
 
 void KeyboardInputMapper::dumpParameters(String8& dump) {
     dump.append(INDENT3 "Parameters:\n");
-    dump.appendFormat(INDENT4 "AssociatedDisplayId: %d\n",
-            mParameters.associatedDisplayId);
+    dump.appendFormat(INDENT4 "HasAssociatedDisplay: %s\n",
+            toString(mParameters.hasAssociatedDisplay));
     dump.appendFormat(INDENT4 "OrientationAware: %s\n",
             toString(mParameters.orientationAware));
 }
@@ -2086,7 +2072,7 @@
 
     if (down) {
         // Rotate key codes according to orientation if needed.
-        if (mParameters.orientationAware && mParameters.associatedDisplayId >= 0) {
+        if (mParameters.orientationAware && mParameters.hasAssociatedDisplay) {
             keyCode = rotateKeyCode(keyCode, mOrientation);
         }
 
@@ -2317,9 +2303,11 @@
     }
 
     if (!changes || (changes & InputReaderConfiguration::CHANGE_DISPLAY_INFO)) {
-        if (mParameters.orientationAware && mParameters.associatedDisplayId >= 0) {
-            if (!config->getDisplayInfo(mParameters.associatedDisplayId,
-                        false /*external*/, NULL, NULL, &mOrientation)) {
+        if (mParameters.orientationAware && mParameters.hasAssociatedDisplay) {
+            DisplayViewport v;
+            if (config->getDisplayInfo(false /*external*/, &v)) {
+                mOrientation = v.orientation;
+            } else {
                 mOrientation = DISPLAY_ORIENTATION_0;
             }
         } else {
@@ -2344,16 +2332,16 @@
     getDevice()->getConfiguration().tryGetProperty(String8("cursor.orientationAware"),
             mParameters.orientationAware);
 
-    mParameters.associatedDisplayId = -1;
+    mParameters.hasAssociatedDisplay = false;
     if (mParameters.mode == Parameters::MODE_POINTER || mParameters.orientationAware) {
-        mParameters.associatedDisplayId = 0;
+        mParameters.hasAssociatedDisplay = true;
     }
 }
 
 void CursorInputMapper::dumpParameters(String8& dump) {
     dump.append(INDENT3 "Parameters:\n");
-    dump.appendFormat(INDENT4 "AssociatedDisplayId: %d\n",
-            mParameters.associatedDisplayId);
+    dump.appendFormat(INDENT4 "HasAssociatedDisplay: %s\n",
+            toString(mParameters.hasAssociatedDisplay));
 
     switch (mParameters.mode) {
     case Parameters::MODE_POINTER:
@@ -2420,7 +2408,7 @@
     bool moved = deltaX != 0 || deltaY != 0;
 
     // Rotate delta according to orientation if needed.
-    if (mParameters.orientationAware && mParameters.associatedDisplayId >= 0
+    if (mParameters.orientationAware && mParameters.hasAssociatedDisplay
             && (deltaX != 0.0f || deltaY != 0.0f)) {
         rotateDelta(mOrientation, &deltaX, &deltaY);
     }
@@ -2782,15 +2770,15 @@
     getDevice()->getConfiguration().tryGetProperty(String8("touch.orientationAware"),
             mParameters.orientationAware);
 
-    mParameters.associatedDisplayId = -1;
+    mParameters.hasAssociatedDisplay = false;
     mParameters.associatedDisplayIsExternal = false;
     if (mParameters.orientationAware
             || mParameters.deviceType == Parameters::DEVICE_TYPE_TOUCH_SCREEN
             || mParameters.deviceType == Parameters::DEVICE_TYPE_POINTER) {
+        mParameters.hasAssociatedDisplay = true;
         mParameters.associatedDisplayIsExternal =
                 mParameters.deviceType == Parameters::DEVICE_TYPE_TOUCH_SCREEN
                         && getDevice()->isExternal();
-        mParameters.associatedDisplayId = 0;
     }
 }
 
@@ -2822,8 +2810,9 @@
         ALOG_ASSERT(false);
     }
 
-    dump.appendFormat(INDENT4 "AssociatedDisplay: id=%d, isExternal=%s\n",
-            mParameters.associatedDisplayId, toString(mParameters.associatedDisplayIsExternal));
+    dump.appendFormat(INDENT4 "AssociatedDisplay: present=%s, isExternal=%s\n",
+            toString(mParameters.hasAssociatedDisplay),
+            toString(mParameters.associatedDisplayIsExternal));
     dump.appendFormat(INDENT4 "OrientationAware: %s\n",
             toString(mParameters.orientationAware));
 }
@@ -2861,7 +2850,7 @@
             mSource |= AINPUT_SOURCE_STYLUS;
         }
     } else if (mParameters.deviceType == Parameters::DEVICE_TYPE_TOUCH_SCREEN
-            && mParameters.associatedDisplayId >= 0) {
+            && mParameters.hasAssociatedDisplay) {
         mSource = AINPUT_SOURCE_TOUCHSCREEN;
         mDeviceMode = DEVICE_MODE_DIRECT;
         if (hasStylus()) {
@@ -2881,15 +2870,13 @@
     }
 
     // Get associated display dimensions.
-    if (mParameters.associatedDisplayId >= 0) {
-        if (!mConfig.getDisplayInfo(mParameters.associatedDisplayId,
-                mParameters.associatedDisplayIsExternal,
-                &mAssociatedDisplayWidth, &mAssociatedDisplayHeight,
-                &mAssociatedDisplayOrientation)) {
+    if (mParameters.hasAssociatedDisplay) {
+        if (!mConfig.getDisplayInfo(mParameters.associatedDisplayIsExternal,
+                &mAssociatedDisplayViewport)) {
             ALOGI(INDENT "Touch device '%s' could not query the properties of its associated "
-                    "display %d.  The device will be inoperable until the display size "
+                    "display.  The device will be inoperable until the display size "
                     "becomes available.",
-                    getDeviceName().string(), mParameters.associatedDisplayId);
+                    getDeviceName().string());
             mDeviceMode = DEVICE_MODE_DISABLED;
             return;
         }
@@ -2898,10 +2885,16 @@
     // Configure dimensions.
     int32_t width, height, orientation;
     if (mDeviceMode == DEVICE_MODE_DIRECT || mDeviceMode == DEVICE_MODE_POINTER) {
-        width = mAssociatedDisplayWidth;
-        height = mAssociatedDisplayHeight;
+        width = mAssociatedDisplayViewport.logicalRight - mAssociatedDisplayViewport.logicalLeft;
+        height = mAssociatedDisplayViewport.logicalBottom - mAssociatedDisplayViewport.logicalTop;
+        if (mAssociatedDisplayViewport.orientation == DISPLAY_ORIENTATION_90
+                || mAssociatedDisplayViewport.orientation == DISPLAY_ORIENTATION_270) {
+            int32_t temp = height;
+            height = width;
+            width = temp;
+        }
         orientation = mParameters.orientationAware ?
-                mAssociatedDisplayOrientation : DISPLAY_ORIENTATION_0;
+                mAssociatedDisplayViewport.orientation : DISPLAY_ORIENTATION_0;
     } else {
         width = mRawPointerAxes.x.maxValue - mRawPointerAxes.x.minValue + 1;
         height = mRawPointerAxes.y.maxValue - mRawPointerAxes.y.minValue + 1;
@@ -3163,8 +3156,7 @@
             int32_t rawWidth = mRawPointerAxes.x.maxValue - mRawPointerAxes.x.minValue + 1;
             int32_t rawHeight = mRawPointerAxes.y.maxValue - mRawPointerAxes.y.minValue + 1;
             float rawDiagonal = hypotf(rawWidth, rawHeight);
-            float displayDiagonal = hypotf(mAssociatedDisplayWidth,
-                    mAssociatedDisplayHeight);
+            float displayDiagonal = hypotf(width, height);
 
             // Scale movements such that one whole swipe of the touch pad covers a
             // given area relative to the diagonal size of the display when no acceleration
diff --git a/services/input/InputReader.h b/services/input/InputReader.h
index 6c06986..e345a5fb 100644
--- a/services/input/InputReader.h
+++ b/services/input/InputReader.h
@@ -24,7 +24,6 @@
 #include <androidfw/Input.h>
 #include <androidfw/VelocityControl.h>
 #include <androidfw/VelocityTracker.h>
-#include <ui/DisplayInfo.h>
 #include <utils/KeyedVector.h>
 #include <utils/threads.h>
 #include <utils/Timers.h>
@@ -48,6 +47,45 @@
 class InputDevice;
 class InputMapper;
 
+/*
+ * Describes how coordinates are mapped on a physical display.
+ * See com.android.server.display.DisplayViewport.
+ */
+struct DisplayViewport {
+    int32_t displayId; // -1 if invalid
+    int32_t orientation;
+    int32_t logicalLeft;
+    int32_t logicalTop;
+    int32_t logicalRight;
+    int32_t logicalBottom;
+    int32_t physicalLeft;
+    int32_t physicalTop;
+    int32_t physicalRight;
+    int32_t physicalBottom;
+
+    DisplayViewport() :
+            displayId(-1), orientation(DISPLAY_ORIENTATION_0),
+            logicalLeft(0), logicalTop(0), logicalRight(0), logicalBottom(0),
+            physicalLeft(0), physicalTop(0), physicalRight(0), physicalBottom(0) {
+    }
+
+    bool operator==(const DisplayViewport& other) const {
+        return displayId == other.displayId
+                && orientation == other.orientation
+                && logicalLeft == other.logicalLeft
+                && logicalTop == other.logicalTop
+                && logicalRight == other.logicalRight
+                && logicalBottom == other.logicalBottom
+                && physicalLeft == other.physicalLeft
+                && physicalTop == other.physicalTop
+                && physicalRight == other.physicalRight
+                && physicalBottom == other.physicalBottom;
+    }
+
+    bool operator!=(const DisplayViewport& other) const {
+        return !(*this == other);
+    }
+};
 
 /*
  * Input reader configuration.
@@ -180,25 +218,12 @@
             pointerGestureZoomSpeedRatio(0.3f),
             showTouches(false) { }
 
-    bool getDisplayInfo(int32_t displayId, bool external,
-            int32_t* width, int32_t* height, int32_t* orientation) const;
-
-    void setDisplayInfo(int32_t displayId, bool external,
-            int32_t width, int32_t height, int32_t orientation);
+    bool getDisplayInfo(bool external, DisplayViewport* outViewport) const;
+    void setDisplayInfo(bool external, const DisplayViewport& viewport);
 
 private:
-    struct DisplayInfo {
-        int32_t width;
-        int32_t height;
-        int32_t orientation;
-
-        DisplayInfo() :
-            width(-1), height(-1), orientation(DISPLAY_ORIENTATION_0) {
-        }
-    };
-
-    DisplayInfo mInternalDisplay;
-    DisplayInfo mExternalDisplay;
+    DisplayViewport mInternalDisplay;
+    DisplayViewport mExternalDisplay;
 };
 
 
@@ -992,7 +1017,7 @@
 
     // Immutable configuration parameters.
     struct Parameters {
-        int32_t associatedDisplayId;
+        bool hasAssociatedDisplay;
         bool orientationAware;
     } mParameters;
 
@@ -1042,7 +1067,7 @@
         };
 
         Mode mode;
-        int32_t associatedDisplayId;
+        bool hasAssociatedDisplay;
         bool orientationAware;
     } mParameters;
 
@@ -1143,7 +1168,7 @@
         };
 
         DeviceType deviceType;
-        int32_t associatedDisplayId;
+        bool hasAssociatedDisplay;
         bool associatedDisplayIsExternal;
         bool orientationAware;
 
@@ -1277,10 +1302,8 @@
     int32_t mSurfaceWidth;
     int32_t mSurfaceHeight;
 
-    // The associated display orientation and width and height set by configureSurface().
-    int32_t mAssociatedDisplayOrientation;
-    int32_t mAssociatedDisplayWidth;
-    int32_t mAssociatedDisplayHeight;
+    // The associated display viewport set by configureSurface().
+    DisplayViewport mAssociatedDisplayViewport;
 
     // Translation and scaling factors, orientation-independent.
     float mXScale;
diff --git a/services/input/PointerController.cpp b/services/input/PointerController.cpp
index fc828a6..9af521b 100644
--- a/services/input/PointerController.cpp
+++ b/services/input/PointerController.cpp
@@ -307,9 +307,17 @@
     }
 }
 
-void PointerController::setDisplaySize(int32_t width, int32_t height) {
+void PointerController::setDisplayViewport(int32_t width, int32_t height, int32_t orientation) {
     AutoMutex _l(mLock);
 
+    // Adjust to use the display's unrotated coordinate frame.
+    if (orientation == DISPLAY_ORIENTATION_90
+            || orientation == DISPLAY_ORIENTATION_270) {
+        int32_t temp = height;
+        height = width;
+        width = temp;
+    }
+
     if (mLocked.displayWidth != width || mLocked.displayHeight != height) {
         mLocked.displayWidth = width;
         mLocked.displayHeight = height;
@@ -324,12 +332,7 @@
         }
 
         fadeOutAndReleaseAllSpotsLocked();
-        updatePointerLocked();
     }
-}
-
-void PointerController::setDisplayOrientation(int32_t orientation) {
-    AutoMutex _l(mLock);
 
     if (mLocked.displayOrientation != orientation) {
         // Apply offsets to convert from the pixel top-left corner position to the pixel center.
@@ -380,9 +383,9 @@
         mLocked.pointerX = x - 0.5f;
         mLocked.pointerY = y - 0.5f;
         mLocked.displayOrientation = orientation;
-
-        updatePointerLocked();
     }
+
+    updatePointerLocked();
 }
 
 void PointerController::setPointerIcon(const SpriteIcon& icon) {
diff --git a/services/input/PointerController.h b/services/input/PointerController.h
index 4c307c4..fd68b61 100644
--- a/services/input/PointerController.h
+++ b/services/input/PointerController.h
@@ -170,8 +170,7 @@
             const uint32_t* spotIdToIndex, BitSet32 spotIdBits);
     virtual void clearSpots();
 
-    void setDisplaySize(int32_t width, int32_t height);
-    void setDisplayOrientation(int32_t orientation);
+    void setDisplayViewport(int32_t width, int32_t height, int32_t orientation);
     void setPointerIcon(const SpriteIcon& icon);
     void setInactivityTimeout(InactivityTimeout inactivityTimeout);
 
diff --git a/services/input/tests/InputReader_test.cpp b/services/input/tests/InputReader_test.cpp
index abda10b..03516af 100644
--- a/services/input/tests/InputReader_test.cpp
+++ b/services/input/tests/InputReader_test.cpp
@@ -138,8 +138,21 @@
 
     void setDisplayInfo(int32_t displayId, int32_t width, int32_t height, int32_t orientation) {
         // Set the size of both the internal and external display at the same time.
-        mConfig.setDisplayInfo(displayId, false /*external*/, width, height, orientation);
-        mConfig.setDisplayInfo(displayId, true /*external*/, width, height, orientation);
+        bool isRotated = (orientation == DISPLAY_ORIENTATION_90
+                || orientation == DISPLAY_ORIENTATION_270);
+        DisplayViewport v;
+        v.displayId = displayId;
+        v.orientation = orientation;
+        v.logicalLeft = 0;
+        v.logicalTop = 0;
+        v.logicalRight = isRotated ? height : width;
+        v.logicalBottom = isRotated ? width : height;
+        v.physicalLeft = 0;
+        v.physicalTop = 0;
+        v.physicalRight = isRotated ? height : width;
+        v.physicalBottom = isRotated ? width : height;
+        mConfig.setDisplayInfo(false /*external*/, v);
+        mConfig.setDisplayInfo(true /*external*/, v);
     }
 
     void addExcludedDeviceName(const String8& deviceName) {
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 1aad9b3..c28afb2 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -304,6 +304,7 @@
 
             ActivityManagerService.self().setWindowManager(wm);
             display.setWindowManager(wm);
+            display.setInputManager(inputManager);
 
             // Skip Bluetooth if we have an emulator kernel
             // TODO: Use a more reliable check to see if this product should
diff --git a/services/java/com/android/server/display/DisplayDevice.java b/services/java/com/android/server/display/DisplayDevice.java
index 995c553..8eeefb4 100644
--- a/services/java/com/android/server/display/DisplayDevice.java
+++ b/services/java/com/android/server/display/DisplayDevice.java
@@ -132,14 +132,17 @@
                 || mCurrentDisplayRect == null
                 || !mCurrentDisplayRect.equals(displayRect)) {
             mCurrentOrientation = orientation;
+
             if (mCurrentLayerStackRect == null) {
                 mCurrentLayerStackRect = new Rect();
             }
             mCurrentLayerStackRect.set(layerStackRect);
+
             if (mCurrentDisplayRect == null) {
                 mCurrentDisplayRect = new Rect();
             }
             mCurrentDisplayRect.set(displayRect);
+
             Surface.setDisplayProjection(mDisplayToken,
                     orientation, layerStackRect, displayRect);
         }
@@ -156,6 +159,26 @@
     }
 
     /**
+     * Populates the specified viewport object with orientation,
+     * physical and logical rects based on the display's current projection.
+     */
+    public final void populateViewportLocked(DisplayViewport viewport) {
+        viewport.orientation = mCurrentOrientation;
+
+        if (mCurrentLayerStackRect != null) {
+            viewport.logicalFrame.set(mCurrentLayerStackRect);
+        } else {
+            viewport.logicalFrame.setEmpty();
+        }
+
+        if (mCurrentDisplayRect != null) {
+            viewport.physicalFrame.set(mCurrentDisplayRect);
+        } else {
+            viewport.physicalFrame.setEmpty();
+        }
+    }
+
+    /**
      * Dumps the local state of the display device.
      * Does not need to dump the display device info because that is already dumped elsewhere.
      */
diff --git a/services/java/com/android/server/display/DisplayDeviceInfo.java b/services/java/com/android/server/display/DisplayDeviceInfo.java
index c90a1c6..7c57694 100644
--- a/services/java/com/android/server/display/DisplayDeviceInfo.java
+++ b/services/java/com/android/server/display/DisplayDeviceInfo.java
@@ -43,6 +43,21 @@
     public static final int FLAG_SUPPORTS_ROTATION = 1 << 2;
 
     /**
+     * Touch attachment: Display does not receive touch.
+     */
+    public static final int TOUCH_NONE = 0;
+
+    /**
+     * Touch attachment: Touch input is via the internal interface.
+     */
+    public static final int TOUCH_INTERNAL = 1;
+
+    /**
+     * Touch attachment: Touch input is via an external interface, such as USB.
+     */
+    public static final int TOUCH_EXTERNAL = 2;
+
+    /**
      * Gets the name of the display device, which may be derived from
      * EDID or other sources.  The name may be displayed to the user.
      */
@@ -90,6 +105,11 @@
      */
     public int flags;
 
+    /**
+     * The touch attachment, per {@link DisplayViewport#touch}.
+     */
+    public int touch;
+
     public void setAssumedDensityForExternalDisplay(int width, int height) {
         densityDpi = Math.min(width, height) * DisplayMetrics.DENSITY_XHIGH / 1080;
         // Technically, these values should be smaller than the apparent density
@@ -112,7 +132,8 @@
                 && densityDpi == other.densityDpi
                 && xDpi == other.xDpi
                 && yDpi == other.yDpi
-                && flags == other.flags;
+                && flags == other.flags
+                && touch == other.touch;
     }
 
     @Override
@@ -129,6 +150,7 @@
         xDpi = other.xDpi;
         yDpi = other.yDpi;
         flags = other.flags;
+        touch = other.touch;
     }
 
     // For debugging purposes
@@ -136,7 +158,20 @@
     public String toString() {
         return "DisplayDeviceInfo{\"" + name + "\": " + width + " x " + height + ", " + refreshRate + " fps, "
                 + "density " + densityDpi + ", " + xDpi + " x " + yDpi + " dpi"
-                + flagsToString(flags) + "}";
+                + ", touch " + touchToString(touch) + flagsToString(flags) + "}";
+    }
+
+    private static String touchToString(int touch) {
+        switch (touch) {
+            case TOUCH_NONE:
+                return "NONE";
+            case TOUCH_INTERNAL:
+                return "INTERNAL";
+            case TOUCH_EXTERNAL:
+                return "EXTERNAL";
+            default:
+                return Integer.toString(touch);
+        }
     }
 
     private static String flagsToString(int flags) {
diff --git a/services/java/com/android/server/display/DisplayManagerService.java b/services/java/com/android/server/display/DisplayManagerService.java
index dc85d3f..e11d454 100644
--- a/services/java/com/android/server/display/DisplayManagerService.java
+++ b/services/java/com/android/server/display/DisplayManagerService.java
@@ -24,6 +24,7 @@
 import android.hardware.display.DisplayManagerGlobal;
 import android.hardware.display.IDisplayManager;
 import android.hardware.display.IDisplayManagerCallback;
+import android.hardware.display.WifiDisplayStatus;
 import android.os.Binder;
 import android.os.Handler;
 import android.os.IBinder;
@@ -95,6 +96,7 @@
     private static final int MSG_REGISTER_ADDITIONAL_DISPLAY_ADAPTERS = 2;
     private static final int MSG_DELIVER_DISPLAY_EVENT = 3;
     private static final int MSG_REQUEST_TRAVERSAL = 4;
+    private static final int MSG_UPDATE_VIEWPORT = 5;
 
     private final Context mContext;
     private final boolean mHeadless;
@@ -102,6 +104,7 @@
     private final Handler mUiHandler;
     private final DisplayAdapterListener mDisplayAdapterListener;
     private WindowManagerFuncs mWindowManagerFuncs;
+    private InputManagerFuncs mInputManagerFuncs;
 
     // The synchronization root for the display manager.
     // This lock guards most of the display manager's state.
@@ -137,6 +140,14 @@
     // to the surface flinger state.
     private boolean mPendingTraversal;
 
+    // The Wifi display adapter, or null if not registered.
+    private WifiDisplayAdapter mWifiDisplayAdapter;
+
+    // Viewports of the default display and the display that should receive touch
+    // input from an external source.  Used by the input system.
+    private final DisplayViewport mDefaultViewport = new DisplayViewport();
+    private final DisplayViewport mExternalTouchViewport = new DisplayViewport();
+
     // Temporary callback list, used when sending display events to applications.
     // May be used outside of the lock but only on the handler thread.
     private final ArrayList<CallbackRecord> mTempCallbacks = new ArrayList<CallbackRecord>();
@@ -144,6 +155,11 @@
     // Temporary display info, used for comparing display configurations.
     private final DisplayInfo mTempDisplayInfo = new DisplayInfo();
 
+    // Temporary viewports, used when sending new viewport information to the
+    // input system.  May be used outside of the lock but only on the handler thread.
+    private final DisplayViewport mTempDefaultViewport = new DisplayViewport();
+    private final DisplayViewport mTempExternalTouchViewport = new DisplayViewport();
+
     public DisplayManagerService(Context context, Handler mainHandler, Handler uiHandler) {
         mContext = context;
         mHeadless = SystemProperties.get(SYSTEM_HEADLESS).equals("1");
@@ -179,7 +195,7 @@
     }
 
     /**
-     * Called during initialization to associated the display manager with the
+     * Called during initialization to associate the display manager with the
      * window manager.
      */
     public void setWindowManager(WindowManagerFuncs windowManagerFuncs) {
@@ -190,6 +206,17 @@
     }
 
     /**
+     * Called during initialization to associate the display manager with the
+     * input manager.
+     */
+    public void setInputManager(InputManagerFuncs inputManagerFuncs) {
+        synchronized (mSyncRoot) {
+            mInputManagerFuncs = inputManagerFuncs;
+            scheduleTraversalLocked();
+        }
+    }
+
+    /**
      * Called when the system is ready to go.
      */
     public void systemReady(boolean safeMode, boolean onlyCore) {
@@ -315,6 +342,77 @@
         }
     }
 
+    @Override // Binder call
+    public void scanWifiDisplays() {
+        if (mContext.checkCallingPermission(android.Manifest.permission.CONFIGURE_WIFI_DISPLAY)
+                != PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException("Requires CONFIGURE_WIFI_DISPLAY permission");
+        }
+
+        final long token = Binder.clearCallingIdentity();
+        try {
+            synchronized (mSyncRoot) {
+                mWifiDisplayAdapter.requestScanLocked();
+            }
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    @Override // Binder call
+    public void connectWifiDisplay(String address) {
+        if (mContext.checkCallingPermission(android.Manifest.permission.CONFIGURE_WIFI_DISPLAY)
+                != PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException("Requires CONFIGURE_WIFI_DISPLAY permission");
+        }
+        if (address == null) {
+            throw new IllegalArgumentException("address must not be null");
+        }
+
+        final long token = Binder.clearCallingIdentity();
+        try {
+            synchronized (mSyncRoot) {
+                mWifiDisplayAdapter.requestConnectLocked(address);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    @Override // Binder call
+    public void disconnectWifiDisplay() {
+        if (mContext.checkCallingPermission(android.Manifest.permission.CONFIGURE_WIFI_DISPLAY)
+                != PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException("Requires CONFIGURE_WIFI_DISPLAY permission");
+        }
+
+        final long token = Binder.clearCallingIdentity();
+        try {
+            synchronized (mSyncRoot) {
+                mWifiDisplayAdapter.requestDisconnectLocked();
+            }
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    @Override // Binder call
+    public WifiDisplayStatus getWifiDisplayStatus() {
+        if (mContext.checkCallingPermission(android.Manifest.permission.CONFIGURE_WIFI_DISPLAY)
+                != PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException("Requires CONFIGURE_WIFI_DISPLAY permission");
+        }
+
+        final long token = Binder.clearCallingIdentity();
+        try {
+            synchronized (mSyncRoot) {
+                return mWifiDisplayAdapter.getWifiDisplayStatusLocked();
+            }
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
     private void registerDefaultDisplayAdapter() {
         // Register default display adapter.
         synchronized (mSyncRoot) {
@@ -333,8 +431,9 @@
             if (shouldRegisterNonEssentialDisplayAdaptersLocked()) {
                 registerDisplayAdapterLocked(new OverlayDisplayAdapter(
                         mSyncRoot, mContext, mHandler, mDisplayAdapterListener, mUiHandler));
-                registerDisplayAdapterLocked(new WifiDisplayAdapter(
-                        mSyncRoot, mContext, mHandler, mDisplayAdapterListener));
+                mWifiDisplayAdapter = new WifiDisplayAdapter(
+                        mSyncRoot, mContext, mHandler, mDisplayAdapterListener);
+                registerDisplayAdapterLocked(mWifiDisplayAdapter);
             }
         }
     }
@@ -411,7 +510,7 @@
         final int displayId = assignDisplayIdLocked(isDefault);
         final int layerStack = assignLayerStackLocked(displayId);
 
-        LogicalDisplay display = new LogicalDisplay(layerStack, device);
+        LogicalDisplay display = new LogicalDisplay(displayId, layerStack, device);
         display.updateLocked(mDisplayDevices);
         if (!display.isValidLocked()) {
             // This should never happen currently.
@@ -472,6 +571,10 @@
         }
         mRemovedDisplayDevices.clear();
 
+        // Clear all viewports before configuring displays so that we can keep
+        // track of which ones we have configured.
+        clearViewportsLocked();
+
         // Configure each display device.
         final int count = mDisplayDevices.size();
         for (int i = 0; i < count; i++) {
@@ -479,6 +582,11 @@
             configureDisplayInTransactionLocked(device);
             device.performTraversalInTransactionLocked();
         }
+
+        // Tell the input system about these new viewports.
+        if (mInputManagerFuncs != null) {
+            mHandler.sendEmptyMessage(MSG_UPDATE_VIEWPORT);
+        }
     }
 
     /**
@@ -501,7 +609,11 @@
                 scheduleTraversalLocked();
             }
         }
+    }
 
+    private void clearViewportsLocked() {
+        mDefaultViewport.valid = false;
+        mExternalTouchViewport.valid = false;
     }
 
     private void configureDisplayInTransactionLocked(DisplayDevice device) {
@@ -517,11 +629,30 @@
         // Apply the logical display configuration to the display device.
         if (display == null) {
             // TODO: no logical display for the device, blank it
-            Slog.d(TAG, "Missing logical display to use for physical display device: "
+            Slog.w(TAG, "Missing logical display to use for physical display device: "
                     + device.getDisplayDeviceInfoLocked());
+            return;
         } else {
             display.configureDisplayInTransactionLocked(device);
         }
+
+        // Update the viewports if needed.
+        DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked();
+        if (!mDefaultViewport.valid
+                && (info.flags & DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY) != 0) {
+            setViewportLocked(mDefaultViewport, display, device);
+        }
+        if (!mExternalTouchViewport.valid
+                && info.touch == DisplayDeviceInfo.TOUCH_EXTERNAL) {
+            setViewportLocked(mExternalTouchViewport, display, device);
+        }
+    }
+
+    private static void setViewportLocked(DisplayViewport viewport,
+            LogicalDisplay display, DisplayDevice device) {
+        viewport.valid = true;
+        viewport.displayId = display.getDisplayIdLocked();
+        device.populateViewportLocked(viewport);
     }
 
     private LogicalDisplay findLogicalDisplayForDeviceLocked(DisplayDevice device) {
@@ -614,6 +745,10 @@
                 pw.println("  Display " + displayId + ":");
                 display.dumpLocked(ipw);
             }
+
+            pw.println();
+            pw.println("Default viewport: " + mDefaultViewport);
+            pw.println("External touch viewport: " + mExternalTouchViewport);
         }
     }
 
@@ -638,6 +773,18 @@
         void requestTraversal();
     }
 
+    /**
+     * Private interface to the input manager.
+     */
+    public interface InputManagerFuncs {
+        /**
+         * Sets information about the displays as needed by the input system.
+         * The input system should copy this information if required.
+         */
+        void setDisplayViewports(DisplayViewport defaultViewport,
+                DisplayViewport externalTouchViewport);
+    }
+
     private final class DisplayManagerHandler extends Handler {
         public DisplayManagerHandler(Looper looper) {
             super(looper, null, true /*async*/);
@@ -661,6 +808,16 @@
                 case MSG_REQUEST_TRAVERSAL:
                     mWindowManagerFuncs.requestTraversal();
                     break;
+
+                case MSG_UPDATE_VIEWPORT: {
+                    synchronized (mSyncRoot) {
+                        mTempDefaultViewport.copyFrom(mDefaultViewport);
+                        mTempExternalTouchViewport.copyFrom(mExternalTouchViewport);
+                    }
+                    mInputManagerFuncs.setDisplayViewports(
+                            mTempDefaultViewport, mTempExternalTouchViewport);
+                    break;
+                }
             }
         }
     }
diff --git a/services/java/com/android/server/display/DisplayViewport.java b/services/java/com/android/server/display/DisplayViewport.java
new file mode 100644
index 0000000..ed4016d
--- /dev/null
+++ b/services/java/com/android/server/display/DisplayViewport.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2012 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.server.display;
+
+import android.graphics.Rect;
+
+/**
+ * Describes how the pixels of physical display device reflects the content of
+ * a logical display.
+ * <p>
+ * This information is used by the input system to translate touch input from
+ * physical display coordinates into logical display coordinates.
+ * </p>
+ */
+public final class DisplayViewport {
+    // True if this viewport is valid.
+    public boolean valid;
+
+    // The logical display id.
+    public int displayId;
+
+    // The rotation applied to the physical coordinate system.
+    public int orientation;
+
+    // The portion of the logical display that are presented on this physical display.
+    public final Rect logicalFrame = new Rect();
+
+    // The portion of the (rotated) physical display that shows the logical display contents.
+    // The relation between logical and physical frame defines how the coordinate system
+    // should be scaled or translated after rotation.
+    public final Rect physicalFrame = new Rect();
+
+    public void copyFrom(DisplayViewport viewport) {
+        valid = viewport.valid;
+        displayId = viewport.displayId;
+        orientation = viewport.orientation;
+        logicalFrame.set(viewport.logicalFrame);
+        physicalFrame.set(viewport.physicalFrame);
+    }
+
+    // For debugging purposes.
+    @Override
+    public String toString() {
+        return "DisplayViewport{valid=" + valid
+                + ", displayId=" + displayId
+                + ", orientation=" + orientation
+                + ", logicalFrame=" + logicalFrame
+                + ", physicalFrame=" + physicalFrame
+                + "}";
+    }
+}
diff --git a/services/java/com/android/server/display/HeadlessDisplayAdapter.java b/services/java/com/android/server/display/HeadlessDisplayAdapter.java
index 3aaacf1..7629db6 100644
--- a/services/java/com/android/server/display/HeadlessDisplayAdapter.java
+++ b/services/java/com/android/server/display/HeadlessDisplayAdapter.java
@@ -61,6 +61,7 @@
                 mInfo.yDpi = 160;
                 mInfo.flags = DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY
                         | DisplayDeviceInfo.FLAG_SECURE;
+                mInfo.touch = DisplayDeviceInfo.TOUCH_NONE;
             }
             return mInfo;
         }
diff --git a/services/java/com/android/server/display/LocalDisplayAdapter.java b/services/java/com/android/server/display/LocalDisplayAdapter.java
index 4962753..80c860b 100644
--- a/services/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/java/com/android/server/display/LocalDisplayAdapter.java
@@ -129,10 +129,12 @@
                     mInfo.densityDpi = (int)(mPhys.density * 160 + 0.5f);
                     mInfo.xDpi = mPhys.xDpi;
                     mInfo.yDpi = mPhys.yDpi;
+                    mInfo.touch = DisplayDeviceInfo.TOUCH_INTERNAL;
                 } else {
                     mInfo.name = getContext().getResources().getString(
                             com.android.internal.R.string.display_manager_hdmi_display_name);
                     mInfo.flags = DisplayDeviceInfo.FLAG_SECURE;
+                    mInfo.touch = DisplayDeviceInfo.TOUCH_EXTERNAL;
                     mInfo.setAssumedDensityForExternalDisplay(mPhys.width, mPhys.height);
                 }
             }
diff --git a/services/java/com/android/server/display/LogicalDisplay.java b/services/java/com/android/server/display/LogicalDisplay.java
index e0f63dd..2e75260 100644
--- a/services/java/com/android/server/display/LogicalDisplay.java
+++ b/services/java/com/android/server/display/LogicalDisplay.java
@@ -54,6 +54,7 @@
 final class LogicalDisplay {
     private final DisplayInfo mBaseDisplayInfo = new DisplayInfo();
 
+    private final int mDisplayId;
     private final int mLayerStack;
     private DisplayInfo mOverrideDisplayInfo; // set by the window manager
     private DisplayInfo mInfo;
@@ -70,12 +71,22 @@
     private final Rect mTempLayerStackRect = new Rect();
     private final Rect mTempDisplayRect = new Rect();
 
-    public LogicalDisplay(int layerStack, DisplayDevice primaryDisplayDevice) {
+    public LogicalDisplay(int displayId, int layerStack, DisplayDevice primaryDisplayDevice) {
+        mDisplayId = displayId;
         mLayerStack = layerStack;
         mPrimaryDisplayDevice = primaryDisplayDevice;
     }
 
     /**
+     * Gets the logical display id of this logical display.
+     *
+     * @return The logical display id.
+     */
+    public int getDisplayIdLocked() {
+        return mDisplayId;
+    }
+
+    /**
      * Gets the primary display device associated with this logical display.
      *
      * @return The primary display device.
diff --git a/services/java/com/android/server/display/OverlayDisplayAdapter.java b/services/java/com/android/server/display/OverlayDisplayAdapter.java
index e2d3059..9b0e534 100644
--- a/services/java/com/android/server/display/OverlayDisplayAdapter.java
+++ b/services/java/com/android/server/display/OverlayDisplayAdapter.java
@@ -228,6 +228,7 @@
                 mInfo.xDpi = mDensityDpi;
                 mInfo.yDpi = mDensityDpi;
                 mInfo.flags = DisplayDeviceInfo.FLAG_SECURE;
+                mInfo.touch = DisplayDeviceInfo.TOUCH_NONE;
             }
             return mInfo;
         }
diff --git a/services/java/com/android/server/display/WifiDisplayAdapter.java b/services/java/com/android/server/display/WifiDisplayAdapter.java
index 38007af..ca500c0 100644
--- a/services/java/com/android/server/display/WifiDisplayAdapter.java
+++ b/services/java/com/android/server/display/WifiDisplayAdapter.java
@@ -20,6 +20,10 @@
 import com.android.internal.util.IndentingPrintWriter;
 
 import android.content.Context;
+import android.content.Intent;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.WifiDisplay;
+import android.hardware.display.WifiDisplayStatus;
 import android.media.RemoteDisplay;
 import android.os.Handler;
 import android.os.IBinder;
@@ -27,6 +31,7 @@
 import android.view.Surface;
 
 import java.io.PrintWriter;
+import java.util.Arrays;
 
 /**
  * Connects to Wifi displays that implement the Miracast protocol.
@@ -48,6 +53,15 @@
     private WifiDisplayHandle mDisplayHandle;
     private WifiDisplayController mDisplayController;
 
+    private WifiDisplayStatus mCurrentStatus;
+    private boolean mEnabled;
+    private WifiDisplay mConnectedDisplay;
+    private WifiDisplay[] mKnownDisplays = WifiDisplay.EMPTY_ARRAY;
+    private boolean mScanInProgress;
+    private boolean mConnectionInProgress;
+
+    private boolean mPendingStatusChangeBroadcast;
+
     public WifiDisplayAdapter(DisplayManagerService.SyncRoot syncRoot,
             Context context, Handler handler, Listener listener) {
         super(syncRoot, context, handler, listener, TAG);
@@ -64,6 +78,14 @@
             mDisplayHandle.dumpLocked(pw);
         }
 
+        pw.println("mCurrentStatus=" + getWifiDisplayStatusLocked());
+        pw.println("mEnabled=" + mEnabled);
+        pw.println("mConnectedDisplay=" + mConnectedDisplay);
+        pw.println("mKnownDisplays=" + Arrays.toString(mKnownDisplays));
+        pw.println("mScanInProgress=" + mScanInProgress);
+        pw.println("mConnectionInProgress=" + mConnectionInProgress);
+        pw.println("mPendingStatusChangeBroadcast=" + mPendingStatusChangeBroadcast);
+
         // Try to dump the controller state.
         if (mDisplayController == null) {
             pw.println("mDisplayController=null");
@@ -88,28 +110,160 @@
         });
     }
 
-    private void connectLocked(String deviceName, String iface) {
-        disconnectLocked();
-
-        String name = getContext().getResources().getString(
-                com.android.internal.R.string.display_manager_wifi_display_name,
-                deviceName);
-        mDisplayHandle = new WifiDisplayHandle(name, iface);
+    public void requestScanLocked() {
+        getHandler().post(new Runnable() {
+            @Override
+            public void run() {
+                if (mDisplayController != null) {
+                    mDisplayController.requestScan();
+                }
+            }
+        });
     }
 
-    private void disconnectLocked() {
+    public void requestConnectLocked(final String address) {
+        getHandler().post(new Runnable() {
+            @Override
+            public void run() {
+                if (mDisplayController != null) {
+                    mDisplayController.requestConnect(address);
+                }
+            }
+        });
+    }
+
+    public void requestDisconnectLocked() {
+        getHandler().post(new Runnable() {
+            @Override
+            public void run() {
+                if (mDisplayController != null) {
+                    mDisplayController.requestDisconnect();
+                }
+            }
+        });
+    }
+
+    public WifiDisplayStatus getWifiDisplayStatusLocked() {
+        if (mCurrentStatus == null) {
+            mCurrentStatus = new WifiDisplayStatus(mEnabled,
+                    mConnectedDisplay, mKnownDisplays,
+                    mScanInProgress, mConnectionInProgress);
+        }
+        return mCurrentStatus;
+    }
+
+    private void handleConnectLocked(WifiDisplay display, String iface) {
+        handleDisconnectLocked();
+
+        mDisplayHandle = new WifiDisplayHandle(display.getDeviceName(), iface);
+    }
+
+    private void handleDisconnectLocked() {
         if (mDisplayHandle != null) {
             mDisplayHandle.disposeLocked();
             mDisplayHandle = null;
         }
     }
 
+    private void scheduleStatusChangedBroadcastLocked() {
+        if (!mPendingStatusChangeBroadcast) {
+            mPendingStatusChangeBroadcast = true;
+            getHandler().post(mStatusChangeBroadcast);
+        }
+    }
+
+    private final Runnable mStatusChangeBroadcast = new Runnable() {
+        @Override
+        public void run() {
+            final Intent intent;
+            synchronized (getSyncRoot()) {
+                if (!mPendingStatusChangeBroadcast) {
+                    return;
+                }
+
+                mPendingStatusChangeBroadcast = false;
+                intent = new Intent(DisplayManager.ACTION_WIFI_DISPLAY_STATUS_CHANGED);
+                intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+                intent.putExtra(DisplayManager.EXTRA_WIFI_DISPLAY_STATUS,
+                        getWifiDisplayStatusLocked());
+            }
+
+            // Send protected broadcast about wifi display status to receivers that
+            // have the required permission.
+            getContext().sendBroadcast(intent,
+                    android.Manifest.permission.CONFIGURE_WIFI_DISPLAY);
+        }
+    };
+
     private final WifiDisplayController.Listener mWifiDisplayListener =
             new WifiDisplayController.Listener() {
         @Override
-        public void onDisplayConnected(String deviceName, String iface) {
+        public void onEnablementChanged(boolean enabled) {
             synchronized (getSyncRoot()) {
-                connectLocked(deviceName, iface);
+                if (mEnabled != enabled) {
+                    mCurrentStatus = null;
+                    mEnabled = enabled;
+                    scheduleStatusChangedBroadcastLocked();
+                }
+            }
+        }
+
+        @Override
+        public void onScanStarted() {
+            synchronized (getSyncRoot()) {
+                if (!mScanInProgress) {
+                    mCurrentStatus = null;
+                    mScanInProgress = true;
+                    scheduleStatusChangedBroadcastLocked();
+                }
+            }
+        }
+
+        public void onScanFinished(WifiDisplay[] knownDisplays) {
+            synchronized (getSyncRoot()) {
+                if (!Arrays.equals(mKnownDisplays, knownDisplays) || mScanInProgress) {
+                    mCurrentStatus = null;
+                    mKnownDisplays = knownDisplays;
+                    mScanInProgress = false;
+                    scheduleStatusChangedBroadcastLocked();
+                }
+            }
+        }
+
+        @Override
+        public void onDisplayConnecting(WifiDisplay display) {
+            synchronized (getSyncRoot()) {
+                if (!mConnectionInProgress) {
+                    mCurrentStatus = null;
+                    mConnectionInProgress = true;
+                    scheduleStatusChangedBroadcastLocked();
+                }
+            }
+        }
+
+        @Override
+        public void onDisplayConnectionFailed() {
+            synchronized (getSyncRoot()) {
+                if (mConnectionInProgress) {
+                    mCurrentStatus = null;
+                    mConnectionInProgress = false;
+                    scheduleStatusChangedBroadcastLocked();
+                }
+            }
+        }
+
+        @Override
+        public void onDisplayConnected(WifiDisplay display, String iface) {
+            synchronized (getSyncRoot()) {
+                handleConnectLocked(display, iface);
+
+                if (mConnectedDisplay == null || !mConnectedDisplay.equals(display)
+                        || mConnectionInProgress) {
+                    mCurrentStatus = null;
+                    mConnectedDisplay = display;
+                    mConnectionInProgress = false;
+                    scheduleStatusChangedBroadcastLocked();
+                }
             }
         }
 
@@ -117,7 +271,14 @@
         public void onDisplayDisconnected() {
             // Stop listening.
             synchronized (getSyncRoot()) {
-                disconnectLocked();
+                handleDisconnectLocked();
+
+                if (mConnectedDisplay != null || mConnectionInProgress) {
+                    mCurrentStatus = null;
+                    mConnectedDisplay = null;
+                    mConnectionInProgress = false;
+                    scheduleStatusChangedBroadcastLocked();
+                }
             }
         }
     };
@@ -163,6 +324,7 @@
                 mInfo.height = mHeight;
                 mInfo.refreshRate = mRefreshRate;
                 mInfo.flags = mFlags;
+                mInfo.touch = DisplayDeviceInfo.TOUCH_EXTERNAL;
                 mInfo.setAssumedDensityForExternalDisplay(mWidth, mHeight);
             }
             return mInfo;
diff --git a/services/java/com/android/server/display/WifiDisplayController.java b/services/java/com/android/server/display/WifiDisplayController.java
index b446e66..131502f 100644
--- a/services/java/com/android/server/display/WifiDisplayController.java
+++ b/services/java/com/android/server/display/WifiDisplayController.java
@@ -22,6 +22,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.hardware.display.WifiDisplay;
 import android.net.NetworkInfo;
 import android.net.wifi.p2p.WifiP2pConfig;
 import android.net.wifi.p2p.WifiP2pDevice;
@@ -64,6 +65,12 @@
     private static final int MAX_THROUGHPUT = 50;
     private static final int CONNECTION_TIMEOUT_SECONDS = 30;
 
+    private static final int DISCOVER_PEERS_MAX_RETRIES = 10;
+    private static final int DISCOVER_PEERS_RETRY_DELAY_MILLIS = 500;
+
+    private static final int CONNECT_MAX_RETRIES = 3;
+    private static final int CONNECT_RETRY_DELAY_MILLIS = 500;
+
     private final Context mContext;
     private final Handler mHandler;
     private final Listener mListener;
@@ -78,6 +85,12 @@
     private final ArrayList<WifiP2pDevice> mKnownWifiDisplayPeers =
             new ArrayList<WifiP2pDevice>();
 
+    // True if there is a call to discoverPeers in progress.
+    private boolean mDiscoverPeersInProgress;
+
+    // Number of discover peers retries remaining.
+    private int mDiscoverPeersRetriesLeft;
+
     // The device to which we want to connect, or null if we want to be disconnected.
     private WifiP2pDevice mDesiredDevice;
 
@@ -94,6 +107,9 @@
     // The device that we announced to the rest of the system.
     private WifiP2pDevice mPublishedDevice;
 
+    // Number of connection retries remaining.
+    private int mConnectionRetriesLeft;
+
     public WifiDisplayController(Context context, Handler handler, Listener listener) {
         mContext = context;
         mHandler = handler;
@@ -114,10 +130,13 @@
         pw.println("mWfdEnabled=" + mWfdEnabled);
         pw.println("mWfdEnabling=" + mWfdEnabling);
         pw.println("mNetworkInfo=" + mNetworkInfo);
+        pw.println("mDiscoverPeersInProgress=" + mDiscoverPeersInProgress);
+        pw.println("mDiscoverPeersRetriesLeft=" + mDiscoverPeersRetriesLeft);
         pw.println("mDesiredDevice=" + describeWifiP2pDevice(mDesiredDevice));
         pw.println("mConnectingDisplay=" + describeWifiP2pDevice(mConnectingDevice));
         pw.println("mConnectedDevice=" + describeWifiP2pDevice(mConnectedDevice));
         pw.println("mPublishedDevice=" + describeWifiP2pDevice(mPublishedDevice));
+        pw.println("mConnectionRetriesLeft=" + mConnectionRetriesLeft);
 
         pw.println("mKnownWifiDisplayPeers: size=" + mKnownWifiDisplayPeers.size());
         for (WifiP2pDevice device : mKnownWifiDisplayPeers) {
@@ -125,6 +144,22 @@
         }
     }
 
+    public void requestScan() {
+        discoverPeers();
+    }
+
+    public void requestConnect(String address) {
+        for (WifiP2pDevice device : mKnownWifiDisplayPeers) {
+            if (device.deviceAddress.equals(address)) {
+                connect(device);
+            }
+        }
+    }
+
+    public void requestDisconnect() {
+        disconnect();
+    }
+
     private void enableWfd() {
         if (!mWfdEnabled && !mWfdEnabling) {
             mWfdEnabling = true;
@@ -142,8 +177,8 @@
                         Slog.d(TAG, "Successfully set WFD info.");
                     }
                     if (mWfdEnabling) {
-                        mWfdEnabled = true;
                         mWfdEnabling = false;
+                        setWfdEnabled(true);
                         discoverPeers();
                     }
                 }
@@ -159,7 +194,28 @@
         }
     }
 
+    private void setWfdEnabled(final boolean enabled) {
+        if (mWfdEnabled != enabled) {
+            mWfdEnabled = enabled;
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    mListener.onEnablementChanged(enabled);
+                }
+            });
+        }
+    }
+
     private void discoverPeers() {
+        if (!mDiscoverPeersInProgress) {
+            mDiscoverPeersInProgress = true;
+            mDiscoverPeersRetriesLeft = DISCOVER_PEERS_MAX_RETRIES;
+            handleScanStarted();
+            tryDiscoverPeers();
+        }
+    }
+
+    private void tryDiscoverPeers() {
         mWifiP2pManager.discoverPeers(mWifiP2pChannel, new ActionListener() {
             @Override
             public void onSuccess() {
@@ -167,6 +223,7 @@
                     Slog.d(TAG, "Discover peers succeeded.  Requesting peers now.");
                 }
 
+                mDiscoverPeersInProgress = false;
                 requestPeers();
             }
 
@@ -175,6 +232,32 @@
                 if (DEBUG) {
                     Slog.d(TAG, "Discover peers failed with reason " + reason + ".");
                 }
+
+                if (mDiscoverPeersInProgress) {
+                    if (reason == 0 && mDiscoverPeersRetriesLeft > 0 && mWfdEnabled) {
+                        mHandler.postDelayed(new Runnable() {
+                            @Override
+                            public void run() {
+                                if (mDiscoverPeersInProgress) {
+                                    if (mDiscoverPeersRetriesLeft > 0 && mWfdEnabled) {
+                                        mDiscoverPeersRetriesLeft -= 1;
+                                        if (DEBUG) {
+                                            Slog.d(TAG, "Retrying discovery.  Retries left: "
+                                                    + mDiscoverPeersRetriesLeft);
+                                        }
+                                        tryDiscoverPeers();
+                                    } else {
+                                        handleScanFinished();
+                                        mDiscoverPeersInProgress = false;
+                                    }
+                                }
+                            }
+                        }, DISCOVER_PEERS_RETRY_DELAY_MILLIS);
+                    } else {
+                        handleScanFinished();
+                        mDiscoverPeersInProgress = false;
+                    }
+                }
             }
         });
     }
@@ -198,16 +281,31 @@
                     }
                 }
 
-                // TODO: shouldn't auto-connect like this, let UI do it explicitly
-                if (!mKnownWifiDisplayPeers.isEmpty()) {
-                    final WifiP2pDevice device = mKnownWifiDisplayPeers.get(0);
+                handleScanFinished();
+            }
+        });
+    }
 
-                    if (device.status == WifiP2pDevice.AVAILABLE) {
-                        connect(device);
-                    }
-                }
+    private void handleScanStarted() {
+        mHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                mListener.onScanStarted();
+            }
+        });
+    }
 
-                // TODO: publish this information to applications
+    private void handleScanFinished() {
+        final int count = mKnownWifiDisplayPeers.size();
+        final WifiDisplay[] displays = WifiDisplay.CREATOR.newArray(count);
+        for (int i = 0; i < count; i++) {
+            displays[i] = createWifiDisplay(mKnownWifiDisplayPeers.get(i));
+        }
+
+        mHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                mListener.onScanFinished(displays);
             }
         });
     }
@@ -230,9 +328,11 @@
                         + describeWifiP2pDevice(device) + " and not part way through "
                         + "connecting to a different device.");
             }
+            return;
         }
 
         mDesiredDevice = device;
+        mConnectionRetriesLeft = CONNECT_MAX_RETRIES;
         updateConnection();
     }
 
@@ -241,6 +341,21 @@
         updateConnection();
     }
 
+    private void retryConnection() {
+        if (mDesiredDevice != null && mPublishedDevice != mDesiredDevice
+                && mConnectionRetriesLeft > 0) {
+            mConnectionRetriesLeft -= 1;
+            Slog.i(TAG, "Retrying Wifi display connection.  Retries left: "
+                    + mConnectionRetriesLeft);
+
+            // Cheap hack.  Make a new instance of the device object so that we
+            // can distinguish it from the previous connection attempt.
+            // This will cause us to tear everything down before we try again.
+            mDesiredDevice = new WifiP2pDevice(mDesiredDevice);
+            updateConnection();
+        }
+    }
+
     /**
      * This function is called repeatedly after each asynchronous operation
      * until all preconditions for the connection have been satisfied and the
@@ -335,6 +450,14 @@
             WifiP2pConfig config = new WifiP2pConfig();
             config.deviceAddress = mConnectingDevice.deviceAddress;
 
+            final WifiDisplay display = createWifiDisplay(mConnectingDevice);
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    mListener.onDisplayConnecting(display);
+                }
+            });
+
             final WifiP2pDevice newDevice = mDesiredDevice;
             mWifiP2pManager.connect(mWifiP2pChannel, config, new ActionListener() {
                 @Override
@@ -353,7 +476,7 @@
                             + newDevice.deviceName + ", reason=" + reason);
                     if (mConnectingDevice == newDevice) {
                         mConnectingDevice = null;
-                        handleConnectionFailure();
+                        handleConnectionFailure(false);
                     }
                 }
             });
@@ -366,20 +489,20 @@
             if (addr == null) {
                 Slog.i(TAG, "Failed to get local interface address for communicating "
                         + "with Wifi display: " + mConnectedDevice.deviceName);
-                handleConnectionFailure();
+                handleConnectionFailure(false);
                 return; // done
             }
 
             WifiP2pWfdInfo wfdInfo = mConnectedDevice.wfdInfo;
             int port = (wfdInfo != null ? wfdInfo.getControlPort() : DEFAULT_CONTROL_PORT);
-            final String name = mConnectedDevice.deviceName;
+            final WifiDisplay display = createWifiDisplay(mConnectedDevice);
             final String iface = addr.getHostAddress() + ":" + port;
 
             mPublishedDevice = mConnectedDevice;
             mHandler.post(new Runnable() {
                 @Override
                 public void run() {
-                    mListener.onDisplayConnected(name, iface);
+                    mListener.onDisplayConnected(display, iface);
                 }
             });
         }
@@ -395,7 +518,7 @@
                     enableWfd();
                 }
             } else {
-                mWfdEnabled = false;
+                setWfdEnabled(false);
                 disconnect();
             }
         }
@@ -426,7 +549,7 @@
                             Slog.i(TAG, "Aborting connection to Wifi display because "
                                     + "the current P2P group does not contain the device "
                                     + "we expected to find: " + mConnectingDevice.deviceName);
-                            handleConnectionFailure();
+                            handleConnectionFailure(false);
                             return;
                         }
 
@@ -436,7 +559,8 @@
                         }
 
                         if (mConnectingDevice != null && mConnectingDevice == mDesiredDevice) {
-                            Slog.i(TAG, "Connected to Wifi display: " + mConnectingDevice.deviceName);
+                            Slog.i(TAG, "Connected to Wifi display: "
+                                    + mConnectingDevice.deviceName);
 
                             mHandler.removeCallbacks(mConnectionTimeout);
                             mConnectedDeviceGroupInfo = info;
@@ -459,15 +583,32 @@
                 Slog.i(TAG, "Timed out waiting for Wifi display connection after "
                         + CONNECTION_TIMEOUT_SECONDS + " seconds: "
                         + mConnectingDevice.deviceName);
-                handleConnectionFailure();
+                handleConnectionFailure(true);
             }
         }
     };
 
-    private void handleConnectionFailure() {
+    private void handleConnectionFailure(boolean timeoutOccurred) {
         if (mDesiredDevice != null) {
             Slog.i(TAG, "Wifi display connection failed!");
-            disconnect();
+
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    mListener.onDisplayConnectionFailed();
+                }
+            });
+
+            if (mConnectionRetriesLeft > 0) {
+                mHandler.postDelayed(new Runnable() {
+                    @Override
+                    public void run() {
+                        retryConnection();
+                    }
+                }, timeoutOccurred ? 0 : CONNECT_RETRY_DELAY_MILLIS);
+            } else {
+                disconnect();
+            }
         }
     }
 
@@ -496,12 +637,10 @@
 
     private static boolean isWifiDisplay(WifiP2pDevice device) {
         // FIXME: the wfdInfo API doesn't work yet
-        return false;
-        //return device.deviceName.equals("DWD-300-22ACC2");
-        //return device.deviceName.startsWith("DWD-")
-        //        || device.deviceName.startsWith("DIRECT-")
-        //        || device.deviceName.startsWith("CAVM-");
-        //return device.wfdInfo != null && device.wfdInfo.isWfdEnabled();
+        return device.deviceName.startsWith("DWD-")
+                || device.deviceName.startsWith("DIRECT-")
+                || device.deviceName.startsWith("CAVM-");
+        //device.wfdInfo != null && device.wfdInfo.isWfdEnabled();
     }
 
     private static String describeWifiP2pDevice(WifiP2pDevice device) {
@@ -512,6 +651,10 @@
         return group != null ? group.toString().replace('\n', ',') : "null";
     }
 
+    private static WifiDisplay createWifiDisplay(WifiP2pDevice device) {
+        return new WifiDisplay(device.deviceAddress, device.deviceName);
+    }
+
     private final BroadcastReceiver mWifiP2pReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
@@ -549,7 +692,14 @@
      * Called on the handler thread when displays are connected or disconnected.
      */
     public interface Listener {
-        void onDisplayConnected(String deviceName, String iface);
+        void onEnablementChanged(boolean enabled);
+
+        void onScanStarted();
+        void onScanFinished(WifiDisplay[] knownDisplays);
+
+        void onDisplayConnecting(WifiDisplay display);
+        void onDisplayConnectionFailed();
+        void onDisplayConnected(WifiDisplay display, String iface);
         void onDisplayDisconnected();
     }
 }
diff --git a/services/java/com/android/server/input/InputManagerService.java b/services/java/com/android/server/input/InputManagerService.java
index fd4f5fc..95655a5 100644
--- a/services/java/com/android/server/input/InputManagerService.java
+++ b/services/java/com/android/server/input/InputManagerService.java
@@ -19,6 +19,8 @@
 import com.android.internal.R;
 import com.android.internal.util.XmlUtils;
 import com.android.server.Watchdog;
+import com.android.server.display.DisplayManagerService;
+import com.android.server.display.DisplayViewport;
 
 import org.xmlpull.v1.XmlPullParser;
 
@@ -90,7 +92,8 @@
 /*
  * Wraps the C++ InputManager and provides its callbacks.
  */
-public class InputManagerService extends IInputManager.Stub implements Watchdog.Monitor {
+public class InputManagerService extends IInputManager.Stub
+        implements Watchdog.Monitor, DisplayManagerService.InputManagerFuncs {
     static final String TAG = "InputManager";
     static final boolean DEBUG = false;
 
@@ -143,11 +146,11 @@
     private static native int nativeInit(InputManagerService service,
             Context context, MessageQueue messageQueue);
     private static native void nativeStart(int ptr);
-    private static native void nativeSetDisplaySize(int ptr, int displayId,
-            int width, int height, int externalWidth, int externalHeight);
-    private static native void nativeSetDisplayOrientation(int ptr, int displayId,
-            int rotation, int externalRotation);
-    
+    private static native void nativeSetDisplayViewport(int ptr, boolean external,
+            int displayId, int rotation,
+            int logicalLeft, int logicalTop, int logicalRight, int logicalBottom,
+            int physicalLeft, int physicalTop, int physicalRight, int physicalBottom);
+
     private static native int nativeGetScanCodeState(int ptr,
             int deviceId, int sourceMask, int scanCode);
     private static native int nativeGetKeyCodeState(int ptr,
@@ -282,28 +285,27 @@
         nativeReloadDeviceAliases(mPtr);
     }
 
-    public void setDisplaySize(int displayId, int width, int height) {
-        if (width <= 0 || height <= 0) {
-            throw new IllegalArgumentException("Invalid display id or dimensions.");
+    @Override
+    public void setDisplayViewports(DisplayViewport defaultViewport,
+            DisplayViewport externalTouchViewport) {
+        if (defaultViewport.valid) {
+            setDisplayViewport(false, defaultViewport);
         }
-        
-        if (DEBUG) {
-            Slog.d(TAG, "Setting display #" + displayId + " size to " + width + "x" + height);
+
+        if (externalTouchViewport.valid) {
+            setDisplayViewport(true, externalTouchViewport);
+        } else if (defaultViewport.valid) {
+            setDisplayViewport(true, defaultViewport);
         }
-        // FIXME: external size is deprecated
-        nativeSetDisplaySize(mPtr, displayId, width, height, 1280, 720);
     }
 
-    public void setDisplayOrientation(int displayId, int rotation) {
-        if (rotation < Surface.ROTATION_0 || rotation > Surface.ROTATION_270) {
-            throw new IllegalArgumentException("Invalid rotation.");
-        }
-        
-        if (DEBUG) {
-            Slog.d(TAG, "Setting display #" + displayId + " orientation to rotation " + rotation);
-        }
-        // FIXME: external rotation is deprecated
-        nativeSetDisplayOrientation(mPtr, displayId, rotation, Surface.ROTATION_0);
+    private void setDisplayViewport(boolean external, DisplayViewport viewport) {
+        nativeSetDisplayViewport(mPtr, external,
+                viewport.displayId, viewport.orientation,
+                viewport.logicalFrame.left, viewport.logicalFrame.top,
+                viewport.logicalFrame.right, viewport.logicalFrame.bottom,
+                viewport.physicalFrame.left, viewport.physicalFrame.top,
+                viewport.physicalFrame.right, viewport.physicalFrame.bottom);
     }
 
     /**
diff --git a/services/java/com/android/server/power/DisplayPowerController.java b/services/java/com/android/server/power/DisplayPowerController.java
index 4f5561a..5f4a786 100644
--- a/services/java/com/android/server/power/DisplayPowerController.java
+++ b/services/java/com/android/server/power/DisplayPowerController.java
@@ -83,7 +83,8 @@
     private static final boolean USE_ELECTRON_BEAM_ON_ANIMATION = false;
 
     // If true, enables the use of the screen auto-brightness adjustment setting.
-    private static final boolean USE_SCREEN_AUTO_BRIGHTNESS_ADJUSTMENT = false;
+    private static final boolean USE_SCREEN_AUTO_BRIGHTNESS_ADJUSTMENT =
+            PowerManager.useScreenAutoBrightnessAdjustmentFeature();
 
     // The maximum range of gamma adjustment possible using the screen
     // auto-brightness adjustment setting.
diff --git a/services/java/com/android/server/wm/WindowManagerService.java b/services/java/com/android/server/wm/WindowManagerService.java
index 18e793d..cdca8bc 100755
--- a/services/java/com/android/server/wm/WindowManagerService.java
+++ b/services/java/com/android/server/wm/WindowManagerService.java
@@ -5947,7 +5947,6 @@
         mWaitingForConfig = true;
         getDefaultDisplayContent().layoutNeeded = true;
         startFreezingDisplayLocked(inTransaction);
-        mInputManager.setDisplayOrientation(0, rotation);
 
         // We need to update our screen size information to match the new
         // rotation.  Note that this is redundant with the later call to
@@ -7121,10 +7120,6 @@
                 displayInfo.appWidth, displayInfo.appHeight);
 
             final DisplayContent displayContent = getDefaultDisplayContent();
-            mInputManager.setDisplaySize(displayContent.getDisplayId(),
-                    displayContent.mInitialDisplayWidth, displayContent.mInitialDisplayHeight);
-            mInputManager.setDisplayOrientation(displayContent.getDisplayId(),
-                    mDefaultDisplay.getRotation());
             mPolicy.setInitialDisplaySize(mDefaultDisplay, displayContent.mInitialDisplayWidth,
                     displayContent.mInitialDisplayHeight, displayContent.mInitialDisplayDensity);
         }
diff --git a/services/jni/com_android_server_input_InputManagerService.cpp b/services/jni/com_android_server_input_InputManagerService.cpp
index 495d4ab..5e36bf8 100644
--- a/services/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/jni/com_android_server_input_InputManagerService.cpp
@@ -164,9 +164,7 @@
 
     void dump(String8& dump);
 
-    void setDisplaySize(int32_t displayId, int32_t width, int32_t height,
-            int32_t externalWidth, int32_t externalHeight);
-    void setDisplayOrientation(int32_t displayId, int32_t orientation, int32_t externalOrientation);
+    void setDisplayViewport(bool external, const DisplayViewport& viewport);
 
     status_t registerInputChannel(JNIEnv* env, const sp<InputChannel>& inputChannel,
             const sp<InputWindowHandle>& inputWindowHandle, bool monitor);
@@ -223,10 +221,8 @@
     Mutex mLock;
     struct Locked {
         // Display size information.
-        int32_t displayWidth, displayHeight; // -1 when not initialized
-        int32_t displayOrientation;
-        int32_t displayExternalWidth, displayExternalHeight; // -1 when not initialized
-        int32_t displayExternalOrientation;
+        DisplayViewport internalViewport;
+        DisplayViewport externalViewport;
 
         // System UI visibility.
         int32_t systemUiVisibility;
@@ -274,13 +270,6 @@
 
     {
         AutoMutex _l(mLock);
-        mLocked.displayWidth = -1;
-        mLocked.displayHeight = -1;
-        mLocked.displayOrientation = DISPLAY_ORIENTATION_0;
-        mLocked.displayExternalWidth = -1;
-        mLocked.displayExternalHeight = -1;
-        mLocked.displayExternalOrientation = DISPLAY_ORIENTATION_0;
-
         mLocked.systemUiVisibility = ASYSTEM_UI_VISIBILITY_STATUS_BAR_VISIBLE;
         mLocked.pointerSpeed = 0;
         mLocked.pointerGesturesEnabled = true;
@@ -316,57 +305,26 @@
     return false;
 }
 
-void NativeInputManager::setDisplaySize(int32_t displayId, int32_t width, int32_t height,
-        int32_t externalWidth, int32_t externalHeight) {
+void NativeInputManager::setDisplayViewport(bool external, const DisplayViewport& viewport) {
     bool changed = false;
-    if (displayId == 0) {
+    {
         AutoMutex _l(mLock);
 
-        if (mLocked.displayWidth != width || mLocked.displayHeight != height) {
+        DisplayViewport& v = external ? mLocked.externalViewport : mLocked.internalViewport;
+        if (v != viewport) {
             changed = true;
-            mLocked.displayWidth = width;
-            mLocked.displayHeight = height;
+            v = viewport;
 
-            sp<PointerController> controller = mLocked.pointerController.promote();
-            if (controller != NULL) {
-                controller->setDisplaySize(width, height);
+            if (!external) {
+                sp<PointerController> controller = mLocked.pointerController.promote();
+                if (controller != NULL) {
+                    controller->setDisplayViewport(
+                            viewport.logicalRight - viewport.logicalLeft,
+                            viewport.logicalBottom - viewport.logicalTop,
+                            viewport.orientation);
+                }
             }
         }
-
-        if (mLocked.displayExternalWidth != externalWidth
-                || mLocked.displayExternalHeight != externalHeight) {
-            changed = true;
-            mLocked.displayExternalWidth = externalWidth;
-            mLocked.displayExternalHeight = externalHeight;
-        }
-    }
-
-    if (changed) {
-        mInputManager->getReader()->requestRefreshConfiguration(
-                InputReaderConfiguration::CHANGE_DISPLAY_INFO);
-    }
-}
-
-void NativeInputManager::setDisplayOrientation(int32_t displayId, int32_t orientation,
-        int32_t externalOrientation) {
-    bool changed = false;
-    if (displayId == 0) {
-        AutoMutex _l(mLock);
-
-        if (mLocked.displayOrientation != orientation) {
-            changed = true;
-            mLocked.displayOrientation = orientation;
-
-            sp<PointerController> controller = mLocked.pointerController.promote();
-            if (controller != NULL) {
-                controller->setDisplayOrientation(orientation);
-            }
-        }
-
-        if (mLocked.displayExternalOrientation != externalOrientation) {
-            changed = true;
-            mLocked.displayExternalOrientation = externalOrientation;
-        }
     }
 
     if (changed) {
@@ -448,11 +406,8 @@
 
         outConfig->showTouches = mLocked.showTouches;
 
-        outConfig->setDisplayInfo(0, false /*external*/,
-                mLocked.displayWidth, mLocked.displayHeight, mLocked.displayOrientation);
-        outConfig->setDisplayInfo(0, true /*external*/,
-                mLocked.displayExternalWidth, mLocked.displayExternalHeight,
-                mLocked.displayExternalOrientation);
+        outConfig->setDisplayInfo(false /*external*/, mLocked.internalViewport);
+        outConfig->setDisplayInfo(true /*external*/, mLocked.externalViewport);
     } // release lock
 }
 
@@ -466,8 +421,11 @@
         controller = new PointerController(this, mLooper, mLocked.spriteController);
         mLocked.pointerController = controller;
 
-        controller->setDisplaySize(mLocked.displayWidth, mLocked.displayHeight);
-        controller->setDisplayOrientation(mLocked.displayOrientation);
+        DisplayViewport& v = mLocked.internalViewport;
+        controller->setDisplayViewport(
+                v.logicalRight - v.logicalLeft,
+                v.logicalBottom - v.logicalTop,
+                v.orientation);
 
         JNIEnv* env = jniEnv();
         jobject pointerIconObj = env->CallObjectMethod(mServiceObj,
@@ -1032,22 +990,24 @@
     }
 }
 
-static void nativeSetDisplaySize(JNIEnv* env, jclass clazz, jint ptr,
-        jint displayId, jint width, jint height, jint externalWidth, jint externalHeight) {
+static void nativeSetDisplayViewport(JNIEnv* env, jclass clazz, jint ptr, jboolean external,
+        jint displayId, jint orientation,
+        jint logicalLeft, jint logicalTop, jint logicalRight, jint logicalBottom,
+        jint physicalLeft, jint physicalTop, jint physicalRight, jint physicalBottom) {
     NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
 
-    // XXX we could get this from the SurfaceFlinger directly instead of requiring it
-    // to be passed in like this, not sure which is better but leaving it like this
-    // keeps the window manager in direct control of when display transitions propagate down
-    // to the input dispatcher
-    im->setDisplaySize(displayId, width, height, externalWidth, externalHeight);
-}
-
-static void nativeSetDisplayOrientation(JNIEnv* env, jclass clazz,
-        jint ptr, jint displayId, jint orientation, jint externalOrientation) {
-    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
-
-    im->setDisplayOrientation(displayId, orientation, externalOrientation);
+    DisplayViewport v;
+    v.displayId = displayId;
+    v.orientation = orientation;
+    v.logicalLeft = logicalLeft;
+    v.logicalTop = logicalTop;
+    v.logicalRight = logicalRight;
+    v.logicalBottom = logicalBottom;
+    v.physicalLeft = physicalLeft;
+    v.physicalTop = physicalTop;
+    v.physicalRight = physicalRight;
+    v.physicalBottom = physicalBottom;
+    im->setDisplayViewport(external, v);
 }
 
 static jint nativeGetScanCodeState(JNIEnv* env, jclass clazz,
@@ -1328,10 +1288,8 @@
             (void*) nativeInit },
     { "nativeStart", "(I)V",
             (void*) nativeStart },
-    { "nativeSetDisplaySize", "(IIIIII)V",
-            (void*) nativeSetDisplaySize },
-    { "nativeSetDisplayOrientation", "(IIII)V",
-            (void*) nativeSetDisplayOrientation },
+    { "nativeSetDisplayViewport", "(IZIIIIIIIIII)V",
+            (void*) nativeSetDisplayViewport },
     { "nativeGetScanCodeState", "(IIII)I",
             (void*) nativeGetScanCodeState },
     { "nativeGetKeyCodeState", "(IIII)I",