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