429 lines
15 KiB
Java
429 lines
15 KiB
Java
/*
|
|
* 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.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;
|
|
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.
|
|
* <p>
|
|
* The Wifi display protocol relies on Wifi direct for discovering and pairing
|
|
* with the display. Once connected, the Media Server opens an RTSP socket and accepts
|
|
* a connection from the display. After session negotiation, the Media Server
|
|
* streams encoded buffers to the display.
|
|
* </p><p>
|
|
* This class is responsible for connecting to Wifi displays and mediating
|
|
* the interactions between Media Server, Surface Flinger and the Display Manager Service.
|
|
* </p><p>
|
|
* Display adapters are guarded by the {@link DisplayManagerService.SyncRoot} lock.
|
|
* </p>
|
|
*/
|
|
final class WifiDisplayAdapter extends DisplayAdapter {
|
|
private static final String TAG = "WifiDisplayAdapter";
|
|
|
|
private WifiDisplayHandle mDisplayHandle;
|
|
private WifiDisplayController mDisplayController;
|
|
|
|
private WifiDisplayStatus mCurrentStatus;
|
|
private boolean mEnabled;
|
|
private int mScanState;
|
|
private int mActiveDisplayState;
|
|
private WifiDisplay mActiveDisplay;
|
|
private WifiDisplay[] mKnownDisplays = WifiDisplay.EMPTY_ARRAY;
|
|
|
|
private boolean mPendingStatusChangeBroadcast;
|
|
|
|
public WifiDisplayAdapter(DisplayManagerService.SyncRoot syncRoot,
|
|
Context context, Handler handler, Listener listener) {
|
|
super(syncRoot, context, handler, listener, TAG);
|
|
}
|
|
|
|
@Override
|
|
public void dumpLocked(PrintWriter pw) {
|
|
super.dumpLocked(pw);
|
|
|
|
if (mDisplayHandle == null) {
|
|
pw.println("mDisplayHandle=null");
|
|
} else {
|
|
pw.println("mDisplayHandle:");
|
|
mDisplayHandle.dumpLocked(pw);
|
|
}
|
|
|
|
pw.println("mCurrentStatus=" + getWifiDisplayStatusLocked());
|
|
pw.println("mEnabled=" + mEnabled);
|
|
pw.println("mScanState=" + mScanState);
|
|
pw.println("mActiveDisplayState=" + mActiveDisplayState);
|
|
pw.println("mActiveDisplay=" + mActiveDisplay);
|
|
pw.println("mKnownDisplays=" + Arrays.toString(mKnownDisplays));
|
|
pw.println("mPendingStatusChangeBroadcast=" + mPendingStatusChangeBroadcast);
|
|
|
|
// Try to dump the controller state.
|
|
if (mDisplayController == null) {
|
|
pw.println("mDisplayController=null");
|
|
} else {
|
|
pw.println("mDisplayController:");
|
|
final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ");
|
|
ipw.increaseIndent();
|
|
DumpUtils.dumpAsync(getHandler(), mDisplayController, ipw, 200);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void registerLocked() {
|
|
super.registerLocked();
|
|
|
|
getHandler().post(new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
mDisplayController = new WifiDisplayController(
|
|
getContext(), getHandler(), mWifiDisplayListener);
|
|
}
|
|
});
|
|
}
|
|
|
|
public void requestScanLocked() {
|
|
getHandler().post(new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
if (mDisplayController != null) {
|
|
mDisplayController.requestScan();
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
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, mScanState, mActiveDisplayState,
|
|
mActiveDisplay, mKnownDisplays);
|
|
}
|
|
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 onEnablementChanged(boolean enabled) {
|
|
synchronized (getSyncRoot()) {
|
|
if (mEnabled != enabled) {
|
|
mCurrentStatus = null;
|
|
mEnabled = enabled;
|
|
scheduleStatusChangedBroadcastLocked();
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onScanStarted() {
|
|
synchronized (getSyncRoot()) {
|
|
if (mScanState != WifiDisplayStatus.SCAN_STATE_SCANNING) {
|
|
mCurrentStatus = null;
|
|
mScanState = WifiDisplayStatus.SCAN_STATE_SCANNING;
|
|
scheduleStatusChangedBroadcastLocked();
|
|
}
|
|
}
|
|
}
|
|
|
|
public void onScanFinished(WifiDisplay[] knownDisplays) {
|
|
synchronized (getSyncRoot()) {
|
|
if (mScanState != WifiDisplayStatus.SCAN_STATE_NOT_SCANNING
|
|
|| !Arrays.equals(mKnownDisplays, knownDisplays)) {
|
|
mCurrentStatus = null;
|
|
mScanState = WifiDisplayStatus.SCAN_STATE_NOT_SCANNING;
|
|
mKnownDisplays = knownDisplays;
|
|
scheduleStatusChangedBroadcastLocked();
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onDisplayConnecting(WifiDisplay display) {
|
|
synchronized (getSyncRoot()) {
|
|
if (mActiveDisplayState != WifiDisplayStatus.DISPLAY_STATE_CONNECTING
|
|
|| mActiveDisplay == null
|
|
|| !mActiveDisplay.equals(display)) {
|
|
mCurrentStatus = null;
|
|
mActiveDisplayState = WifiDisplayStatus.DISPLAY_STATE_CONNECTING;
|
|
mActiveDisplay = display;
|
|
scheduleStatusChangedBroadcastLocked();
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onDisplayConnectionFailed() {
|
|
synchronized (getSyncRoot()) {
|
|
if (mActiveDisplayState != WifiDisplayStatus.DISPLAY_STATE_NOT_CONNECTED
|
|
|| mActiveDisplay != null) {
|
|
mCurrentStatus = null;
|
|
mActiveDisplayState = WifiDisplayStatus.DISPLAY_STATE_NOT_CONNECTED;
|
|
mActiveDisplay = null;
|
|
scheduleStatusChangedBroadcastLocked();
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onDisplayConnected(WifiDisplay display, String iface) {
|
|
synchronized (getSyncRoot()) {
|
|
handleConnectLocked(display, iface);
|
|
|
|
if (mActiveDisplayState != WifiDisplayStatus.DISPLAY_STATE_CONNECTED
|
|
|| mActiveDisplay == null
|
|
|| !mActiveDisplay.equals(display)) {
|
|
mCurrentStatus = null;
|
|
mActiveDisplayState = WifiDisplayStatus.DISPLAY_STATE_CONNECTED;
|
|
mActiveDisplay = display;
|
|
scheduleStatusChangedBroadcastLocked();
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onDisplayDisconnected() {
|
|
// Stop listening.
|
|
synchronized (getSyncRoot()) {
|
|
handleDisconnectLocked();
|
|
|
|
if (mActiveDisplayState != WifiDisplayStatus.DISPLAY_STATE_NOT_CONNECTED
|
|
|| mActiveDisplay != null) {
|
|
mCurrentStatus = null;
|
|
mActiveDisplayState = WifiDisplayStatus.DISPLAY_STATE_NOT_CONNECTED;
|
|
mActiveDisplay = null;
|
|
scheduleStatusChangedBroadcastLocked();
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
private final class WifiDisplayDevice extends DisplayDevice {
|
|
private final String mName;
|
|
private final int mWidth;
|
|
private final int mHeight;
|
|
private final float mRefreshRate;
|
|
private final int mFlags;
|
|
|
|
private Surface mSurface;
|
|
private DisplayDeviceInfo mInfo;
|
|
|
|
public WifiDisplayDevice(IBinder displayToken, String name,
|
|
int width, int height, float refreshRate, int flags,
|
|
Surface surface) {
|
|
super(WifiDisplayAdapter.this, displayToken);
|
|
mName = name;
|
|
mWidth = width;
|
|
mHeight = height;
|
|
mRefreshRate = refreshRate;
|
|
mFlags = flags;
|
|
mSurface = surface;
|
|
}
|
|
|
|
public void clearSurfaceLocked() {
|
|
mSurface = null;
|
|
sendTraversalRequestLocked();
|
|
}
|
|
|
|
@Override
|
|
public void performTraversalInTransactionLocked() {
|
|
setSurfaceInTransactionLocked(mSurface);
|
|
}
|
|
|
|
@Override
|
|
public DisplayDeviceInfo getDisplayDeviceInfoLocked() {
|
|
if (mInfo == null) {
|
|
mInfo = new DisplayDeviceInfo();
|
|
mInfo.name = mName;
|
|
mInfo.width = mWidth;
|
|
mInfo.height = mHeight;
|
|
mInfo.refreshRate = mRefreshRate;
|
|
mInfo.flags = mFlags;
|
|
mInfo.touch = DisplayDeviceInfo.TOUCH_EXTERNAL;
|
|
mInfo.setAssumedDensityForExternalDisplay(mWidth, mHeight);
|
|
}
|
|
return mInfo;
|
|
}
|
|
}
|
|
|
|
private final class WifiDisplayHandle implements RemoteDisplay.Listener {
|
|
private final String mName;
|
|
private final String mIface;
|
|
private final RemoteDisplay mRemoteDisplay;
|
|
|
|
private WifiDisplayDevice mDevice;
|
|
private int mLastError;
|
|
|
|
public WifiDisplayHandle(String name, String iface) {
|
|
mName = name;
|
|
mIface = iface;
|
|
mRemoteDisplay = RemoteDisplay.listen(iface, this, getHandler());
|
|
|
|
Slog.i(TAG, "Listening for Wifi display connections on " + iface
|
|
+ " from " + mName);
|
|
}
|
|
|
|
public void disposeLocked() {
|
|
Slog.i(TAG, "Stopped listening for Wifi display connections on " + mIface
|
|
+ " from " + mName);
|
|
|
|
removeDisplayLocked();
|
|
mRemoteDisplay.dispose();
|
|
}
|
|
|
|
public void dumpLocked(PrintWriter pw) {
|
|
pw.println(" " + mName + ": " + (mDevice != null ? "connected" : "disconnected"));
|
|
pw.println(" mIface=" + mIface);
|
|
pw.println(" mLastError=" + mLastError);
|
|
}
|
|
|
|
// Called on the handler thread.
|
|
@Override
|
|
public void onDisplayConnected(Surface surface, int width, int height, int flags) {
|
|
synchronized (getSyncRoot()) {
|
|
mLastError = 0;
|
|
removeDisplayLocked();
|
|
addDisplayLocked(surface, width, height, flags);
|
|
|
|
Slog.i(TAG, "Wifi display connected: " + mName);
|
|
}
|
|
}
|
|
|
|
// Called on the handler thread.
|
|
@Override
|
|
public void onDisplayDisconnected() {
|
|
synchronized (getSyncRoot()) {
|
|
mLastError = 0;
|
|
removeDisplayLocked();
|
|
|
|
Slog.i(TAG, "Wifi display disconnected: " + mName);
|
|
}
|
|
}
|
|
|
|
// Called on the handler thread.
|
|
@Override
|
|
public void onDisplayError(int error) {
|
|
synchronized (getSyncRoot()) {
|
|
mLastError = error;
|
|
removeDisplayLocked();
|
|
|
|
Slog.i(TAG, "Wifi display disconnected due to error " + error + ": " + mName);
|
|
}
|
|
}
|
|
|
|
private void addDisplayLocked(Surface surface, int width, int height, int flags) {
|
|
int deviceFlags = 0;
|
|
if ((flags & RemoteDisplay.DISPLAY_FLAG_SECURE) != 0) {
|
|
deviceFlags |= DisplayDeviceInfo.FLAG_SECURE;
|
|
}
|
|
|
|
float refreshRate = 60.0f; // TODO: get this for real
|
|
|
|
IBinder displayToken = Surface.createDisplay(mName);
|
|
mDevice = new WifiDisplayDevice(displayToken, mName, width, height,
|
|
refreshRate, deviceFlags, surface);
|
|
sendDisplayDeviceEventLocked(mDevice, DISPLAY_DEVICE_EVENT_ADDED);
|
|
}
|
|
|
|
private void removeDisplayLocked() {
|
|
if (mDevice != null) {
|
|
mDevice.clearSurfaceLocked();
|
|
sendDisplayDeviceEventLocked(mDevice, DISPLAY_DEVICE_EVENT_REMOVED);
|
|
mDevice = null;
|
|
}
|
|
}
|
|
}
|
|
}
|