diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java index 2814301e49a8e..4347e75fff653 100644 --- a/core/java/android/hardware/display/DisplayManager.java +++ b/core/java/android/hardware/display/DisplayManager.java @@ -40,6 +40,28 @@ public final class DisplayManager { private final Object mLock = new Object(); private final SparseArray mDisplays = new SparseArray(); + /** + * Broadcast receiver that indicates when the Wifi display status changes. + *

+ * The status is provided as a {@link WifiDisplayStatus} object in the + * {@link #EXTRA_WIFI_DISPLAY_STATUS} extra. + *

+ * 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. + *

+ * @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; @@ -126,6 +148,47 @@ public final class DisplayManager { mGlobal.unregisterDisplayListener(listener); } + /** + * 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. */ diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java index 4077964ab9489..14b54407e5f5a 100644 --- a/core/java/android/hardware/display/DisplayManagerGlobal.java +++ b/core/java/android/hardware/display/DisplayManagerGlobal.java @@ -253,6 +253,43 @@ public final class DisplayManagerGlobal { } } + 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 d802aa16d6a5b..36a9a7f5fa57b 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 @@ interface IDisplayManager { 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 0000000000000..773307515bda6 --- /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 0000000000000..e51e97e54452e --- /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. + *

+ * This object is immutable. + *

+ * + * @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 CREATOR = new Creator() { + 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 0000000000000..35c633ee4866a --- /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 0000000000000..542d1b3a94b4d --- /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. + *

+ * This object is immutable. + *

+ * + * @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 CREATOR = new Creator() { + 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. + *

+ * 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. + *

+ */ + 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 d0427f07ce511..560021d37e005 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -108,6 +108,8 @@ + + diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index 9a8e71279f0cc..139715ccb6439 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -486,7 +486,6 @@ - diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index f65d5b0f09bae..f989e4edde2cf 100755 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -3688,9 +3688,6 @@ %1$s: %2$dx%3$d, %4$d dpi - - Wifi display: %1$s - Emergency call diff --git a/services/java/com/android/server/display/DisplayManagerService.java b/services/java/com/android/server/display/DisplayManagerService.java index dc85d3f1d6fca..41a0c09d8bb1a 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.content.pm.PackageManager; 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 @@ public final class DisplayManagerService extends IDisplayManager.Stub { // 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 mTempCallbacks = new ArrayList(); @@ -315,6 +319,77 @@ public final class DisplayManagerService extends IDisplayManager.Stub { } } + @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 @@ public final class DisplayManagerService extends IDisplayManager.Stub { 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 38007af06ec6e..abf0d27d223e5 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.DumpUtils; 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.util.Slog; 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 @@ final class WifiDisplayAdapter extends DisplayAdapter { 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 @@ final class WifiDisplayAdapter extends DisplayAdapter { 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 @@ final class WifiDisplayAdapter extends DisplayAdapter { }); } - 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 @@ final class WifiDisplayAdapter extends DisplayAdapter { 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 0e857da4cb447..131502fea0db4 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.BroadcastReceiver; 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 @@ final class WifiDisplayController implements DumpUtils.Dump { } } + 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 @@ final class WifiDisplayController implements DumpUtils.Dump { Slog.d(TAG, "Successfully set WFD info."); } if (mWfdEnabling) { - mWfdEnabled = true; mWfdEnabling = false; + setWfdEnabled(true); discoverPeers(); } } @@ -177,10 +194,23 @@ final class WifiDisplayController implements DumpUtils.Dump { } } + 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 @@ final class WifiDisplayController implements DumpUtils.Dump { } tryDiscoverPeers(); } else { + handleScanFinished(); mDiscoverPeersInProgress = false; } } } }, DISCOVER_PEERS_RETRY_DELAY_MILLIS); } else { + handleScanFinished(); mDiscoverPeersInProgress = false; } } @@ -249,16 +281,31 @@ final class WifiDisplayController implements DumpUtils.Dump { } } - // 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 @@ final class WifiDisplayController implements DumpUtils.Dump { 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 @@ final class WifiDisplayController implements DumpUtils.Dump { 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 @@ final class WifiDisplayController implements DumpUtils.Dump { enableWfd(); } } else { - mWfdEnabled = false; + setWfdEnabled(false); disconnect(); } } @@ -537,6 +592,13 @@ final class WifiDisplayController implements DumpUtils.Dump { 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 @@ final class WifiDisplayController implements DumpUtils.Dump { 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 @@ final class WifiDisplayController implements DumpUtils.Dump { 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 @@ final class WifiDisplayController implements DumpUtils.Dump { * 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(); } }