Add new wifi display discovery API.

The API is quite simple.  There are a few extra functions
on DisplayManager to scan, connect and disconnect from
wifi displays and get status, and a single protected
broadcast sent when the status changes.

Change-Id: Ic91dbab5ee818e790b27fa32e1a1e93788793be0
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/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index d0427f0..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" />
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 3e27abc..4db8cd1 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 f65d5b0..f989e4e 100755
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -3688,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/services/java/com/android/server/display/DisplayManagerService.java b/services/java/com/android/server/display/DisplayManagerService.java
index dc85d3f..41a0c09 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;
@@ -137,6 +138,9 @@
     // to the surface flinger state.
     private boolean mPendingTraversal;
 
+    // The Wifi display adapter, or null if not registered.
+    private WifiDisplayAdapter mWifiDisplayAdapter;
+
     // 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>();
@@ -315,6 +319,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 +408,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);
             }
         }
     }
diff --git a/services/java/com/android/server/display/WifiDisplayAdapter.java b/services/java/com/android/server/display/WifiDisplayAdapter.java
index 38007af..abf0d27 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();
+                }
             }
         }
     };
diff --git a/services/java/com/android/server/display/WifiDisplayController.java b/services/java/com/android/server/display/WifiDisplayController.java
index 0e857da..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;
@@ -143,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;
@@ -160,8 +177,8 @@
                         Slog.d(TAG, "Successfully set WFD info.");
                     }
                     if (mWfdEnabling) {
-                        mWfdEnabled = true;
                         mWfdEnabling = false;
+                        setWfdEnabled(true);
                         discoverPeers();
                     }
                 }
@@ -177,10 +194,23 @@
         }
     }
 
+    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();
         }
     }
@@ -217,12 +247,14 @@
                                         }
                                         tryDiscoverPeers();
                                     } else {
+                                        handleScanFinished();
                                         mDiscoverPeersInProgress = false;
                                     }
                                 }
                             }
                         }, DISCOVER_PEERS_RETRY_DELAY_MILLIS);
                     } else {
+                        handleScanFinished();
                         mDiscoverPeersInProgress = false;
                     }
                 }
@@ -249,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);
             }
         });
     }
@@ -403,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
@@ -440,14 +495,14 @@
 
             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);
                 }
             });
         }
@@ -463,7 +518,7 @@
                     enableWfd();
                 }
             } else {
-                mWfdEnabled = false;
+                setWfdEnabled(false);
                 disconnect();
             }
         }
@@ -537,6 +592,13 @@
         if (mDesiredDevice != null) {
             Slog.i(TAG, "Wifi display connection failed!");
 
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    mListener.onDisplayConnectionFailed();
+                }
+            });
+
             if (mConnectionRetriesLeft > 0) {
                 mHandler.postDelayed(new Runnable() {
                     @Override
@@ -575,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) {
@@ -591,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) {
@@ -628,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();
     }
 }