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