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