VirtualDisplayAdapter.java revision ae6119ffce7f2be8846d1d2fd1497965946b66b8
1/*
2 * Copyright (C) 2013 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
17package com.android.server.display;
18
19import android.content.Context;
20import android.hardware.display.DisplayManager;
21import android.hardware.display.IVirtualDisplayCallback;
22import android.media.projection.IMediaProjection;
23import android.media.projection.IMediaProjectionCallback;
24import android.os.Handler;
25import android.os.IBinder;
26import android.os.SystemProperties;
27import android.os.IBinder.DeathRecipient;
28import android.os.Message;
29import android.os.RemoteException;
30import android.util.ArrayMap;
31import android.util.Slog;
32import android.view.Display;
33import android.view.Surface;
34import android.view.SurfaceControl;
35
36import java.io.PrintWriter;
37
38/**
39 * A display adapter that provides virtual displays on behalf of applications.
40 * <p>
41 * Display adapters are guarded by the {@link DisplayManagerService.SyncRoot} lock.
42 * </p>
43 */
44final class VirtualDisplayAdapter extends DisplayAdapter {
45    static final String TAG = "VirtualDisplayAdapter";
46    static final boolean DEBUG = false;
47
48    private final ArrayMap<IBinder, VirtualDisplayDevice> mVirtualDisplayDevices =
49            new ArrayMap<IBinder, VirtualDisplayDevice>();
50    private Handler mHandler;
51
52    // Called with SyncRoot lock held.
53    public VirtualDisplayAdapter(DisplayManagerService.SyncRoot syncRoot,
54            Context context, Handler handler, Listener listener) {
55        super(syncRoot, context, handler, listener, TAG);
56        mHandler = handler;
57    }
58
59    public DisplayDevice createVirtualDisplayLocked(IVirtualDisplayCallback callback,
60            IMediaProjection projection, int ownerUid, String ownerPackageName,
61            String name, int width, int height, int densityDpi, Surface surface, int flags) {
62        boolean secure = (flags & DisplayManager.VIRTUAL_DISPLAY_FLAG_SECURE) != 0;
63        IBinder appToken = callback.asBinder();
64        IBinder displayToken = SurfaceControl.createDisplay(name, secure);
65        VirtualDisplayDevice device = new VirtualDisplayDevice(displayToken, appToken,
66                ownerUid, ownerPackageName, name, width, height, densityDpi, surface, flags,
67                new Callback(callback, mHandler));
68
69        mVirtualDisplayDevices.put(appToken, device);
70
71        try {
72            if (projection != null) {
73                projection.registerCallback(new MediaProjectionCallback(appToken));
74            }
75            appToken.linkToDeath(device, 0);
76        } catch (RemoteException ex) {
77            mVirtualDisplayDevices.remove(appToken);
78            device.destroyLocked();
79            return null;
80        }
81
82        // Return the display device without actually sending the event indicating
83        // that it was added.  The caller will handle it.
84        return device;
85    }
86
87    public void resizeVirtualDisplayLocked(IBinder appToken,
88            int width, int height, int densityDpi) {
89        VirtualDisplayDevice device = mVirtualDisplayDevices.get(appToken);
90        if (device != null) {
91            device.resizeLocked(width, height, densityDpi);
92        }
93    }
94
95
96    public void setVirtualDisplaySurfaceLocked(IBinder appToken, Surface surface) {
97        VirtualDisplayDevice device = mVirtualDisplayDevices.get(appToken);
98        if (device != null) {
99            device.setSurfaceLocked(surface);
100        }
101    }
102
103    public DisplayDevice releaseVirtualDisplayLocked(IBinder appToken) {
104        VirtualDisplayDevice device = mVirtualDisplayDevices.remove(appToken);
105        if (device != null) {
106            device.destroyLocked();
107            appToken.unlinkToDeath(device, 0);
108        }
109
110        // Return the display device that was removed without actually sending the
111        // event indicating that it was removed.  The caller will handle it.
112        return device;
113    }
114
115    private void handleBinderDiedLocked(IBinder appToken) {
116        VirtualDisplayDevice device = mVirtualDisplayDevices.remove(appToken);
117        if (device != null) {
118            Slog.i(TAG, "Virtual display device released because application token died: "
119                    + device.mOwnerPackageName);
120            device.destroyLocked();
121            sendDisplayDeviceEventLocked(device, DISPLAY_DEVICE_EVENT_REMOVED);
122        }
123    }
124
125    private void handleMediaProjectionStoppedLocked(IBinder appToken) {
126        VirtualDisplayDevice device = mVirtualDisplayDevices.remove(appToken);
127        if (device != null) {
128            Slog.i(TAG, "Virtual display device released because media projection stopped: "
129                    + device.mName);
130            device.stopLocked();
131        }
132    }
133
134    private final class VirtualDisplayDevice extends DisplayDevice implements DeathRecipient {
135        private static final int PENDING_SURFACE_CHANGE = 0x01;
136        private static final int PENDING_RESIZE = 0x02;
137
138        private final IBinder mAppToken;
139        private final int mOwnerUid;
140        final String mOwnerPackageName;
141        final String mName;
142        private final int mFlags;
143        private final Callback mCallback;
144
145        private int mWidth;
146        private int mHeight;
147        private int mDensityDpi;
148        private Surface mSurface;
149        private DisplayDeviceInfo mInfo;
150        private int mDisplayState;
151        private boolean mStopped;
152        private int mPendingChanges;
153
154        public VirtualDisplayDevice(IBinder displayToken, IBinder appToken,
155                int ownerUid, String ownerPackageName,
156                String name, int width, int height, int densityDpi, Surface surface, int flags,
157                Callback callback) {
158            super(VirtualDisplayAdapter.this, displayToken);
159            mAppToken = appToken;
160            mOwnerUid = ownerUid;
161            mOwnerPackageName = ownerPackageName;
162            mName = name;
163            mWidth = width;
164            mHeight = height;
165            mDensityDpi = densityDpi;
166            mSurface = surface;
167            mFlags = flags;
168            mCallback = callback;
169            mDisplayState = Display.STATE_UNKNOWN;
170            mPendingChanges |= PENDING_SURFACE_CHANGE;
171        }
172
173        @Override
174        public void binderDied() {
175            synchronized (getSyncRoot()) {
176                if (mSurface != null) {
177                    handleBinderDiedLocked(mAppToken);
178                }
179            }
180        }
181
182        public void destroyLocked() {
183            if (mSurface != null) {
184                mSurface.release();
185                mSurface = null;
186            }
187            SurfaceControl.destroyDisplay(getDisplayTokenLocked());
188            mCallback.dispatchDisplayStopped();
189        }
190
191        @Override
192        public Runnable requestDisplayStateLocked(int state) {
193            if (state != mDisplayState) {
194                mDisplayState = state;
195                if (state == Display.STATE_OFF) {
196                    mCallback.dispatchDisplayPaused();
197                } else {
198                    mCallback.dispatchDisplayResumed();
199                }
200            }
201            return null;
202        }
203
204        @Override
205        public void performTraversalInTransactionLocked() {
206            if ((mPendingChanges & PENDING_RESIZE) != 0) {
207                SurfaceControl.setDisplaySize(getDisplayTokenLocked(), mWidth, mHeight);
208            }
209            if ((mPendingChanges & PENDING_SURFACE_CHANGE) != 0) {
210                setSurfaceInTransactionLocked(mSurface);
211            }
212            mPendingChanges = 0;
213        }
214
215        public void setSurfaceLocked(Surface surface) {
216            if (!mStopped && mSurface != surface) {
217                if ((mSurface != null) != (surface != null)) {
218                    sendDisplayDeviceEventLocked(this, DISPLAY_DEVICE_EVENT_CHANGED);
219                }
220                sendTraversalRequestLocked();
221                mSurface = surface;
222                mInfo = null;
223                mPendingChanges |= PENDING_SURFACE_CHANGE;
224            }
225        }
226
227        public void resizeLocked(int width, int height, int densityDpi) {
228            if (mWidth != width || mHeight != height || mDensityDpi != densityDpi) {
229                sendDisplayDeviceEventLocked(this, DISPLAY_DEVICE_EVENT_CHANGED);
230                sendTraversalRequestLocked();
231                mWidth = width;
232                mHeight = height;
233                mDensityDpi = densityDpi;
234                mInfo = null;
235                mPendingChanges |= PENDING_RESIZE;
236            }
237        }
238
239        public void stopLocked() {
240            setSurfaceLocked(null);
241            mStopped = true;
242        }
243
244        @Override
245        public void dumpLocked(PrintWriter pw) {
246            super.dumpLocked(pw);
247            pw.println("mFlags=" + mFlags);
248            pw.println("mDisplayState=" + Display.stateToString(mDisplayState));
249            pw.println("mStopped=" + mStopped);
250        }
251
252
253        @Override
254        public DisplayDeviceInfo getDisplayDeviceInfoLocked() {
255            if (mInfo == null) {
256                mInfo = new DisplayDeviceInfo();
257                mInfo.name = mName;
258                mInfo.width = mWidth;
259                mInfo.height = mHeight;
260                mInfo.refreshRate = 60;
261                mInfo.supportedRefreshRates = new float[] { 60.0f };
262                mInfo.densityDpi = mDensityDpi;
263                mInfo.xDpi = mDensityDpi;
264                mInfo.yDpi = mDensityDpi;
265                mInfo.presentationDeadlineNanos = 1000000000L / (int) mInfo.refreshRate; // 1 frame
266                mInfo.flags = 0;
267                if ((mFlags & DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC) == 0) {
268                    mInfo.flags |= DisplayDeviceInfo.FLAG_PRIVATE
269                            | DisplayDeviceInfo.FLAG_NEVER_BLANK;
270                }
271                if ((mFlags & DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR) != 0) {
272                    mInfo.flags &= ~DisplayDeviceInfo.FLAG_NEVER_BLANK;
273                } else {
274                    mInfo.flags |= DisplayDeviceInfo.FLAG_OWN_CONTENT_ONLY;
275                }
276
277                if ((mFlags & DisplayManager.VIRTUAL_DISPLAY_FLAG_SECURE) != 0) {
278                    mInfo.flags |= DisplayDeviceInfo.FLAG_SECURE;
279                }
280                if ((mFlags & DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION) != 0) {
281                    mInfo.flags |= DisplayDeviceInfo.FLAG_PRESENTATION;
282
283                    if ((mFlags & DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC) != 0) {
284                        // For demonstration purposes, allow rotation of the external display.
285                        // In the future we might allow the user to configure this directly.
286                        if ("portrait".equals(SystemProperties.get(
287                                "persist.demo.remoterotation"))) {
288                            mInfo.rotation = Surface.ROTATION_270;
289                        }
290                    }
291                }
292                mInfo.type = Display.TYPE_VIRTUAL;
293                mInfo.touch = DisplayDeviceInfo.TOUCH_NONE;
294                mInfo.state = mSurface != null ? Display.STATE_ON : Display.STATE_OFF;
295                mInfo.ownerUid = mOwnerUid;
296                mInfo.ownerPackageName = mOwnerPackageName;
297            }
298            return mInfo;
299        }
300    }
301
302    private static class Callback extends Handler {
303        private static final int MSG_ON_DISPLAY_PAUSED = 0;
304        private static final int MSG_ON_DISPLAY_RESUMED = 1;
305        private static final int MSG_ON_DISPLAY_STOPPED = 2;
306
307        private final IVirtualDisplayCallback mCallback;
308
309        public Callback(IVirtualDisplayCallback callback, Handler handler) {
310            super(handler.getLooper());
311            mCallback = callback;
312        }
313
314        @Override
315        public void handleMessage(Message msg) {
316            try {
317                switch (msg.what) {
318                    case MSG_ON_DISPLAY_PAUSED:
319                        mCallback.onPaused();
320                        break;
321                    case MSG_ON_DISPLAY_RESUMED:
322                        mCallback.onResumed();
323                        break;
324                    case MSG_ON_DISPLAY_STOPPED:
325                        mCallback.onStopped();
326                        break;
327                }
328            } catch (RemoteException e) {
329                Slog.w(TAG, "Failed to notify listener of virtual display event.", e);
330            }
331        }
332
333        public void dispatchDisplayPaused() {
334            sendEmptyMessage(MSG_ON_DISPLAY_PAUSED);
335        }
336
337        public void dispatchDisplayResumed() {
338            sendEmptyMessage(MSG_ON_DISPLAY_RESUMED);
339        }
340
341        public void dispatchDisplayStopped() {
342            sendEmptyMessage(MSG_ON_DISPLAY_STOPPED);
343        }
344    }
345
346    private final class MediaProjectionCallback extends IMediaProjectionCallback.Stub {
347        private IBinder mAppToken;
348        public MediaProjectionCallback(IBinder appToken) {
349            mAppToken = appToken;
350        }
351
352        @Override
353        public void onStop() {
354            synchronized (getSyncRoot()) {
355                handleMediaProjectionStoppedLocked(mAppToken);
356            }
357        }
358    }
359}
360