blob: ee82050d5e8be3be54af6336a39a77d958b58315 [file] [log] [blame]
/*
* 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;
}
}
}
}