Home | History | Annotate | Download | only in display
      1 /*
      2  * Copyright (C) 2012 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.server.display;
     18 
     19 import com.android.internal.util.DumpUtils;
     20 import com.android.internal.util.IndentingPrintWriter;
     21 
     22 import android.content.BroadcastReceiver;
     23 import android.content.Context;
     24 import android.content.Intent;
     25 import android.content.IntentFilter;
     26 import android.hardware.display.DisplayManager;
     27 import android.hardware.display.WifiDisplay;
     28 import android.hardware.display.WifiDisplaySessionInfo;
     29 import android.hardware.display.WifiDisplayStatus;
     30 import android.media.RemoteDisplay;
     31 import android.os.Handler;
     32 import android.os.IBinder;
     33 import android.os.Looper;
     34 import android.os.Message;
     35 import android.os.UserHandle;
     36 import android.util.Slog;
     37 import android.view.Display;
     38 import android.view.Surface;
     39 import android.view.SurfaceControl;
     40 
     41 import java.io.PrintWriter;
     42 import java.util.Arrays;
     43 import java.util.List;
     44 import java.util.ArrayList;
     45 
     46 import libcore.util.Objects;
     47 
     48 /**
     49  * Connects to Wifi displays that implement the Miracast protocol.
     50  * <p>
     51  * The Wifi display protocol relies on Wifi direct for discovering and pairing
     52  * with the display.  Once connected, the Media Server opens an RTSP socket and accepts
     53  * a connection from the display.  After session negotiation, the Media Server
     54  * streams encoded buffers to the display.
     55  * </p><p>
     56  * This class is responsible for connecting to Wifi displays and mediating
     57  * the interactions between Media Server, Surface Flinger and the Display Manager Service.
     58  * </p><p>
     59  * Display adapters are guarded by the {@link DisplayManagerService.SyncRoot} lock.
     60  * </p>
     61  */
     62 final class WifiDisplayAdapter extends DisplayAdapter {
     63     private static final String TAG = "WifiDisplayAdapter";
     64 
     65     private static final boolean DEBUG = false;
     66 
     67     private static final int MSG_SEND_STATUS_CHANGE_BROADCAST = 1;
     68 
     69     private static final String ACTION_DISCONNECT = "android.server.display.wfd.DISCONNECT";
     70 
     71     // Unique id prefix for wifi displays
     72     private static final String DISPLAY_NAME_PREFIX = "wifi:";
     73 
     74     private final WifiDisplayHandler mHandler;
     75     private final PersistentDataStore mPersistentDataStore;
     76     private final boolean mSupportsProtectedBuffers;
     77 
     78     private WifiDisplayController mDisplayController;
     79     private WifiDisplayDevice mDisplayDevice;
     80 
     81     private WifiDisplayStatus mCurrentStatus;
     82     private int mFeatureState;
     83     private int mScanState;
     84     private int mActiveDisplayState;
     85     private WifiDisplay mActiveDisplay;
     86     private WifiDisplay[] mDisplays = WifiDisplay.EMPTY_ARRAY;
     87     private WifiDisplay[] mAvailableDisplays = WifiDisplay.EMPTY_ARRAY;
     88     private WifiDisplay[] mRememberedDisplays = WifiDisplay.EMPTY_ARRAY;
     89     private WifiDisplaySessionInfo mSessionInfo;
     90 
     91     private boolean mPendingStatusChangeBroadcast;
     92 
     93     // Called with SyncRoot lock held.
     94     public WifiDisplayAdapter(DisplayManagerService.SyncRoot syncRoot,
     95             Context context, Handler handler, Listener listener,
     96             PersistentDataStore persistentDataStore) {
     97         super(syncRoot, context, handler, listener, TAG);
     98         mHandler = new WifiDisplayHandler(handler.getLooper());
     99         mPersistentDataStore = persistentDataStore;
    100         mSupportsProtectedBuffers = context.getResources().getBoolean(
    101                 com.android.internal.R.bool.config_wifiDisplaySupportsProtectedBuffers);
    102     }
    103 
    104     @Override
    105     public void dumpLocked(PrintWriter pw) {
    106         super.dumpLocked(pw);
    107 
    108         pw.println("mCurrentStatus=" + getWifiDisplayStatusLocked());
    109         pw.println("mFeatureState=" + mFeatureState);
    110         pw.println("mScanState=" + mScanState);
    111         pw.println("mActiveDisplayState=" + mActiveDisplayState);
    112         pw.println("mActiveDisplay=" + mActiveDisplay);
    113         pw.println("mDisplays=" + Arrays.toString(mDisplays));
    114         pw.println("mAvailableDisplays=" + Arrays.toString(mAvailableDisplays));
    115         pw.println("mRememberedDisplays=" + Arrays.toString(mRememberedDisplays));
    116         pw.println("mPendingStatusChangeBroadcast=" + mPendingStatusChangeBroadcast);
    117         pw.println("mSupportsProtectedBuffers=" + mSupportsProtectedBuffers);
    118 
    119         // Try to dump the controller state.
    120         if (mDisplayController == null) {
    121             pw.println("mDisplayController=null");
    122         } else {
    123             pw.println("mDisplayController:");
    124             final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "  ");
    125             ipw.increaseIndent();
    126             DumpUtils.dumpAsync(getHandler(), mDisplayController, ipw, 200);
    127         }
    128     }
    129 
    130     @Override
    131     public void registerLocked() {
    132         super.registerLocked();
    133 
    134         updateRememberedDisplaysLocked();
    135 
    136         getHandler().post(new Runnable() {
    137             @Override
    138             public void run() {
    139                 mDisplayController = new WifiDisplayController(
    140                         getContext(), getHandler(), mWifiDisplayListener);
    141 
    142                 getContext().registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL,
    143                         new IntentFilter(ACTION_DISCONNECT), null, mHandler);
    144             }
    145         });
    146     }
    147 
    148     public void requestStartScanLocked() {
    149         if (DEBUG) {
    150             Slog.d(TAG, "requestStartScanLocked");
    151         }
    152 
    153         getHandler().post(new Runnable() {
    154             @Override
    155             public void run() {
    156                 if (mDisplayController != null) {
    157                     mDisplayController.requestStartScan();
    158                 }
    159             }
    160         });
    161     }
    162 
    163     public void requestStopScanLocked() {
    164         if (DEBUG) {
    165             Slog.d(TAG, "requestStopScanLocked");
    166         }
    167 
    168         getHandler().post(new Runnable() {
    169             @Override
    170             public void run() {
    171                 if (mDisplayController != null) {
    172                     mDisplayController.requestStopScan();
    173                 }
    174             }
    175         });
    176     }
    177 
    178     public void requestConnectLocked(final String address) {
    179         if (DEBUG) {
    180             Slog.d(TAG, "requestConnectLocked: address=" + address);
    181         }
    182 
    183         getHandler().post(new Runnable() {
    184             @Override
    185             public void run() {
    186                 if (mDisplayController != null) {
    187                     mDisplayController.requestConnect(address);
    188                 }
    189             }
    190         });
    191     }
    192 
    193     public void requestPauseLocked() {
    194         if (DEBUG) {
    195             Slog.d(TAG, "requestPauseLocked");
    196         }
    197 
    198         getHandler().post(new Runnable() {
    199             @Override
    200             public void run() {
    201                 if (mDisplayController != null) {
    202                     mDisplayController.requestPause();
    203                 }
    204             }
    205         });
    206       }
    207 
    208     public void requestResumeLocked() {
    209         if (DEBUG) {
    210             Slog.d(TAG, "requestResumeLocked");
    211         }
    212 
    213         getHandler().post(new Runnable() {
    214             @Override
    215             public void run() {
    216                 if (mDisplayController != null) {
    217                     mDisplayController.requestResume();
    218                 }
    219             }
    220         });
    221     }
    222 
    223     public void requestDisconnectLocked() {
    224         if (DEBUG) {
    225             Slog.d(TAG, "requestDisconnectedLocked");
    226         }
    227 
    228         getHandler().post(new Runnable() {
    229             @Override
    230             public void run() {
    231                 if (mDisplayController != null) {
    232                     mDisplayController.requestDisconnect();
    233                 }
    234             }
    235         });
    236     }
    237 
    238     public void requestRenameLocked(String address, String alias) {
    239         if (DEBUG) {
    240             Slog.d(TAG, "requestRenameLocked: address=" + address + ", alias=" + alias);
    241         }
    242 
    243         if (alias != null) {
    244             alias = alias.trim();
    245             if (alias.isEmpty() || alias.equals(address)) {
    246                 alias = null;
    247             }
    248         }
    249 
    250         WifiDisplay display = mPersistentDataStore.getRememberedWifiDisplay(address);
    251         if (display != null && !Objects.equal(display.getDeviceAlias(), alias)) {
    252             display = new WifiDisplay(address, display.getDeviceName(), alias,
    253                     false, false, false);
    254             if (mPersistentDataStore.rememberWifiDisplay(display)) {
    255                 mPersistentDataStore.saveIfNeeded();
    256                 updateRememberedDisplaysLocked();
    257                 scheduleStatusChangedBroadcastLocked();
    258             }
    259         }
    260 
    261         if (mActiveDisplay != null && mActiveDisplay.getDeviceAddress().equals(address)) {
    262             renameDisplayDeviceLocked(mActiveDisplay.getFriendlyDisplayName());
    263         }
    264     }
    265 
    266     public void requestForgetLocked(String address) {
    267         if (DEBUG) {
    268             Slog.d(TAG, "requestForgetLocked: address=" + address);
    269         }
    270 
    271         if (mPersistentDataStore.forgetWifiDisplay(address)) {
    272             mPersistentDataStore.saveIfNeeded();
    273             updateRememberedDisplaysLocked();
    274             scheduleStatusChangedBroadcastLocked();
    275         }
    276 
    277         if (mActiveDisplay != null && mActiveDisplay.getDeviceAddress().equals(address)) {
    278             requestDisconnectLocked();
    279         }
    280     }
    281 
    282     public WifiDisplayStatus getWifiDisplayStatusLocked() {
    283         if (mCurrentStatus == null) {
    284             mCurrentStatus = new WifiDisplayStatus(
    285                     mFeatureState, mScanState, mActiveDisplayState,
    286                     mActiveDisplay, mDisplays, mSessionInfo);
    287         }
    288 
    289         if (DEBUG) {
    290             Slog.d(TAG, "getWifiDisplayStatusLocked: result=" + mCurrentStatus);
    291         }
    292         return mCurrentStatus;
    293     }
    294 
    295     private void updateDisplaysLocked() {
    296         List<WifiDisplay> displays = new ArrayList<WifiDisplay>(
    297                 mAvailableDisplays.length + mRememberedDisplays.length);
    298         boolean[] remembered = new boolean[mAvailableDisplays.length];
    299         for (WifiDisplay d : mRememberedDisplays) {
    300             boolean available = false;
    301             for (int i = 0; i < mAvailableDisplays.length; i++) {
    302                 if (d.equals(mAvailableDisplays[i])) {
    303                     remembered[i] = available = true;
    304                     break;
    305                 }
    306             }
    307             if (!available) {
    308                 displays.add(new WifiDisplay(d.getDeviceAddress(), d.getDeviceName(),
    309                         d.getDeviceAlias(), false, false, true));
    310             }
    311         }
    312         for (int i = 0; i < mAvailableDisplays.length; i++) {
    313             WifiDisplay d = mAvailableDisplays[i];
    314             displays.add(new WifiDisplay(d.getDeviceAddress(), d.getDeviceName(),
    315                     d.getDeviceAlias(), true, d.canConnect(), remembered[i]));
    316         }
    317         mDisplays = displays.toArray(WifiDisplay.EMPTY_ARRAY);
    318     }
    319 
    320     private void updateRememberedDisplaysLocked() {
    321         mRememberedDisplays = mPersistentDataStore.getRememberedWifiDisplays();
    322         mActiveDisplay = mPersistentDataStore.applyWifiDisplayAlias(mActiveDisplay);
    323         mAvailableDisplays = mPersistentDataStore.applyWifiDisplayAliases(mAvailableDisplays);
    324         updateDisplaysLocked();
    325     }
    326 
    327     private void fixRememberedDisplayNamesFromAvailableDisplaysLocked() {
    328         // It may happen that a display name has changed since it was remembered.
    329         // Consult the list of available displays and update the name if needed.
    330         // We don't do anything special for the active display here.  The display
    331         // controller will send a separate event when it needs to be updates.
    332         boolean changed = false;
    333         for (int i = 0; i < mRememberedDisplays.length; i++) {
    334             WifiDisplay rememberedDisplay = mRememberedDisplays[i];
    335             WifiDisplay availableDisplay = findAvailableDisplayLocked(
    336                     rememberedDisplay.getDeviceAddress());
    337             if (availableDisplay != null && !rememberedDisplay.equals(availableDisplay)) {
    338                 if (DEBUG) {
    339                     Slog.d(TAG, "fixRememberedDisplayNamesFromAvailableDisplaysLocked: "
    340                             + "updating remembered display to " + availableDisplay);
    341                 }
    342                 mRememberedDisplays[i] = availableDisplay;
    343                 changed |= mPersistentDataStore.rememberWifiDisplay(availableDisplay);
    344             }
    345         }
    346         if (changed) {
    347             mPersistentDataStore.saveIfNeeded();
    348         }
    349     }
    350 
    351     private WifiDisplay findAvailableDisplayLocked(String address) {
    352         for (WifiDisplay display : mAvailableDisplays) {
    353             if (display.getDeviceAddress().equals(address)) {
    354                 return display;
    355             }
    356         }
    357         return null;
    358     }
    359 
    360     private void addDisplayDeviceLocked(WifiDisplay display,
    361             Surface surface, int width, int height, int flags) {
    362         removeDisplayDeviceLocked();
    363 
    364         if (mPersistentDataStore.rememberWifiDisplay(display)) {
    365             mPersistentDataStore.saveIfNeeded();
    366             updateRememberedDisplaysLocked();
    367             scheduleStatusChangedBroadcastLocked();
    368         }
    369 
    370         boolean secure = (flags & RemoteDisplay.DISPLAY_FLAG_SECURE) != 0;
    371         int deviceFlags = DisplayDeviceInfo.FLAG_PRESENTATION;
    372         if (secure) {
    373             deviceFlags |= DisplayDeviceInfo.FLAG_SECURE;
    374             if (mSupportsProtectedBuffers) {
    375                 deviceFlags |= DisplayDeviceInfo.FLAG_SUPPORTS_PROTECTED_BUFFERS;
    376             }
    377         }
    378 
    379         float refreshRate = 60.0f; // TODO: get this for real
    380 
    381         String name = display.getFriendlyDisplayName();
    382         String address = display.getDeviceAddress();
    383         IBinder displayToken = SurfaceControl.createDisplay(name, secure);
    384         mDisplayDevice = new WifiDisplayDevice(displayToken, name, width, height,
    385                 refreshRate, deviceFlags, address, surface);
    386         sendDisplayDeviceEventLocked(mDisplayDevice, DISPLAY_DEVICE_EVENT_ADDED);
    387     }
    388 
    389     private void removeDisplayDeviceLocked() {
    390         if (mDisplayDevice != null) {
    391             mDisplayDevice.destroyLocked();
    392             sendDisplayDeviceEventLocked(mDisplayDevice, DISPLAY_DEVICE_EVENT_REMOVED);
    393             mDisplayDevice = null;
    394         }
    395     }
    396 
    397     private void renameDisplayDeviceLocked(String name) {
    398         if (mDisplayDevice != null && !mDisplayDevice.getNameLocked().equals(name)) {
    399             mDisplayDevice.setNameLocked(name);
    400             sendDisplayDeviceEventLocked(mDisplayDevice, DISPLAY_DEVICE_EVENT_CHANGED);
    401         }
    402     }
    403 
    404     private void scheduleStatusChangedBroadcastLocked() {
    405         mCurrentStatus = null;
    406         if (!mPendingStatusChangeBroadcast) {
    407             mPendingStatusChangeBroadcast = true;
    408             mHandler.sendEmptyMessage(MSG_SEND_STATUS_CHANGE_BROADCAST);
    409         }
    410     }
    411 
    412     // Runs on the handler.
    413     private void handleSendStatusChangeBroadcast() {
    414         final Intent intent;
    415         synchronized (getSyncRoot()) {
    416             if (!mPendingStatusChangeBroadcast) {
    417                 return;
    418             }
    419 
    420             mPendingStatusChangeBroadcast = false;
    421             intent = new Intent(DisplayManager.ACTION_WIFI_DISPLAY_STATUS_CHANGED);
    422             intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
    423             intent.putExtra(DisplayManager.EXTRA_WIFI_DISPLAY_STATUS,
    424                     getWifiDisplayStatusLocked());
    425         }
    426 
    427         // Send protected broadcast about wifi display status to registered receivers.
    428         getContext().sendBroadcastAsUser(intent, UserHandle.ALL);
    429     }
    430 
    431     private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
    432         @Override
    433         public void onReceive(Context context, Intent intent) {
    434             if (intent.getAction().equals(ACTION_DISCONNECT)) {
    435                 synchronized (getSyncRoot()) {
    436                     requestDisconnectLocked();
    437                 }
    438             }
    439         }
    440     };
    441 
    442     private final WifiDisplayController.Listener mWifiDisplayListener =
    443             new WifiDisplayController.Listener() {
    444         @Override
    445         public void onFeatureStateChanged(int featureState) {
    446             synchronized (getSyncRoot()) {
    447                 if (mFeatureState != featureState) {
    448                     mFeatureState = featureState;
    449                     scheduleStatusChangedBroadcastLocked();
    450                 }
    451             }
    452         }
    453 
    454         @Override
    455         public void onScanStarted() {
    456             synchronized (getSyncRoot()) {
    457                 if (mScanState != WifiDisplayStatus.SCAN_STATE_SCANNING) {
    458                     mScanState = WifiDisplayStatus.SCAN_STATE_SCANNING;
    459                     scheduleStatusChangedBroadcastLocked();
    460                 }
    461             }
    462         }
    463 
    464         @Override
    465         public void onScanResults(WifiDisplay[] availableDisplays) {
    466             synchronized (getSyncRoot()) {
    467                 availableDisplays = mPersistentDataStore.applyWifiDisplayAliases(
    468                         availableDisplays);
    469 
    470                 boolean changed = !Arrays.equals(mAvailableDisplays, availableDisplays);
    471 
    472                 // Check whether any of the available displays changed canConnect status.
    473                 for (int i = 0; !changed && i<availableDisplays.length; i++) {
    474                     changed = availableDisplays[i].canConnect()
    475                             != mAvailableDisplays[i].canConnect();
    476                 }
    477 
    478                 if (changed) {
    479                     mAvailableDisplays = availableDisplays;
    480                     fixRememberedDisplayNamesFromAvailableDisplaysLocked();
    481                     updateDisplaysLocked();
    482                     scheduleStatusChangedBroadcastLocked();
    483                 }
    484             }
    485         }
    486 
    487         @Override
    488         public void onScanFinished() {
    489             synchronized (getSyncRoot()) {
    490                 if (mScanState != WifiDisplayStatus.SCAN_STATE_NOT_SCANNING) {
    491                     mScanState = WifiDisplayStatus.SCAN_STATE_NOT_SCANNING;
    492                     scheduleStatusChangedBroadcastLocked();
    493                 }
    494             }
    495         }
    496 
    497         @Override
    498         public void onDisplayConnecting(WifiDisplay display) {
    499             synchronized (getSyncRoot()) {
    500                 display = mPersistentDataStore.applyWifiDisplayAlias(display);
    501 
    502                 if (mActiveDisplayState != WifiDisplayStatus.DISPLAY_STATE_CONNECTING
    503                         || mActiveDisplay == null
    504                         || !mActiveDisplay.equals(display)) {
    505                     mActiveDisplayState = WifiDisplayStatus.DISPLAY_STATE_CONNECTING;
    506                     mActiveDisplay = display;
    507                     scheduleStatusChangedBroadcastLocked();
    508                 }
    509             }
    510         }
    511 
    512         @Override
    513         public void onDisplayConnectionFailed() {
    514             synchronized (getSyncRoot()) {
    515                 if (mActiveDisplayState != WifiDisplayStatus.DISPLAY_STATE_NOT_CONNECTED
    516                         || mActiveDisplay != null) {
    517                     mActiveDisplayState = WifiDisplayStatus.DISPLAY_STATE_NOT_CONNECTED;
    518                     mActiveDisplay = null;
    519                     scheduleStatusChangedBroadcastLocked();
    520                 }
    521             }
    522         }
    523 
    524         @Override
    525         public void onDisplayConnected(WifiDisplay display, Surface surface,
    526                 int width, int height, int flags) {
    527             synchronized (getSyncRoot()) {
    528                 display = mPersistentDataStore.applyWifiDisplayAlias(display);
    529                 addDisplayDeviceLocked(display, surface, width, height, flags);
    530 
    531                 if (mActiveDisplayState != WifiDisplayStatus.DISPLAY_STATE_CONNECTED
    532                         || mActiveDisplay == null
    533                         || !mActiveDisplay.equals(display)) {
    534                     mActiveDisplayState = WifiDisplayStatus.DISPLAY_STATE_CONNECTED;
    535                     mActiveDisplay = display;
    536                     scheduleStatusChangedBroadcastLocked();
    537                 }
    538             }
    539         }
    540 
    541         @Override
    542         public void onDisplaySessionInfo(WifiDisplaySessionInfo sessionInfo) {
    543             synchronized (getSyncRoot()) {
    544                 mSessionInfo = sessionInfo;
    545                 scheduleStatusChangedBroadcastLocked();
    546             }
    547         }
    548 
    549         @Override
    550         public void onDisplayChanged(WifiDisplay display) {
    551             synchronized (getSyncRoot()) {
    552                 display = mPersistentDataStore.applyWifiDisplayAlias(display);
    553                 if (mActiveDisplay != null
    554                         && mActiveDisplay.hasSameAddress(display)
    555                         && !mActiveDisplay.equals(display)) {
    556                     mActiveDisplay = display;
    557                     renameDisplayDeviceLocked(display.getFriendlyDisplayName());
    558                     scheduleStatusChangedBroadcastLocked();
    559                 }
    560             }
    561         }
    562 
    563         @Override
    564         public void onDisplayDisconnected() {
    565             // Stop listening.
    566             synchronized (getSyncRoot()) {
    567                 removeDisplayDeviceLocked();
    568 
    569                 if (mActiveDisplayState != WifiDisplayStatus.DISPLAY_STATE_NOT_CONNECTED
    570                         || mActiveDisplay != null) {
    571                     mActiveDisplayState = WifiDisplayStatus.DISPLAY_STATE_NOT_CONNECTED;
    572                     mActiveDisplay = null;
    573                     scheduleStatusChangedBroadcastLocked();
    574                 }
    575             }
    576         }
    577     };
    578 
    579     private final class WifiDisplayDevice extends DisplayDevice {
    580         private String mName;
    581         private final int mWidth;
    582         private final int mHeight;
    583         private final float mRefreshRate;
    584         private final int mFlags;
    585         private final String mAddress;
    586 
    587         private Surface mSurface;
    588         private DisplayDeviceInfo mInfo;
    589 
    590         public WifiDisplayDevice(IBinder displayToken, String name,
    591                 int width, int height, float refreshRate, int flags, String address,
    592                 Surface surface) {
    593             super(WifiDisplayAdapter.this, displayToken, DISPLAY_NAME_PREFIX + address);
    594             mName = name;
    595             mWidth = width;
    596             mHeight = height;
    597             mRefreshRate = refreshRate;
    598             mFlags = flags;
    599             mAddress = address;
    600             mSurface = surface;
    601         }
    602 
    603         public void destroyLocked() {
    604             if (mSurface != null) {
    605                 mSurface.release();
    606                 mSurface = null;
    607             }
    608             SurfaceControl.destroyDisplay(getDisplayTokenLocked());
    609         }
    610 
    611         public void setNameLocked(String name) {
    612             mName = name;
    613             mInfo = null;
    614         }
    615 
    616         @Override
    617         public void performTraversalInTransactionLocked() {
    618             if (mSurface != null) {
    619                 setSurfaceInTransactionLocked(mSurface);
    620             }
    621         }
    622 
    623         @Override
    624         public DisplayDeviceInfo getDisplayDeviceInfoLocked() {
    625             if (mInfo == null) {
    626                 mInfo = new DisplayDeviceInfo();
    627                 mInfo.name = mName;
    628                 mInfo.uniqueId = getUniqueId();
    629                 mInfo.width = mWidth;
    630                 mInfo.height = mHeight;
    631                 mInfo.refreshRate = mRefreshRate;
    632                 mInfo.supportedRefreshRates = new float[] { mRefreshRate };
    633                 mInfo.presentationDeadlineNanos = 1000000000L / (int) mRefreshRate; // 1 frame
    634                 mInfo.flags = mFlags;
    635                 mInfo.type = Display.TYPE_WIFI;
    636                 mInfo.address = mAddress;
    637                 mInfo.touch = DisplayDeviceInfo.TOUCH_EXTERNAL;
    638                 mInfo.setAssumedDensityForExternalDisplay(mWidth, mHeight);
    639             }
    640             return mInfo;
    641         }
    642     }
    643 
    644     private final class WifiDisplayHandler extends Handler {
    645         public WifiDisplayHandler(Looper looper) {
    646             super(looper, null, true /*async*/);
    647         }
    648 
    649         @Override
    650         public void handleMessage(Message msg) {
    651             switch (msg.what) {
    652                 case MSG_SEND_STATUS_CHANGE_BROADCAST:
    653                     handleSendStatusChangeBroadcast();
    654                     break;
    655             }
    656         }
    657     }
    658 }
    659