Add support for remembering Wifi display devices.

Add a setting to globally disable Wifi display.

Fixed a bug where the wifi display broadcast receiver
was running on the wrong thread.

Removed the wifi-display QuickSettings dialog, all functionality
has been moved to Settings.

Bug: 7178216
Bug: 7192799
Change-Id: I9796baac8245d664cf28fa147b9ed978d81d8ab9
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index 4347e75..58a0f13 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -160,6 +160,10 @@
     /**
      * Connects to a Wifi display.
      * The results are sent as a {@link #ACTION_WIFI_DISPLAY_STATUS_CHANGED} broadcast.
+     * <p>
+     * Automatically remembers the display after a successful connection, if not
+     * already remembered.
+     * </p>
      *
      * @param deviceAddress The MAC address of the device to which we should connect.
      * @hide
@@ -178,6 +182,36 @@
     }
 
     /**
+     * Renames a Wifi display.
+     * <p>
+     * The display must already be remembered for this call to succeed.  In other words,
+     * we must already have successfully connected to the display at least once and then
+     * not forgotten it.
+     * </p>
+     *
+     * @param deviceAddress The MAC address of the device to rename.
+     * @param alias The alias name by which to remember the device, or null
+     * or empty if no alias should be used.
+     * @hide
+     */
+    public void renameWifiDisplay(String deviceAddress, String alias) {
+        mGlobal.renameWifiDisplay(deviceAddress, alias);
+    }
+
+    /**
+     * Forgets a previously remembered Wifi display.
+     * <p>
+     * Automatically disconnects from the display if currently connected to it.
+     * </p>
+     *
+     * @param deviceAddress The MAC address of the device to forget.
+     * @hide
+     */
+    public void forgetWifiDisplay(String deviceAddress) {
+        mGlobal.forgetWifiDisplay(deviceAddress);
+    }
+
+    /**
      * Gets the current Wifi display status.
      * Watch for changes in the status by registering a broadcast receiver for
      * {@link #ACTION_WIFI_DISPLAY_STATUS_CHANGED}.
diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java
index 14b5440..a858681 100644
--- a/core/java/android/hardware/display/DisplayManagerGlobal.java
+++ b/core/java/android/hardware/display/DisplayManagerGlobal.java
@@ -281,6 +281,31 @@
         }
     }
 
+    public void renameWifiDisplay(String deviceAddress, String alias) {
+        if (deviceAddress == null) {
+            throw new IllegalArgumentException("deviceAddress must not be null");
+        }
+
+        try {
+            mDm.renameWifiDisplay(deviceAddress, alias);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to rename Wifi display " + deviceAddress
+                    + " with alias " + alias + ".", ex);
+        }
+    }
+
+    public void forgetWifiDisplay(String deviceAddress) {
+        if (deviceAddress == null) {
+            throw new IllegalArgumentException("deviceAddress must not be null");
+        }
+
+        try {
+            mDm.forgetWifiDisplay(deviceAddress);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to forget Wifi display.", ex);
+        }
+    }
+
     public WifiDisplayStatus getWifiDisplayStatus() {
         try {
             return mDm.getWifiDisplayStatus();
diff --git a/core/java/android/hardware/display/IDisplayManager.aidl b/core/java/android/hardware/display/IDisplayManager.aidl
index 36a9a7f..4b6fb53 100644
--- a/core/java/android/hardware/display/IDisplayManager.aidl
+++ b/core/java/android/hardware/display/IDisplayManager.aidl
@@ -38,5 +38,11 @@
     void disconnectWifiDisplay();
 
     // Requires CONFIGURE_WIFI_DISPLAY permission.
+    void renameWifiDisplay(String address, String alias);
+
+    // Requires CONFIGURE_WIFI_DISPLAY permission.
+    void forgetWifiDisplay(String address);
+
+    // Requires CONFIGURE_WIFI_DISPLAY permission.
     WifiDisplayStatus getWifiDisplayStatus();
 }
diff --git a/core/java/android/hardware/display/WifiDisplay.java b/core/java/android/hardware/display/WifiDisplay.java
index e51e97e..0138b1c 100644
--- a/core/java/android/hardware/display/WifiDisplay.java
+++ b/core/java/android/hardware/display/WifiDisplay.java
@@ -19,6 +19,8 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import libcore.util.Objects;
+
 /**
  * Describes the properties of a Wifi display.
  * <p>
@@ -30,6 +32,7 @@
 public final class WifiDisplay implements Parcelable {
     private final String mDeviceAddress;
     private final String mDeviceName;
+    private final String mDeviceAlias;
 
     public static final WifiDisplay[] EMPTY_ARRAY = new WifiDisplay[0];
 
@@ -37,7 +40,8 @@
         public WifiDisplay createFromParcel(Parcel in) {
             String deviceAddress = in.readString();
             String deviceName = in.readString();
-            return new WifiDisplay(deviceAddress, deviceName);
+            String deviceAlias = in.readString();
+            return new WifiDisplay(deviceAddress, deviceName, deviceAlias);
         }
 
         public WifiDisplay[] newArray(int size) {
@@ -45,7 +49,7 @@
         }
     };
 
-    public WifiDisplay(String deviceAddress, String deviceName) {
+    public WifiDisplay(String deviceAddress, String deviceName, String deviceAlias) {
         if (deviceAddress == null) {
             throw new IllegalArgumentException("deviceAddress must not be null");
         }
@@ -55,6 +59,7 @@
 
         mDeviceAddress = deviceAddress;
         mDeviceName = deviceName;
+        mDeviceAlias = deviceAlias;
     }
 
     /**
@@ -71,6 +76,25 @@
         return mDeviceName;
     }
 
+    /**
+     * Gets the user-specified alias of the Wifi display device, or null if none.
+     * <p>
+     * The alias should be used in the UI whenever available.  It is the value
+     * provided by the user when renaming the device.
+     * </p>
+     */
+    public String getDeviceAlias() {
+        return mDeviceAlias;
+    }
+
+    /**
+     * Gets the name to show in the UI.
+     * Uses the device alias if available, otherwise uses the device name.
+     */
+    public String getFriendlyDisplayName() {
+        return mDeviceAlias != null ? mDeviceAlias : mDeviceName;
+    }
+
     @Override
     public boolean equals(Object o) {
         return o instanceof WifiDisplay && equals((WifiDisplay)o);
@@ -79,7 +103,8 @@
     public boolean equals(WifiDisplay other) {
         return other != null
                 && mDeviceAddress.equals(other.mDeviceAddress)
-                && mDeviceName.equals(other.mDeviceName);
+                && mDeviceName.equals(other.mDeviceName)
+                && Objects.equal(mDeviceAlias, other.mDeviceAlias);
     }
 
     @Override
@@ -92,6 +117,7 @@
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeString(mDeviceAddress);
         dest.writeString(mDeviceName);
+        dest.writeString(mDeviceAlias);
     }
 
     @Override
@@ -102,6 +128,10 @@
     // For debugging purposes only.
     @Override
     public String toString() {
-        return mDeviceName + " (" + mDeviceAddress + ")";
+        String result = mDeviceName + " (" + mDeviceAddress + ")";
+        if (mDeviceAlias != null) {
+            result += ", alias " + mDeviceAlias;
+        }
+        return result;
     }
 }
