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