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

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

* This class is responsible for connecting to Wifi displays and mediating * the interactions between Media Server, Surface Flinger and the Display Manager Service. *

* Display adapters are guarded by the {@link DisplayManagerService.SyncRoot} lock. *

*/ 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; } } } }