diff --git a/core/java/android/hardware/display/WifiDisplayStatus.java b/core/java/android/hardware/display/WifiDisplayStatus.java
index d5fe45d..f7e72c4 100644
--- a/core/java/android/hardware/display/WifiDisplayStatus.java
+++ b/core/java/android/hardware/display/WifiDisplayStatus.java
@@ -23,7 +23,7 @@
 
 /**
  * Describes the current global state of Wifi display connectivity, including the
- * currently connected display and all known displays.
+ * currently connected display and all available or remembered displays.
  * <p>
  * This object is immutable.
  * </p>
@@ -31,22 +31,37 @@
  * @hide
  */
 public final class WifiDisplayStatus implements Parcelable {
-    private final boolean mEnabled;
+    private final int mFeatureState;
     private final int mScanState;
     private final int mActiveDisplayState;
     private final WifiDisplay mActiveDisplay;
-    private final WifiDisplay[] mKnownDisplays;
+    private final WifiDisplay[] mAvailableDisplays;
+    private final WifiDisplay[] mRememberedDisplays;
 
+    /** Feature state: Wifi display is not available on this device. */
+    public static final int FEATURE_STATE_UNAVAILABLE = 0;
+    /** Feature state: Wifi display is disabled, probably because Wifi is disabled. */
+    public static final int FEATURE_STATE_DISABLED = 1;
+    /** Feature state: Wifi display is turned off in settings. */
+    public static final int FEATURE_STATE_OFF = 2;
+    /** Feature state: Wifi display is turned on in settings. */
+    public static final int FEATURE_STATE_ON = 3;
+
+    /** Scan state: Not currently scanning. */
     public static final int SCAN_STATE_NOT_SCANNING = 0;
+    /** Scan state: Currently scanning. */
     public static final int SCAN_STATE_SCANNING = 1;
 
+    /** Display state: Not connected. */
     public static final int DISPLAY_STATE_NOT_CONNECTED = 0;
+    /** Display state: Connecting to active display. */
     public static final int DISPLAY_STATE_CONNECTING = 1;
+    /** Display state: Connected to active display. */
     public static final int DISPLAY_STATE_CONNECTED = 2;
 
     public static final Creator<WifiDisplayStatus> CREATOR = new Creator<WifiDisplayStatus>() {
         public WifiDisplayStatus createFromParcel(Parcel in) {
-            boolean enabled = (in.readInt() != 0);
+            int featureState = in.readInt();
             int scanState = in.readInt();
             int activeDisplayState= in.readInt();
 
@@ -55,13 +70,18 @@
                 activeDisplay = 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);
+            WifiDisplay[] availableDisplays = WifiDisplay.CREATOR.newArray(in.readInt());
+            for (int i = 0; i < availableDisplays.length; i++) {
+                availableDisplays[i] = WifiDisplay.CREATOR.createFromParcel(in);
             }
 
-            return new WifiDisplayStatus(enabled, scanState, activeDisplayState,
-                    activeDisplay, knownDisplays);
+            WifiDisplay[] rememberedDisplays = WifiDisplay.CREATOR.newArray(in.readInt());
+            for (int i = 0; i < rememberedDisplays.length; i++) {
+                rememberedDisplays[i] = WifiDisplay.CREATOR.createFromParcel(in);
+            }
+
+            return new WifiDisplayStatus(featureState, scanState, activeDisplayState,
+                    activeDisplay, availableDisplays, rememberedDisplays);
         }
 
         public WifiDisplayStatus[] newArray(int size) {
@@ -70,33 +90,38 @@
     };
 
     public WifiDisplayStatus() {
-        this(false, SCAN_STATE_NOT_SCANNING, DISPLAY_STATE_NOT_CONNECTED,
-                null, WifiDisplay.EMPTY_ARRAY);
+        this(FEATURE_STATE_UNAVAILABLE, SCAN_STATE_NOT_SCANNING, DISPLAY_STATE_NOT_CONNECTED,
+                null, WifiDisplay.EMPTY_ARRAY, WifiDisplay.EMPTY_ARRAY);
     }
 
-    public WifiDisplayStatus(boolean enabled, int scanState, int activeDisplayState,
-            WifiDisplay activeDisplay, WifiDisplay[] knownDisplays) {
-        if (knownDisplays == null) {
-            throw new IllegalArgumentException("knownDisplays must not be null");
+    public WifiDisplayStatus(int featureState, int scanState,
+            int activeDisplayState, WifiDisplay activeDisplay,
+            WifiDisplay[] availableDisplays, WifiDisplay[] rememberedDisplays) {
+        if (availableDisplays == null) {
+            throw new IllegalArgumentException("availableDisplays must not be null");
+        }
+        if (rememberedDisplays == null) {
+            throw new IllegalArgumentException("rememberedDisplays must not be null");
         }
 
-        mEnabled = enabled;
+        mFeatureState = featureState;
         mScanState = scanState;
         mActiveDisplayState = activeDisplayState;
         mActiveDisplay = activeDisplay;
-        mKnownDisplays = knownDisplays;
+        mAvailableDisplays = availableDisplays;
+        mRememberedDisplays = rememberedDisplays;
     }
 
     /**
-     * Returns true if the Wifi display feature is enabled and available for use.
+     * Returns the state of the Wifi display feature on this device.
      * <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.
+     * The value of this property reflects whether the device supports the Wifi display,
+     * whether it has been enabled by the user and whether the prerequisites for
+     * connecting to displays have been met.
      * </p>
      */
-    public boolean isEnabled() {
-        return mEnabled;
+    public int getFeatureState() {
+        return mFeatureState;
     }
 
     /**
@@ -127,15 +152,29 @@
     }
 
     /**
-     * Gets the list of all known Wifi displays, never null.
+     * Gets the list of all available Wifi displays as reported by the most recent
+     * scan, never null.
+     * <p>
+     * Some of these displays may already be remembered, others may be unknown.
+     * </p>
      */
-    public WifiDisplay[] getKnownDisplays() {
-        return mKnownDisplays;
+    public WifiDisplay[] getAvailableDisplays() {
+        return mAvailableDisplays;
+    }
+
+    /**
+     * Gets the list of all remembered Wifi displays, never null.
+     * <p>
+     * Not all remembered displays will necessarily be available.
+     * </p>
+     */
+    public WifiDisplay[] getRememberedDisplays() {
+        return mRememberedDisplays;
     }
 
     @Override
     public void writeToParcel(Parcel dest, int flags) {
-        dest.writeInt(mEnabled ? 1 : 0);
+        dest.writeInt(mFeatureState);
         dest.writeInt(mScanState);
         dest.writeInt(mActiveDisplayState);
 
@@ -146,8 +185,13 @@
             dest.writeInt(0);
         }
 
-        dest.writeInt(mKnownDisplays.length);
-        for (WifiDisplay display : mKnownDisplays) {
+        dest.writeInt(mAvailableDisplays.length);
+        for (WifiDisplay display : mAvailableDisplays) {
+            display.writeToParcel(dest, flags);
+        }
+
+        dest.writeInt(mRememberedDisplays.length);
+        for (WifiDisplay display : mRememberedDisplays) {
             display.writeToParcel(dest, flags);
         }
     }
@@ -160,11 +204,12 @@
     // For debugging purposes only.
     @Override
     public String toString() {
-        return "WifiDisplayStatus{enabled=" + mEnabled
+        return "WifiDisplayStatus{featureState=" + mFeatureState
                 + ", scanState=" + mScanState
                 + ", activeDisplayState=" + mActiveDisplayState
                 + ", activeDisplay=" + mActiveDisplay
-                + ", knownDisplays=" + Arrays.toString(mKnownDisplays)
+                + ", availableDisplays=" + Arrays.toString(mAvailableDisplays)
+                + ", rememberedDisplays=" + Arrays.toString(mRememberedDisplays)
                 + "}";
     }
 }
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 2cda5a8..bb118b2 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -214,6 +214,21 @@
             "android.settings.BLUETOOTH_SETTINGS";
 
     /**
+     * Activity Action: Show settings to allow configuration of Wifi Displays.
+     * <p>
+     * In some cases, a matching Activity may not exist, so ensure you
+     * safeguard against this.
+     * <p>
+     * Input: Nothing.
+     * <p>
+     * Output: Nothing.
+     * @hide
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_WIFI_DISPLAY_SETTINGS =
+            "android.settings.WIFI_DISPLAY_SETTINGS";
+
+    /**
      * Activity Action: Show settings to allow configuration of date and time.
      * <p>
      * In some cases, a matching Activity may not exist, so ensure you
@@ -5540,6 +5555,13 @@
            "web_autofill_query_url";
 
        /**
+        * Whether Wifi display is enabled/disabled
+        * 0=disabled. 1=enabled.
+        * @hide
+        */
+       public static final String WIFI_DISPLAY_ON = "wifi_display_on";
+
+       /**
         * Whether to notify the user of open networks.
         * <p>
         * If not connected and the scan results have an open network, we will
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 21e8d76..ab183a3 100755
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -946,4 +946,18 @@
     players. -->
     <integer name="config_safe_media_volume_index">10</integer>
 
+    <!-- Whether WiFi display is supported by this device.
+         There are many prerequisites for this feature to work correctly.
+         Here are a few of them:
+         * The WiFi radio must support WiFi P2P.
+         * The WiFi radio must support concurrent connections to the WiFi display and
+           to an access point.
+         * The Audio Flinger audio_policy.conf file must specify a rule for the "r_submix"
+           remote submix module.  This module is used to record and stream system
+           audio output to the WiFi display encoder in the media server.
+         * The remote submix module "audio.r_submix.default" must be installed on the device.
+         * The device must be provisioned with HDCP keys (for protected content).
+    -->
+    <bool name="config_enableWifiDisplay">false</bool>
+
 </resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 5612360..54e3b06 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -268,6 +268,7 @@
   <java-symbol type="bool" name="config_sendAudioBecomingNoisy" />
   <java-symbol type="bool" name="config_enableScreenshotChord" />
   <java-symbol type="bool" name="config_bluetooth_default_profiles" />
+  <java-symbol type="bool" name="config_enableWifiDisplay" />
 
   <java-symbol type="integer" name="config_cursorWindowSize" />
   <java-symbol type="integer" name="config_longPressOnPowerBehavior" />
diff --git a/packages/SettingsProvider/res/values/defaults.xml b/packages/SettingsProvider/res/values/defaults.xml
index 92261da..ab8e961b 100644
--- a/packages/SettingsProvider/res/values/defaults.xml
+++ b/packages/SettingsProvider/res/values/defaults.xml
@@ -34,6 +34,7 @@
     <bool name="def_haptic_feedback">true</bool>
 
     <bool name="def_bluetooth_on">false</bool>
+    <bool name="def_wifi_display_on">false</bool>
     <bool name="def_install_non_market_apps">false</bool>
     <bool name="def_package_verifier_enable">true</bool>
     <!-- Comma-separated list of location providers.
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
index 8a847e1..8275b25 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
@@ -1995,6 +1995,9 @@
             loadIntegerSetting(stmt, Settings.Global.WIFI_MAX_DHCP_RETRY_COUNT,
                     R.integer.def_max_dhcp_retries);
 
+            loadBooleanSetting(stmt, Settings.Global.WIFI_DISPLAY_ON,
+                    R.bool.def_wifi_display_on);
+
             // --- New global settings start here
         } finally {
             if (stmt != null) stmt.close();
diff --git a/packages/SystemUI/res/layout/wifi_display_dialog.xml b/packages/SystemUI/res/layout/wifi_display_dialog.xml
deleted file mode 100644
index a78096e..0000000
--- a/packages/SystemUI/res/layout/wifi_display_dialog.xml
+++ /dev/null
@@ -1,38 +0,0 @@
-<?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:orientation="vertical">
-
-    <ListView android:id="@+id/list"
-        android:layout_width="match_parent"
-        android:layout_height="0dp"
-        android:layout_weight="2" />
-
-    <Button android:id="@+id/scan"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:layout_weight="1"
-        android:text="@string/wifi_display_scan" />
-
-    <Button android:id="@+id/disconnect"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:layout_weight="1"
-        android:text="@string/wifi_display_disconnect" />
-</LinearLayout>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index cbd9957..4545706 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -445,22 +445,4 @@
     <string name="quick_settings_brightness_dialog_title">Brightness</string>
     <!-- QuickSettings: Brightness dialog auto brightness button [CHAR LIMIT=NONE] -->
     <string name="quick_settings_brightness_dialog_auto_brightness_label">AUTO</string>
-
-    <!-- Wifi display: Scan button text [CHAR LIMIT=15] -->
-    <string name="wifi_display_scan">Scan</string>
-
-    <!-- Wifi display: Disconnect button text [CHAR LIMIT=15] -->
-    <string name="wifi_display_disconnect">Disconnect</string>
-
-    <!-- Wifi display: Quick setting dialog title [CHAR LIMIT=30] -->
-    <string name="wifi_display_dialog_title">Wifi Display</string>
-
-    <!-- Wifi display: Subtitle text shown to indicate that a display is available [CHAR LIMIT=30] -->
-    <string name="wifi_display_state_available">Available</string>
-
-    <!-- Wifi display: Subtitle text shown to indicate that a display is connecting [CHAR LIMIT=30] -->
-    <string name="wifi_display_state_connecting">Connecting</string>
-
-    <!-- Wifi display: Subtitle text shown to indicate that a display is connected [CHAR LIMIT=30] -->
-    <string name="wifi_display_state_connected">Connected</string>
 </resources>
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettings.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettings.java
index 9fb6d7c..1b28045 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettings.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettings.java
@@ -81,8 +81,7 @@
 
     private DisplayManager mDisplayManager;
     private WifiDisplayStatus mWifiDisplayStatus;
-    private WifiDisplayListAdapter mWifiDisplayListAdapter;
-    
+
     private BrightnessController mBrightnessController;
     private BluetoothController mBluetoothController;
     private Dialog mBrightnessDialog;
@@ -111,7 +110,6 @@
         mContainerView = container;
         mModel = new QuickSettingsModel(context);
         mWifiDisplayStatus = new WifiDisplayStatus();
-        mWifiDisplayListAdapter = new WifiDisplayListAdapter(context);
 
         Resources r = mContext.getResources();
         mBatteryLevels = (LevelListDrawable) r.getDrawable(R.drawable.qs_sys_battery);
@@ -483,8 +481,7 @@
         wifiDisplayTile.setOnClickListener(new View.OnClickListener() {
             @Override
             public void onClick(View v) {
-                mBar.collapseAllPanels(true);
-                showWifiDisplayDialog();
+                startSettingsActivity(android.provider.Settings.ACTION_WIFI_DISPLAY_SETTINGS);
             }
         });
         mModel.addWifiDisplayTile(wifiDisplayTile, new QuickSettingsModel.RefreshCallback() {
@@ -578,71 +575,13 @@
         }
     }
 
-    // Wifi Display
-    private void showWifiDisplayDialog() {
-        mDisplayManager.scanWifiDisplays();
-        updateWifiDisplayStatus();
-
-        Dialog dialog = new Dialog(mContext);
-        dialog.setContentView(R.layout.wifi_display_dialog);
-        dialog.setCanceledOnTouchOutside(true);
-        dialog.setTitle(R.string.wifi_display_dialog_title);
-
-        Button scanButton = (Button)dialog.findViewById(R.id.scan);
-        scanButton.setOnClickListener(new View.OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                mDisplayManager.scanWifiDisplays();
-            }
-        });
-
-        Button disconnectButton = (Button)dialog.findViewById(R.id.disconnect);
-        disconnectButton.setOnClickListener(new View.OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                mDisplayManager.disconnectWifiDisplay();
-            }
-        });
-
-        ListView list = (ListView)dialog.findViewById(R.id.list);
-        list.setAdapter(mWifiDisplayListAdapter);
-        list.setOnItemClickListener(new AdapterView.OnItemClickListener() {
-            @Override
-            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
-                WifiDisplay display = mWifiDisplayListAdapter.getItem(position);
-                mDisplayManager.connectWifiDisplay(display.getDeviceAddress());
-            }
-        });
-
-        dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
-        dialog.show();
-    }
-
     private void updateWifiDisplayStatus() {
-        applyWifiDisplayStatus(mDisplayManager.getWifiDisplayStatus());
+        mWifiDisplayStatus = mDisplayManager.getWifiDisplayStatus();
+        applyWifiDisplayStatus();
     }
 
-    private void applyWifiDisplayStatus(WifiDisplayStatus status) {
-        mWifiDisplayStatus = status;
-
-        mWifiDisplayListAdapter.clear();
-        mWifiDisplayListAdapter.addAll(status.getKnownDisplays());
-        if (status.getActiveDisplay() != null
-                && !contains(status.getKnownDisplays(), status.getActiveDisplay())) {
-            mWifiDisplayListAdapter.add(status.getActiveDisplay());
-        }
-        mWifiDisplayListAdapter.sort(mWifiDisplayComparator);
-
-        mModel.onWifiDisplayStateChanged(status);
-    }
-
-    private static boolean contains(WifiDisplay[] displays, WifiDisplay display) {
-        for (WifiDisplay d : displays) {
-            if (d.equals(display)) {
-                return true;
-            }
-        }
-        return false;
+    private void applyWifiDisplayStatus() {
+        mModel.onWifiDisplayStateChanged(mWifiDisplayStatus);
     }
 
     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@@ -651,59 +590,9 @@
             if (intent.getAction().equals(DisplayManager.ACTION_WIFI_DISPLAY_STATUS_CHANGED)) {
                 WifiDisplayStatus status = (WifiDisplayStatus)intent.getParcelableExtra(
                         DisplayManager.EXTRA_WIFI_DISPLAY_STATUS);
-                applyWifiDisplayStatus(status);
+                mWifiDisplayStatus = status;
+                applyWifiDisplayStatus();
             }
         }
     };
-
-    private final class WifiDisplayListAdapter extends ArrayAdapter<WifiDisplay> {
-        private final LayoutInflater mInflater;
-
-        public WifiDisplayListAdapter(Context context) {
-            super(context, android.R.layout.simple_list_item_2);
-            mInflater = LayoutInflater.from(context);
-        }
-
-        @Override
-        public View getView(int position, View convertView, ViewGroup parent) {
-            WifiDisplay item = getItem(position);
-            View view = convertView;
-            if (view == null) {
-                view = mInflater.inflate(android.R.layout.simple_list_item_2,
-                        parent, false);
-            }
-            TextView headline = (TextView) view.findViewById(android.R.id.text1);
-            TextView subText = (TextView) view.findViewById(android.R.id.text2);
-            headline.setText(item.getDeviceName());
-
-            int state = WifiDisplayStatus.DISPLAY_STATE_NOT_CONNECTED;
-            if (item.equals(mWifiDisplayStatus.getActiveDisplay())) {
-                state = mWifiDisplayStatus.getActiveDisplayState();
-            }
-            switch (state) {
-                case WifiDisplayStatus.DISPLAY_STATE_CONNECTING:
-                    subText.setText(R.string.wifi_display_state_connecting);
-                    break;
-                case WifiDisplayStatus.DISPLAY_STATE_CONNECTED:
-                    subText.setText(R.string.wifi_display_state_connected);
-                    break;
-                case WifiDisplayStatus.DISPLAY_STATE_NOT_CONNECTED:
-                default:
-                    subText.setText(R.string.wifi_display_state_available);
-                    break;
-            }
-            return view;
-        }
-    }
-
-    private final Comparator<WifiDisplay> mWifiDisplayComparator = new Comparator<WifiDisplay>() {
-        @Override
-        public int compare(WifiDisplay lhs, WifiDisplay rhs) {
-            int c = lhs.getDeviceName().compareToIgnoreCase(rhs.getDeviceName());
-            if (c == 0) {
-                c = lhs.getDeviceAddress().compareToIgnoreCase(rhs.getDeviceAddress());
-            }
-            return c;
-        }
-    };
 }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettingsModel.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettingsModel.java
index 485b3e5..724df34 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettingsModel.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettingsModel.java
@@ -347,9 +347,10 @@
         mWifiDisplayCallback = cb;
     }
     public void onWifiDisplayStateChanged(WifiDisplayStatus status) {
-        mWifiDisplayState.enabled = status.isEnabled();
+        mWifiDisplayState.enabled =
+                (status.getFeatureState() != WifiDisplayStatus.FEATURE_STATE_UNAVAILABLE);
         if (status.getActiveDisplay() != null) {
-            mWifiDisplayState.label = status.getActiveDisplay().getDeviceName();
+            mWifiDisplayState.label = status.getActiveDisplay().getFriendlyDisplayName();
         } else {
             mWifiDisplayState.label = mContext.getString(
                     R.string.quick_settings_wifi_display_no_connection_label);
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 90783b7..1396d8b 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -189,7 +189,7 @@
 
                 // For debug builds, log event loop stalls to dropbox for analysis.
                 if (StrictMode.conditionallyEnableDebugLogging()) {
-                    Slog.i(TAG, "Enabled StrictMode logging for UI Looper");
+                    Slog.i(TAG, "Enabled StrictMode logging for WM Looper");
                 }
             }
         });
diff --git a/services/java/com/android/server/display/DisplayManagerService.java b/services/java/com/android/server/display/DisplayManagerService.java
index 39f2418..02fc6b1 100644
--- a/services/java/com/android/server/display/DisplayManagerService.java
+++ b/services/java/com/android/server/display/DisplayManagerService.java
@@ -148,6 +148,9 @@
     private final DisplayViewport mDefaultViewport = new DisplayViewport();
     private final DisplayViewport mExternalTouchViewport = new DisplayViewport();
 
+    // Persistent data store for all internal settings maintained by the display manager service.
+    private final PersistentDataStore mPersistentDataStore = new PersistentDataStore();
+
     // 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>();
@@ -403,6 +406,50 @@
     }
 
     @Override // Binder call
+    public void renameWifiDisplay(String address, String alias) {
+        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) {
+                if (mWifiDisplayAdapter != null) {
+                    mWifiDisplayAdapter.requestRenameLocked(address, alias);
+                }
+            }
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    @Override // Binder call
+    public void forgetWifiDisplay(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) {
+                if (mWifiDisplayAdapter != null) {
+                    mWifiDisplayAdapter.requestForgetLocked(address);
+                }
+            }
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    @Override // Binder call
     public WifiDisplayStatus getWifiDisplayStatus() {
         if (mContext.checkCallingPermission(android.Manifest.permission.CONFIGURE_WIFI_DISPLAY)
                 != PackageManager.PERMISSION_GRANTED) {
@@ -439,15 +486,27 @@
     private void registerAdditionalDisplayAdapters() {
         synchronized (mSyncRoot) {
             if (shouldRegisterNonEssentialDisplayAdaptersLocked()) {
-                registerDisplayAdapterLocked(new OverlayDisplayAdapter(
-                        mSyncRoot, mContext, mHandler, mDisplayAdapterListener, mUiHandler));
-                mWifiDisplayAdapter = new WifiDisplayAdapter(
-                        mSyncRoot, mContext, mHandler, mDisplayAdapterListener);
-                registerDisplayAdapterLocked(mWifiDisplayAdapter);
+                registerOverlayDisplayAdapterLocked();
+                registerWifiDisplayAdapterLocked();
             }
         }
     }
 
+    private void registerOverlayDisplayAdapterLocked() {
+        registerDisplayAdapterLocked(new OverlayDisplayAdapter(
+                mSyncRoot, mContext, mHandler, mDisplayAdapterListener, mUiHandler));
+    }
+
+    private void registerWifiDisplayAdapterLocked() {
+        if (mContext.getResources().getBoolean(
+                com.android.internal.R.bool.config_enableWifiDisplay)) {
+            mWifiDisplayAdapter = new WifiDisplayAdapter(
+                    mSyncRoot, mContext, mHandler, mDisplayAdapterListener,
+                    mPersistentDataStore);
+            registerDisplayAdapterLocked(mWifiDisplayAdapter);
+        }
+    }
+
     private boolean shouldRegisterNonEssentialDisplayAdaptersLocked() {
         // In safe mode, we disable non-essential display adapters to give the user
         // an opportunity to fix broken settings or other problems that might affect
diff --git a/services/java/com/android/server/display/PersistentDataStore.java b/services/java/com/android/server/display/PersistentDataStore.java
new file mode 100644
index 0000000..6e7717e
--- /dev/null
+++ b/services/java/com/android/server/display/PersistentDataStore.java
@@ -0,0 +1,288 @@
+/*
+ * 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 com.android.internal.util.FastXmlSerializer;
+import com.android.internal.util.XmlUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import android.hardware.display.WifiDisplay;
+import android.util.AtomicFile;
+import android.util.Slog;
+import android.util.Xml;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+
+import libcore.io.IoUtils;
+import libcore.util.Objects;
+
+/**
+ * Manages persistent state recorded by the display manager service as an XML file.
+ * Caller must acquire lock on the data store before accessing it.
+ *
+ * File format:
+ * <code>
+ * &lt;display-manager-state>
+ *   &lt;remembered-wifi-displays>
+ *     &lt;wifi-display deviceAddress="00:00:00:00:00:00" deviceName="XXXX" deviceAlias="YYYY" />
+ *   &gt;remembered-wifi-displays>
+ * &gt;/display-manager-state>
+ * </code>
+ *
+ * TODO: refactor this to extract common code shared with the input manager's data store
+ */
+final class PersistentDataStore {
+    static final String TAG = "DisplayManager";
+
+    // Remembered Wifi display devices.
+    private ArrayList<WifiDisplay> mRememberedWifiDisplays = new ArrayList<WifiDisplay>();
+
+    // The atomic file used to safely read or write the file.
+    private final AtomicFile mAtomicFile;
+
+    // True if the data has been loaded.
+    private boolean mLoaded;
+
+    // True if there are changes to be saved.
+    private boolean mDirty;
+
+    public PersistentDataStore() {
+        mAtomicFile = new AtomicFile(new File("/data/system/display-manager-state.xml"));
+    }
+
+    public void saveIfNeeded() {
+        if (mDirty) {
+            save();
+            mDirty = false;
+        }
+    }
+
+    public WifiDisplay[] getRememberedWifiDisplays() {
+        loadIfNeeded();
+        return mRememberedWifiDisplays.toArray(new WifiDisplay[mRememberedWifiDisplays.size()]);
+    }
+
+    public WifiDisplay applyWifiDisplayAlias(WifiDisplay display) {
+        if (display != null) {
+            loadIfNeeded();
+
+            int index = findRememberedWifiDisplay(display.getDeviceAddress());
+            if (index >= 0) {
+                return mRememberedWifiDisplays.get(index);
+            }
+        }
+        return display;
+    }
+
+    public WifiDisplay[] applyWifiDisplayAliases(WifiDisplay[] displays) {
+        WifiDisplay[] results = displays;
+        if (results != null) {
+            int count = displays.length;
+            for (int i = 0; i < count; i++) {
+                WifiDisplay result = applyWifiDisplayAlias(displays[i]);
+                if (result != displays[i]) {
+                    if (results == displays) {
+                        results = new WifiDisplay[count];
+                        System.arraycopy(displays, 0, results, 0, count);
+                    }
+                    results[i] = result;
+                }
+            }
+        }
+        return results;
+    }
+
+    public boolean rememberWifiDisplay(WifiDisplay display) {
+        loadIfNeeded();
+
+        int index = findRememberedWifiDisplay(display.getDeviceAddress());
+        if (index >= 0) {
+            WifiDisplay other = mRememberedWifiDisplays.get(index);
+            if (other.equals(display)) {
+                return false; // already remembered without change
+            }
+            mRememberedWifiDisplays.set(index, display);
+        } else {
+            mRememberedWifiDisplays.add(display);
+        }
+        setDirty();
+        return true;
+    }
+
+    public boolean renameWifiDisplay(String deviceAddress, String alias) {
+        int index = findRememberedWifiDisplay(deviceAddress);
+        if (index >= 0) {
+            WifiDisplay display = mRememberedWifiDisplays.get(index);
+            if (Objects.equal(display.getDeviceAlias(), alias)) {
+                return false; // already has this alias
+            }
+            WifiDisplay renamedDisplay = new WifiDisplay(deviceAddress,
+                    display.getDeviceName(), alias);
+            mRememberedWifiDisplays.set(index, renamedDisplay);
+            setDirty();
+            return true;
+        }
+        return false;
+    }
+
+    public boolean forgetWifiDisplay(String deviceAddress) {
+        int index = findRememberedWifiDisplay(deviceAddress);
+        if (index >= 0) {
+            mRememberedWifiDisplays.remove(index);
+            setDirty();
+            return true;
+        }
+        return false;
+    }
+
+    private int findRememberedWifiDisplay(String deviceAddress) {
+        int count = mRememberedWifiDisplays.size();
+        for (int i = 0; i < count; i++) {
+            if (mRememberedWifiDisplays.get(i).getDeviceAddress().equals(deviceAddress)) {
+                return i;
+            }
+        }
+        return -1;
+    }
+
+    private void loadIfNeeded() {
+        if (!mLoaded) {
+            load();
+            mLoaded = true;
+        }
+    }
+
+    private void setDirty() {
+        mDirty = true;
+    }
+
+    private void clearState() {
+        mRememberedWifiDisplays.clear();
+    }
+
+    private void load() {
+        clearState();
+
+        final InputStream is;
+        try {
+            is = mAtomicFile.openRead();
+        } catch (FileNotFoundException ex) {
+            return;
+        }
+
+        XmlPullParser parser;
+        try {
+            parser = Xml.newPullParser();
+            parser.setInput(new BufferedInputStream(is), null);
+            loadFromXml(parser);
+        } catch (IOException ex) {
+            Slog.w(TAG, "Failed to load display manager persistent store data.", ex);
+            clearState();
+        } catch (XmlPullParserException ex) {
+            Slog.w(TAG, "Failed to load display manager persistent store data.", ex);
+            clearState();
+        } finally {
+            IoUtils.closeQuietly(is);
+        }
+    }
+
+    private void save() {
+        final FileOutputStream os;
+        try {
+            os = mAtomicFile.startWrite();
+            boolean success = false;
+            try {
+                XmlSerializer serializer = new FastXmlSerializer();
+                serializer.setOutput(new BufferedOutputStream(os), "utf-8");
+                saveToXml(serializer);
+                serializer.flush();
+                success = true;
+            } finally {
+                if (success) {
+                    mAtomicFile.finishWrite(os);
+                } else {
+                    mAtomicFile.failWrite(os);
+                }
+            }
+        } catch (IOException ex) {
+            Slog.w(TAG, "Failed to save display manager persistent store data.", ex);
+        }
+    }
+
+    private void loadFromXml(XmlPullParser parser)
+            throws IOException, XmlPullParserException {
+        XmlUtils.beginDocument(parser, "display-manager-state");
+        final int outerDepth = parser.getDepth();
+        while (XmlUtils.nextElementWithin(parser, outerDepth)) {
+            if (parser.getName().equals("remembered-wifi-displays")) {
+                loadRememberedWifiDisplaysFromXml(parser);
+            }
+        }
+    }
+
+    private void loadRememberedWifiDisplaysFromXml(XmlPullParser parser)
+            throws IOException, XmlPullParserException {
+        final int outerDepth = parser.getDepth();
+        while (XmlUtils.nextElementWithin(parser, outerDepth)) {
+            if (parser.getName().equals("wifi-display")) {
+                String deviceAddress = parser.getAttributeValue(null, "deviceAddress");
+                String deviceName = parser.getAttributeValue(null, "deviceName");
+                String deviceAlias = parser.getAttributeValue(null, "deviceAlias");
+                if (deviceAddress == null || deviceName == null) {
+                    throw new XmlPullParserException(
+                            "Missing deviceAddress or deviceName attribute on wifi-display.");
+                }
+                if (findRememberedWifiDisplay(deviceAddress) >= 0) {
+                    throw new XmlPullParserException(
+                            "Found duplicate wifi display device address.");
+                }
+
+                mRememberedWifiDisplays.add(
+                        new WifiDisplay(deviceAddress, deviceName, deviceAlias));
+            }
+        }
+    }
+
+    private void saveToXml(XmlSerializer serializer) throws IOException {
+        serializer.startDocument(null, true);
+        serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
+        serializer.startTag(null, "display-manager-state");
+        serializer.startTag(null, "remembered-wifi-displays");
+        for (WifiDisplay display : mRememberedWifiDisplays) {
+            serializer.startTag(null, "wifi-display");
+            serializer.attribute(null, "deviceAddress", display.getDeviceAddress());
+            serializer.attribute(null, "deviceName", display.getDeviceName());
+            if (display.getDeviceAlias() != null) {
+                serializer.attribute(null, "deviceAlias", display.getDeviceAlias());
+            }
+            serializer.endTag(null, "wifi-display");
+        }
+        serializer.endTag(null, "remembered-wifi-displays");
+        serializer.endTag(null, "display-manager-state");
+        serializer.endDocument();
+    }
+}
\ No newline at end of file
diff --git a/services/java/com/android/server/display/WifiDisplayAdapter.java b/services/java/com/android/server/display/WifiDisplayAdapter.java
index b57d3dc..1d50ded 100644
--- a/services/java/com/android/server/display/WifiDisplayAdapter.java
+++ b/services/java/com/android/server/display/WifiDisplayAdapter.java
@@ -49,21 +49,26 @@
 final class WifiDisplayAdapter extends DisplayAdapter {
     private static final String TAG = "WifiDisplayAdapter";
 
+    private PersistentDataStore mPersistentDataStore;
+
     private WifiDisplayController mDisplayController;
     private WifiDisplayDevice mDisplayDevice;
 
     private WifiDisplayStatus mCurrentStatus;
-    private boolean mEnabled;
+    private int mFeatureState;
     private int mScanState;
     private int mActiveDisplayState;
     private WifiDisplay mActiveDisplay;
-    private WifiDisplay[] mKnownDisplays = WifiDisplay.EMPTY_ARRAY;
+    private WifiDisplay[] mAvailableDisplays = WifiDisplay.EMPTY_ARRAY;
+    private WifiDisplay[] mRememberedDisplays = WifiDisplay.EMPTY_ARRAY;
 
     private boolean mPendingStatusChangeBroadcast;
 
     public WifiDisplayAdapter(DisplayManagerService.SyncRoot syncRoot,
-            Context context, Handler handler, Listener listener) {
+            Context context, Handler handler, Listener listener,
+            PersistentDataStore persistentDataStore) {
         super(syncRoot, context, handler, listener, TAG);
+        mPersistentDataStore = persistentDataStore;
     }
 
     @Override
@@ -71,11 +76,12 @@
         super.dumpLocked(pw);
 
         pw.println("mCurrentStatus=" + getWifiDisplayStatusLocked());
-        pw.println("mEnabled=" + mEnabled);
+        pw.println("mFeatureState=" + mFeatureState);
         pw.println("mScanState=" + mScanState);
         pw.println("mActiveDisplayState=" + mActiveDisplayState);
         pw.println("mActiveDisplay=" + mActiveDisplay);
-        pw.println("mKnownDisplays=" + Arrays.toString(mKnownDisplays));
+        pw.println("mAvailableDisplays=" + Arrays.toString(mAvailableDisplays));
+        pw.println("mRememberedDisplays=" + Arrays.toString(mRememberedDisplays));
         pw.println("mPendingStatusChangeBroadcast=" + mPendingStatusChangeBroadcast);
 
         // Try to dump the controller state.
@@ -93,6 +99,8 @@
     public void registerLocked() {
         super.registerLocked();
 
+        updateRememberedDisplaysLocked();
+
         getHandler().post(new Runnable() {
             @Override
             public void run() {
@@ -135,18 +143,58 @@
         });
     }
 
+    public void requestRenameLocked(String address, String alias) {
+        if (alias != null) {
+            alias = alias.trim();
+            if (alias.isEmpty()) {
+                alias = null;
+            }
+        }
+
+        if (mPersistentDataStore.renameWifiDisplay(address, alias)) {
+            mPersistentDataStore.saveIfNeeded();
+            updateRememberedDisplaysLocked();
+            scheduleStatusChangedBroadcastLocked();
+        }
+    }
+
+    public void requestForgetLocked(String address) {
+        if (mPersistentDataStore.forgetWifiDisplay(address)) {
+            mPersistentDataStore.saveIfNeeded();
+            updateRememberedDisplaysLocked();
+            scheduleStatusChangedBroadcastLocked();
+        }
+
+        if (mActiveDisplay != null && mActiveDisplay.getDeviceAddress().equals(address)) {
+            requestDisconnectLocked();
+        }
+    }
+
     public WifiDisplayStatus getWifiDisplayStatusLocked() {
         if (mCurrentStatus == null) {
-            mCurrentStatus = new WifiDisplayStatus(mEnabled, mScanState, mActiveDisplayState,
-                    mActiveDisplay, mKnownDisplays);
+            mCurrentStatus = new WifiDisplayStatus(
+                    mFeatureState, mScanState, mActiveDisplayState,
+                    mActiveDisplay, mAvailableDisplays, mRememberedDisplays);
         }
         return mCurrentStatus;
     }
 
+    private void updateRememberedDisplaysLocked() {
+        mRememberedDisplays = mPersistentDataStore.getRememberedWifiDisplays();
+        mActiveDisplay = mPersistentDataStore.applyWifiDisplayAlias(mActiveDisplay);
+        mAvailableDisplays = mPersistentDataStore.applyWifiDisplayAliases(mAvailableDisplays);
+    }
+
     private void handleConnectLocked(WifiDisplay display,
             Surface surface, int width, int height, int flags) {
         handleDisconnectLocked();
 
+        if (mPersistentDataStore.rememberWifiDisplay(display)) {
+            mPersistentDataStore.saveIfNeeded();
+            updateRememberedDisplaysLocked();
+            scheduleStatusChangedBroadcastLocked();
+        }
+
         int deviceFlags = 0;
         if ((flags & RemoteDisplay.DISPLAY_FLAG_SECURE) != 0) {
             deviceFlags |= DisplayDeviceInfo.FLAG_SUPPORTS_SECURE_VIDEO_OUTPUT;
@@ -154,7 +202,7 @@
 
         float refreshRate = 60.0f; // TODO: get this for real
 
-        String name = display.getDeviceName();
+        String name = display.getFriendlyDisplayName();
         IBinder displayToken = Surface.createDisplay(name);
         mDisplayDevice = new WifiDisplayDevice(displayToken, name, width, height,
                 refreshRate, deviceFlags, surface);
@@ -170,6 +218,7 @@
     }
 
     private void scheduleStatusChangedBroadcastLocked() {
+        mCurrentStatus = null;
         if (!mPendingStatusChangeBroadcast) {
             mPendingStatusChangeBroadcast = true;
             getHandler().post(mStatusChangeBroadcast);
@@ -202,11 +251,10 @@
     private final WifiDisplayController.Listener mWifiDisplayListener =
             new WifiDisplayController.Listener() {
         @Override
-        public void onEnablementChanged(boolean enabled) {
+        public void onFeatureStateChanged(int featureState) {
             synchronized (getSyncRoot()) {
-                if (mEnabled != enabled) {
-                    mCurrentStatus = null;
-                    mEnabled = enabled;
+                if (mFeatureState != featureState) {
+                    mFeatureState = featureState;
                     scheduleStatusChangedBroadcastLocked();
                 }
             }
@@ -216,20 +264,21 @@
         public void onScanStarted() {
             synchronized (getSyncRoot()) {
                 if (mScanState != WifiDisplayStatus.SCAN_STATE_SCANNING) {
-                    mCurrentStatus = null;
                     mScanState = WifiDisplayStatus.SCAN_STATE_SCANNING;
                     scheduleStatusChangedBroadcastLocked();
                 }
             }
         }
 
-        public void onScanFinished(WifiDisplay[] knownDisplays) {
+        public void onScanFinished(WifiDisplay[] availableDisplays) {
             synchronized (getSyncRoot()) {
+                availableDisplays = mPersistentDataStore.applyWifiDisplayAliases(
+                        availableDisplays);
+
                 if (mScanState != WifiDisplayStatus.SCAN_STATE_NOT_SCANNING
-                        || !Arrays.equals(mKnownDisplays, knownDisplays)) {
-                    mCurrentStatus = null;
+                        || !Arrays.equals(mAvailableDisplays, availableDisplays)) {
                     mScanState = WifiDisplayStatus.SCAN_STATE_NOT_SCANNING;
-                    mKnownDisplays = knownDisplays;
+                    mAvailableDisplays = availableDisplays;
                     scheduleStatusChangedBroadcastLocked();
                 }
             }
@@ -238,10 +287,11 @@
         @Override
         public void onDisplayConnecting(WifiDisplay display) {
             synchronized (getSyncRoot()) {
+                display = mPersistentDataStore.applyWifiDisplayAlias(display);
+
                 if (mActiveDisplayState != WifiDisplayStatus.DISPLAY_STATE_CONNECTING
                         || mActiveDisplay == null
                         || !mActiveDisplay.equals(display)) {
-                    mCurrentStatus = null;
                     mActiveDisplayState = WifiDisplayStatus.DISPLAY_STATE_CONNECTING;
                     mActiveDisplay = display;
                     scheduleStatusChangedBroadcastLocked();
@@ -254,7 +304,6 @@
             synchronized (getSyncRoot()) {
                 if (mActiveDisplayState != WifiDisplayStatus.DISPLAY_STATE_NOT_CONNECTED
                         || mActiveDisplay != null) {
-                    mCurrentStatus = null;
                     mActiveDisplayState = WifiDisplayStatus.DISPLAY_STATE_NOT_CONNECTED;
                     mActiveDisplay = null;
                     scheduleStatusChangedBroadcastLocked();
@@ -266,12 +315,12 @@
         public void onDisplayConnected(WifiDisplay display, Surface surface,
                 int width, int height, int flags) {
             synchronized (getSyncRoot()) {
+                display = mPersistentDataStore.applyWifiDisplayAlias(display);
                 handleConnectLocked(display, surface, width, height, flags);
 
                 if (mActiveDisplayState != WifiDisplayStatus.DISPLAY_STATE_CONNECTED
                         || mActiveDisplay == null
                         || !mActiveDisplay.equals(display)) {
-                    mCurrentStatus = null;
                     mActiveDisplayState = WifiDisplayStatus.DISPLAY_STATE_CONNECTED;
                     mActiveDisplay = display;
                     scheduleStatusChangedBroadcastLocked();
@@ -287,7 +336,6 @@
 
                 if (mActiveDisplayState != WifiDisplayStatus.DISPLAY_STATE_NOT_CONNECTED
                         || mActiveDisplay != null) {
-                    mCurrentStatus = null;
                     mActiveDisplayState = WifiDisplayStatus.DISPLAY_STATE_NOT_CONNECTED;
                     mActiveDisplay = null;
                     scheduleStatusChangedBroadcastLocked();
diff --git a/services/java/com/android/server/display/WifiDisplayController.java b/services/java/com/android/server/display/WifiDisplayController.java
index 328f687..fd0fcc5 100644
--- a/services/java/com/android/server/display/WifiDisplayController.java
+++ b/services/java/com/android/server/display/WifiDisplayController.java
@@ -19,13 +19,17 @@
 import com.android.internal.util.DumpUtils;
 
 import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.database.ContentObserver;
 import android.hardware.display.WifiDisplay;
+import android.hardware.display.WifiDisplayStatus;
 import android.media.AudioManager;
 import android.media.RemoteDisplay;
 import android.net.NetworkInfo;
+import android.net.Uri;
 import android.net.wifi.p2p.WifiP2pConfig;
 import android.net.wifi.p2p.WifiP2pDevice;
 import android.net.wifi.p2p.WifiP2pDeviceList;
@@ -37,6 +41,7 @@
 import android.net.wifi.p2p.WifiP2pManager.GroupInfoListener;
 import android.net.wifi.p2p.WifiP2pManager.PeerListListener;
 import android.os.Handler;
+import android.provider.Settings;
 import android.util.Slog;
 import android.view.Surface;
 
@@ -94,9 +99,12 @@
     private boolean mWfdEnabling;
     private NetworkInfo mNetworkInfo;
 
-    private final ArrayList<WifiP2pDevice> mKnownWifiDisplayPeers =
+    private final ArrayList<WifiP2pDevice> mAvailableWifiDisplayPeers =
             new ArrayList<WifiP2pDevice>();
 
+    // True if Wifi display is enabled by the user.
+    private boolean mWifiDisplayOnSetting;
+
     // True if there is a call to discoverPeers in progress.
     private boolean mDiscoverPeersInProgress;
 
@@ -146,10 +154,31 @@
         intentFilter.addAction(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION);
         intentFilter.addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION);
         intentFilter.addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION);
-        context.registerReceiver(mWifiP2pReceiver, intentFilter);
+        context.registerReceiver(mWifiP2pReceiver, intentFilter, null, mHandler);
+
+        ContentObserver settingsObserver = new ContentObserver(mHandler) {
+            @Override
+            public void onChange(boolean selfChange, Uri uri) {
+                updateSettings();
+            }
+        };
+
+        final ContentResolver resolver = mContext.getContentResolver();
+        resolver.registerContentObserver(Settings.Global.getUriFor(
+                Settings.Global.WIFI_DISPLAY_ON), false, settingsObserver);
+        updateSettings();
+    }
+
+    private void updateSettings() {
+        final ContentResolver resolver = mContext.getContentResolver();
+        mWifiDisplayOnSetting = Settings.Global.getInt(resolver,
+                Settings.Global.WIFI_DISPLAY_ON, 0) != 0;
+
+        updateWfdEnableState();
     }
 
     public void dump(PrintWriter pw) {
+        pw.println("mWifiDisplayOnSetting=" + mWifiDisplayOnSetting);
         pw.println("mWifiP2pEnabled=" + mWifiP2pEnabled);
         pw.println("mWfdEnabled=" + mWfdEnabled);
         pw.println("mWfdEnabling=" + mWfdEnabling);
@@ -165,8 +194,8 @@
         pw.println("mRemoteDisplayConnected=" + mRemoteDisplayConnected);
         pw.println("mRemoteSubmixOn=" + mRemoteSubmixOn);
 
-        pw.println("mKnownWifiDisplayPeers: size=" + mKnownWifiDisplayPeers.size());
-        for (WifiP2pDevice device : mKnownWifiDisplayPeers) {
+        pw.println("mAvailableWifiDisplayPeers: size=" + mAvailableWifiDisplayPeers.size());
+        for (WifiP2pDevice device : mAvailableWifiDisplayPeers) {
             pw.println("  " + describeWifiP2pDevice(device));
         }
     }
@@ -176,7 +205,7 @@
     }
 
     public void requestConnect(String address) {
-        for (WifiP2pDevice device : mKnownWifiDisplayPeers) {
+        for (WifiP2pDevice device : mAvailableWifiDisplayPeers) {
             if (device.deviceAddress.equals(address)) {
                 connect(device);
             }
@@ -187,49 +216,65 @@
         disconnect();
     }
 
-    private void enableWfd() {
-        if (!mWfdEnabled && !mWfdEnabling) {
-            mWfdEnabling = true;
+    private void updateWfdEnableState() {
+        if (mWifiDisplayOnSetting && mWifiP2pEnabled) {
+            // WFD should be enabled.
+            if (!mWfdEnabled && !mWfdEnabling) {
+                mWfdEnabling = true;
 
-            WifiP2pWfdInfo wfdInfo = new WifiP2pWfdInfo();
-            wfdInfo.setWfdEnabled(true);
-            wfdInfo.setDeviceType(WifiP2pWfdInfo.WFD_SOURCE);
-            wfdInfo.setSessionAvailable(true);
-            wfdInfo.setControlPort(DEFAULT_CONTROL_PORT);
-            wfdInfo.setMaxThroughput(MAX_THROUGHPUT);
-            mWifiP2pManager.setWFDInfo(mWifiP2pChannel, wfdInfo, new ActionListener() {
-                @Override
-                public void onSuccess() {
-                    if (DEBUG) {
-                        Slog.d(TAG, "Successfully set WFD info.");
+                WifiP2pWfdInfo wfdInfo = new WifiP2pWfdInfo();
+                wfdInfo.setWfdEnabled(true);
+                wfdInfo.setDeviceType(WifiP2pWfdInfo.WFD_SOURCE);
+                wfdInfo.setSessionAvailable(true);
+                wfdInfo.setControlPort(DEFAULT_CONTROL_PORT);
+                wfdInfo.setMaxThroughput(MAX_THROUGHPUT);
+                mWifiP2pManager.setWFDInfo(mWifiP2pChannel, wfdInfo, new ActionListener() {
+                    @Override
+                    public void onSuccess() {
+                        if (DEBUG) {
+                            Slog.d(TAG, "Successfully set WFD info.");
+                        }
+                        if (mWfdEnabling) {
+                            mWfdEnabling = false;
+                            mWfdEnabled = true;
+                            reportFeatureState();
+                        }
                     }
-                    if (mWfdEnabling) {
+
+                    @Override
+                    public void onFailure(int reason) {
+                        if (DEBUG) {
+                            Slog.d(TAG, "Failed to set WFD info with reason " + reason + ".");
+                        }
                         mWfdEnabling = false;
-                        setWfdEnabled(true);
                     }
-                }
-
-                @Override
-                public void onFailure(int reason) {
-                    if (DEBUG) {
-                        Slog.d(TAG, "Failed to set WFD info with reason " + reason + ".");
-                    }
-                    mWfdEnabling = false;
-                }
-            });
+                });
+            }
+        } else {
+            // WFD should be disabled.
+            mWfdEnabling = false;
+            mWfdEnabled = false;
+            reportFeatureState();
+            disconnect();
         }
     }
 
-    private void setWfdEnabled(final boolean enabled) {
-        if (mWfdEnabled != enabled) {
-            mWfdEnabled = enabled;
-            mHandler.post(new Runnable() {
-                @Override
-                public void run() {
-                    mListener.onEnablementChanged(enabled);
-                }
-            });
+    private void reportFeatureState() {
+        final int featureState = computeFeatureState();
+        mHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                mListener.onFeatureStateChanged(featureState);
+            }
+        });
+    }
+
+    private int computeFeatureState() {
+        if (!mWifiP2pEnabled) {
+            return WifiDisplayStatus.FEATURE_STATE_DISABLED;
         }
+        return mWifiDisplayOnSetting ? WifiDisplayStatus.FEATURE_STATE_ON :
+                WifiDisplayStatus.FEATURE_STATE_OFF;
     }
 
     private void discoverPeers() {
@@ -296,14 +341,14 @@
                     Slog.d(TAG, "Received list of peers.");
                 }
 
-                mKnownWifiDisplayPeers.clear();
+                mAvailableWifiDisplayPeers.clear();
                 for (WifiP2pDevice device : peers.getDeviceList()) {
                     if (DEBUG) {
                         Slog.d(TAG, "  " + describeWifiP2pDevice(device));
                     }
 
                     if (isWifiDisplay(device)) {
-                        mKnownWifiDisplayPeers.add(device);
+                        mAvailableWifiDisplayPeers.add(device);
                     }
                 }
 
@@ -322,10 +367,10 @@
     }
 
     private void handleScanFinished() {
-        final int count = mKnownWifiDisplayPeers.size();
+        final int count = mAvailableWifiDisplayPeers.size();
         final WifiDisplay[] displays = WifiDisplay.CREATOR.newArray(count);
         for (int i = 0; i < count; i++) {
-            displays[i] = createWifiDisplay(mKnownWifiDisplayPeers.get(i));
+            displays[i] = createWifiDisplay(mAvailableWifiDisplayPeers.get(i));
         }
 
         mHandler.post(new Runnable() {
@@ -368,18 +413,11 @@
     }
 
     private void retryConnection() {
-        if (mDesiredDevice != null && mConnectedDevice != 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();
-        }
+        // 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();
     }
 
     /**
@@ -513,6 +551,13 @@
                     if (mConnectingDevice == newDevice) {
                         Slog.i(TAG, "Failed to initiate connection to Wifi display: "
                                 + newDevice.deviceName + ", reason=" + reason);
+                        mHandler.post(new Runnable() {
+                            @Override
+                            public void run() {
+                                mListener.onDisplayDisconnected();
+                            }
+                        });
+
                         mConnectingDevice = null;
                         handleConnectionFailure(false);
                     }
@@ -595,26 +640,13 @@
     }
 
     private void handleStateChanged(boolean enabled) {
-        if (mWifiP2pEnabled != enabled) {
-            mWifiP2pEnabled = enabled;
-            if (enabled) {
-                if (!mWfdEnabled) {
-                    enableWfd();
-                }
-            } else {
-                setWfdEnabled(false);
-                disconnect();
-            }
-        }
+        mWifiP2pEnabled = enabled;
+        updateWfdEnableState();
     }
 
     private void handlePeersChanged() {
-        if (mWifiP2pEnabled) {
-            if (mWfdEnabled) {
-                requestPeers();
-            } else {
-                enableWfd();
-            }
+        if (mWfdEnabled) {
+            requestPeers();
         }
     }
 
@@ -632,7 +664,8 @@
                         if (mConnectingDevice != null && !info.contains(mConnectingDevice)) {
                             Slog.i(TAG, "Aborting connection to Wifi display because "
                                     + "the current P2P group does not contain the device "
-                                    + "we expected to find: " + mConnectingDevice.deviceName);
+                                    + "we expected to find: " + mConnectingDevice.deviceName
+                                    + ", group info was: " + describeWifiP2pGroup(info));
                             handleConnectionFailure(false);
                             return;
                         }
@@ -704,10 +737,16 @@
 
         if (mDesiredDevice != null) {
             if (mConnectionRetriesLeft > 0) {
+                final WifiP2pDevice oldDevice = mDesiredDevice;
                 mHandler.postDelayed(new Runnable() {
                     @Override
                     public void run() {
-                        retryConnection();
+                        if (mDesiredDevice == oldDevice && mConnectionRetriesLeft > 0) {
+                            mConnectionRetriesLeft -= 1;
+                            Slog.i(TAG, "Retrying Wifi display connection.  Retries left: "
+                                    + mConnectionRetriesLeft);
+                            retryConnection();
+                        }
                     }
                 }, timeoutOccurred ? 0 : CONNECT_RETRY_DELAY_MILLIS);
             } else {
@@ -768,7 +807,7 @@
     }
 
     private static WifiDisplay createWifiDisplay(WifiP2pDevice device) {
-        return new WifiDisplay(device.deviceAddress, device.deviceName);
+        return new WifiDisplay(device.deviceAddress, device.deviceName, null);
     }
 
     private final BroadcastReceiver mWifiP2pReceiver = new BroadcastReceiver() {
@@ -776,6 +815,8 @@
         public void onReceive(Context context, Intent intent) {
             final String action = intent.getAction();
             if (action.equals(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION)) {
+                // This broadcast is sticky so we'll always get the initial Wifi P2P state
+                // on startup.
                 boolean enabled = (intent.getIntExtra(WifiP2pManager.EXTRA_WIFI_STATE,
                         WifiP2pManager.WIFI_P2P_STATE_DISABLED)) ==
                         WifiP2pManager.WIFI_P2P_STATE_ENABLED;
@@ -808,10 +849,10 @@
      * Called on the handler thread when displays are connected or disconnected.
      */
     public interface Listener {
-        void onEnablementChanged(boolean enabled);
+        void onFeatureStateChanged(int featureState);
 
         void onScanStarted();
-        void onScanFinished(WifiDisplay[] knownDisplays);
+        void onScanFinished(WifiDisplay[] availableDisplays);
 
         void onDisplayConnecting(WifiDisplay display);
         void onDisplayConnectionFailed();