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(false);
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(true);
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(false);
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 static final float REFRESH_RATE = 60.0f;
169
170        private final IBinder mAppToken;
171        private final int mOwnerUid;
172        final String mOwnerPackageName;
173        final String mName;
174        private final int mFlags;
175        private final Callback mCallback;
176
177        private int mWidth;
178        private int mHeight;
179        private int mDensityDpi;
180        private Surface mSurface;
181        private DisplayDeviceInfo mInfo;
182        private int mDisplayState;
183        private boolean mStopped;
184        private int mPendingChanges;
185        private int mUniqueIndex;
186        private Display.Mode mMode;
187
188        public VirtualDisplayDevice(IBinder displayToken, IBinder appToken,
189                int ownerUid, String ownerPackageName,
190                String name, int width, int height, int densityDpi, Surface surface, int flags,
191                Callback callback, String uniqueId, int uniqueIndex) {
192            super(VirtualDisplayAdapter.this, displayToken, uniqueId);
193            mAppToken = appToken;
194            mOwnerUid = ownerUid;
195            mOwnerPackageName = ownerPackageName;
196            mName = name;
197            mWidth = width;
198            mHeight = height;
199            mMode = createMode(width, height, REFRESH_RATE);
200            mDensityDpi = densityDpi;
201            mSurface = surface;
202            mFlags = flags;
203            mCallback = callback;
204            mDisplayState = Display.STATE_UNKNOWN;
205            mPendingChanges |= PENDING_SURFACE_CHANGE;
206            mUniqueIndex = uniqueIndex;
207        }
208
209        @Override
210        public void binderDied() {
211            synchronized (getSyncRoot()) {
212                handleBinderDiedLocked(mAppToken);
213            }
214        }
215
216        public void destroyLocked(boolean binderAlive) {
217            if (mSurface != null) {
218                mSurface.release();
219                mSurface = null;
220            }
221            SurfaceControl.destroyDisplay(getDisplayTokenLocked());
222            if (binderAlive) {
223                mCallback.dispatchDisplayStopped();
224            }
225        }
226
227        @Override
228        public Runnable requestDisplayStateLocked(int state, int brightness) {
229            if (state != mDisplayState) {
230                mDisplayState = state;
231                if (state == Display.STATE_OFF) {
232                    mCallback.dispatchDisplayPaused();
233                } else {
234                    mCallback.dispatchDisplayResumed();
235                }
236            }
237            return null;
238        }
239
240        @Override
241        public void performTraversalInTransactionLocked() {
242            if ((mPendingChanges & PENDING_RESIZE) != 0) {
243                SurfaceControl.setDisplaySize(getDisplayTokenLocked(), mWidth, mHeight);
244            }
245            if ((mPendingChanges & PENDING_SURFACE_CHANGE) != 0) {
246                setSurfaceInTransactionLocked(mSurface);
247            }
248            mPendingChanges = 0;
249        }
250
251        public void setSurfaceLocked(Surface surface) {
252            if (!mStopped && mSurface != surface) {
253                if ((mSurface != null) != (surface != null)) {
254                    sendDisplayDeviceEventLocked(this, DISPLAY_DEVICE_EVENT_CHANGED);
255                }
256                sendTraversalRequestLocked();
257                mSurface = surface;
258                mInfo = null;
259                mPendingChanges |= PENDING_SURFACE_CHANGE;
260            }
261        }
262
263        public void resizeLocked(int width, int height, int densityDpi) {
264            if (mWidth != width || mHeight != height || mDensityDpi != densityDpi) {
265                sendDisplayDeviceEventLocked(this, DISPLAY_DEVICE_EVENT_CHANGED);
266                sendTraversalRequestLocked();
267                mWidth = width;
268                mHeight = height;
269                mMode = createMode(width, height, REFRESH_RATE);
270                mDensityDpi = densityDpi;
271                mInfo = null;
272                mPendingChanges |= PENDING_RESIZE;
273            }
274        }
275
276        public void stopLocked() {
277            setSurfaceLocked(null);
278            mStopped = true;
279        }
280
281        @Override
282        public void dumpLocked(PrintWriter pw) {
283            super.dumpLocked(pw);
284            pw.println("mFlags=" + mFlags);
285            pw.println("mDisplayState=" + Display.stateToString(mDisplayState));
286            pw.println("mStopped=" + mStopped);
287        }
288
289
290        @Override
291        public DisplayDeviceInfo getDisplayDeviceInfoLocked() {
292            if (mInfo == null) {
293                mInfo = new DisplayDeviceInfo();
294                mInfo.name = mName;
295                mInfo.uniqueId = getUniqueId();
296                mInfo.width = mWidth;
297                mInfo.height = mHeight;
298                mInfo.modeId = mMode.getModeId();
299                mInfo.defaultModeId = mMode.getModeId();
300                mInfo.supportedModes = new Display.Mode[] { mMode };
301                mInfo.densityDpi = mDensityDpi;
302                mInfo.xDpi = mDensityDpi;
303                mInfo.yDpi = mDensityDpi;
304                mInfo.presentationDeadlineNanos = 1000000000L / (int) REFRESH_RATE; // 1 frame
305                mInfo.flags = 0;
306                if ((mFlags & DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC) == 0) {
307                    mInfo.flags |= DisplayDeviceInfo.FLAG_PRIVATE
308                            | DisplayDeviceInfo.FLAG_NEVER_BLANK;
309                }
310                if ((mFlags & DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR) != 0) {
311                    mInfo.flags &= ~DisplayDeviceInfo.FLAG_NEVER_BLANK;
312                } else {
313                    mInfo.flags |= DisplayDeviceInfo.FLAG_OWN_CONTENT_ONLY;
314                }
315
316                if ((mFlags & DisplayManager.VIRTUAL_DISPLAY_FLAG_SECURE) != 0) {
317                    mInfo.flags |= DisplayDeviceInfo.FLAG_SECURE;
318                }
319                if ((mFlags & DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION) != 0) {
320                    mInfo.flags |= DisplayDeviceInfo.FLAG_PRESENTATION;
321
322                    if ((mFlags & DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC) != 0) {
323                        // For demonstration purposes, allow rotation of the external display.
324                        // In the future we might allow the user to configure this directly.
325                        if ("portrait".equals(SystemProperties.get(
326                                "persist.demo.remoterotation"))) {
327                            mInfo.rotation = Surface.ROTATION_270;
328                        }
329                    }
330                }
331                mInfo.type = Display.TYPE_VIRTUAL;
332                mInfo.touch = DisplayDeviceInfo.TOUCH_NONE;
333                mInfo.state = mSurface != null ? Display.STATE_ON : Display.STATE_OFF;
334                mInfo.ownerUid = mOwnerUid;
335                mInfo.ownerPackageName = mOwnerPackageName;
336            }
337            return mInfo;
338        }
339    }
340
341    private static class Callback extends Handler {
342        private static final int MSG_ON_DISPLAY_PAUSED = 0;
343        private static final int MSG_ON_DISPLAY_RESUMED = 1;
344        private static final int MSG_ON_DISPLAY_STOPPED = 2;
345
346        private final IVirtualDisplayCallback mCallback;
347
348        public Callback(IVirtualDisplayCallback callback, Handler handler) {
349            super(handler.getLooper());
350            mCallback = callback;
351        }
352
353        @Override
354        public void handleMessage(Message msg) {
355            try {
356                switch (msg.what) {
357                    case MSG_ON_DISPLAY_PAUSED:
358                        mCallback.onPaused();
359                        break;
360                    case MSG_ON_DISPLAY_RESUMED:
361                        mCallback.onResumed();
362                        break;
363                    case MSG_ON_DISPLAY_STOPPED:
364                        mCallback.onStopped();
365                        break;
366                }
367            } catch (RemoteException e) {
368                Slog.w(TAG, "Failed to notify listener of virtual display event.", e);
369            }
370        }
371
372        public void dispatchDisplayPaused() {
373            sendEmptyMessage(MSG_ON_DISPLAY_PAUSED);
374        }
375
376        public void dispatchDisplayResumed() {
377            sendEmptyMessage(MSG_ON_DISPLAY_RESUMED);
378        }
379
380        public void dispatchDisplayStopped() {
381            sendEmptyMessage(MSG_ON_DISPLAY_STOPPED);
382        }
383    }
384
385    private final class MediaProjectionCallback extends IMediaProjectionCallback.Stub {
386        private IBinder mAppToken;
387        public MediaProjectionCallback(IBinder appToken) {
388            mAppToken = appToken;
389        }
390
391        @Override
392        public void onStop() {
393            synchronized (getSyncRoot()) {
394                handleMediaProjectionStoppedLocked(mAppToken);
395            }
396        }
397    }
398}
399