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
17package com.android.server.display;
18
19import com.android.internal.util.DumpUtils;
20import com.android.internal.util.IndentingPrintWriter;
21
22import android.content.Context;
23import android.database.ContentObserver;
24import android.graphics.SurfaceTexture;
25import android.os.Handler;
26import android.os.IBinder;
27import android.provider.Settings;
28import android.util.DisplayMetrics;
29import android.util.Slog;
30import android.view.Display;
31import android.view.Gravity;
32import android.view.Surface;
33import android.view.SurfaceControl;
34
35import java.io.PrintWriter;
36import java.util.ArrayList;
37import java.util.Arrays;
38import java.util.List;
39import java.util.regex.Matcher;
40import java.util.regex.Pattern;
41
42/**
43 * A display adapter that uses overlay windows to simulate secondary displays
44 * for development purposes.  Use Development Settings to enable one or more
45 * overlay displays.
46 * <p>
47 * This object has two different handlers (which may be the same) which must not
48 * get confused.  The main handler is used to posting messages to the display manager
49 * service as usual.  The UI handler is only used by the {@link OverlayDisplayWindow}.
50 * </p><p>
51 * Display adapters are guarded by the {@link DisplayManagerService.SyncRoot} lock.
52 * </p><p>
53 * This adapter is configured via the
54 * {@link android.provider.Settings.Global#OVERLAY_DISPLAY_DEVICES} setting. This setting should be
55 * formatted as follows:
56 * <pre>
57 * [mode1]|[mode2]|...,[flag1],[flag2],...
58 * </pre>
59 * with each mode specified as:
60 * <pre>
61 * [width]x[height]/[densityDpi]
62 * </pre>
63 * Supported flags:
64 * <ul>
65 * <li><pre>secure</pre>: creates a secure display</li>
66 * </ul>
67 * </p>
68 */
69final class OverlayDisplayAdapter extends DisplayAdapter {
70    static final String TAG = "OverlayDisplayAdapter";
71    static final boolean DEBUG = false;
72
73    private static final int MIN_WIDTH = 100;
74    private static final int MIN_HEIGHT = 100;
75    private static final int MAX_WIDTH = 4096;
76    private static final int MAX_HEIGHT = 4096;
77
78    private static final Pattern DISPLAY_PATTERN =
79            Pattern.compile("([^,]+)(,[a-z]+)*");
80    private static final Pattern MODE_PATTERN =
81            Pattern.compile("(\\d+)x(\\d+)/(\\d+)");
82
83    // Unique id prefix for overlay displays.
84    private static final String UNIQUE_ID_PREFIX = "overlay:";
85
86    private final Handler mUiHandler;
87    private final ArrayList<OverlayDisplayHandle> mOverlays =
88            new ArrayList<OverlayDisplayHandle>();
89    private String mCurrentOverlaySetting = "";
90
91    // Called with SyncRoot lock held.
92    public OverlayDisplayAdapter(DisplayManagerService.SyncRoot syncRoot,
93            Context context, Handler handler, Listener listener, Handler uiHandler) {
94        super(syncRoot, context, handler, listener, TAG);
95        mUiHandler = uiHandler;
96    }
97
98    @Override
99    public void dumpLocked(PrintWriter pw) {
100        super.dumpLocked(pw);
101
102        pw.println("mCurrentOverlaySetting=" + mCurrentOverlaySetting);
103        pw.println("mOverlays: size=" + mOverlays.size());
104        for (OverlayDisplayHandle overlay : mOverlays) {
105            overlay.dumpLocked(pw);
106        }
107    }
108
109    @Override
110    public void registerLocked() {
111        super.registerLocked();
112
113        getHandler().post(new Runnable() {
114            @Override
115            public void run() {
116                getContext().getContentResolver().registerContentObserver(
117                        Settings.Global.getUriFor(Settings.Global.OVERLAY_DISPLAY_DEVICES),
118                        true, new ContentObserver(getHandler()) {
119                            @Override
120                            public void onChange(boolean selfChange) {
121                                updateOverlayDisplayDevices();
122                            }
123                        });
124
125                updateOverlayDisplayDevices();
126            }
127        });
128    }
129
130    private void updateOverlayDisplayDevices() {
131        synchronized (getSyncRoot()) {
132            updateOverlayDisplayDevicesLocked();
133        }
134    }
135
136    private void updateOverlayDisplayDevicesLocked() {
137        String value = Settings.Global.getString(getContext().getContentResolver(),
138                Settings.Global.OVERLAY_DISPLAY_DEVICES);
139        if (value == null) {
140            value = "";
141        }
142
143        if (value.equals(mCurrentOverlaySetting)) {
144            return;
145        }
146        mCurrentOverlaySetting = value;
147
148        if (!mOverlays.isEmpty()) {
149            Slog.i(TAG, "Dismissing all overlay display devices.");
150            for (OverlayDisplayHandle overlay : mOverlays) {
151                overlay.dismissLocked();
152            }
153            mOverlays.clear();
154        }
155
156        int count = 0;
157        for (String part : value.split(";")) {
158            Matcher displayMatcher = DISPLAY_PATTERN.matcher(part);
159            if (displayMatcher.matches()) {
160                if (count >= 4) {
161                    Slog.w(TAG, "Too many overlay display devices specified: " + value);
162                    break;
163                }
164                String modeString = displayMatcher.group(1);
165                String flagString = displayMatcher.group(2);
166                ArrayList<OverlayMode> modes = new ArrayList<>();
167                for (String mode : modeString.split("\\|")) {
168                    Matcher modeMatcher = MODE_PATTERN.matcher(mode);
169                    if (modeMatcher.matches()) {
170                        try {
171                            int width = Integer.parseInt(modeMatcher.group(1), 10);
172                            int height = Integer.parseInt(modeMatcher.group(2), 10);
173                            int densityDpi = Integer.parseInt(modeMatcher.group(3), 10);
174                            if (width >= MIN_WIDTH && width <= MAX_WIDTH
175                                    && height >= MIN_HEIGHT && height <= MAX_HEIGHT
176                                    && densityDpi >= DisplayMetrics.DENSITY_LOW
177                                    && densityDpi <= DisplayMetrics.DENSITY_XXXHIGH) {
178                                modes.add(new OverlayMode(width, height, densityDpi));
179                                continue;
180                            } else {
181                                Slog.w(TAG, "Ignoring out-of-range overlay display mode: " + mode);
182                            }
183                        } catch (NumberFormatException ex) {
184                        }
185                    } else if (mode.isEmpty()) {
186                        continue;
187                    }
188                }
189                if (!modes.isEmpty()) {
190                    int number = ++count;
191                    String name = getContext().getResources().getString(
192                            com.android.internal.R.string.display_manager_overlay_display_name,
193                            number);
194                    int gravity = chooseOverlayGravity(number);
195                    boolean secure = flagString != null && flagString.contains(",secure");
196
197                    Slog.i(TAG, "Showing overlay display device #" + number
198                            + ": name=" + name + ", modes=" + Arrays.toString(modes.toArray()));
199
200                    mOverlays.add(new OverlayDisplayHandle(name, modes, gravity, secure, number));
201                    continue;
202                }
203            }
204            Slog.w(TAG, "Malformed overlay display devices setting: " + value);
205        }
206    }
207
208    private static int chooseOverlayGravity(int overlayNumber) {
209        switch (overlayNumber) {
210            case 1:
211                return Gravity.TOP | Gravity.LEFT;
212            case 2:
213                return Gravity.BOTTOM | Gravity.RIGHT;
214            case 3:
215                return Gravity.TOP | Gravity.RIGHT;
216            case 4:
217            default:
218                return Gravity.BOTTOM | Gravity.LEFT;
219        }
220    }
221
222    private abstract class OverlayDisplayDevice extends DisplayDevice {
223        private final String mName;
224        private final float mRefreshRate;
225        private final long mDisplayPresentationDeadlineNanos;
226        private final boolean mSecure;
227        private final List<OverlayMode> mRawModes;
228        private final Display.Mode[] mModes;
229        private final int mDefaultMode;
230
231        private int mState;
232        private SurfaceTexture mSurfaceTexture;
233        private Surface mSurface;
234        private DisplayDeviceInfo mInfo;
235        private int mActiveMode;
236
237        public OverlayDisplayDevice(IBinder displayToken, String name,
238                List<OverlayMode> modes, int activeMode, int defaultMode,
239                float refreshRate, long presentationDeadlineNanos,
240                boolean secure, int state,
241                SurfaceTexture surfaceTexture, int number) {
242            super(OverlayDisplayAdapter.this, displayToken, UNIQUE_ID_PREFIX + number);
243            mName = name;
244            mRefreshRate = refreshRate;
245            mDisplayPresentationDeadlineNanos = presentationDeadlineNanos;
246            mSecure = secure;
247            mState = state;
248            mSurfaceTexture = surfaceTexture;
249            mRawModes = modes;
250            mModes = new Display.Mode[modes.size()];
251            for (int i = 0; i < modes.size(); i++) {
252                OverlayMode mode = modes.get(i);
253                mModes[i] = createMode(mode.mWidth, mode.mHeight, refreshRate);
254            }
255            mActiveMode = activeMode;
256            mDefaultMode = defaultMode;
257        }
258
259        public void destroyLocked() {
260            mSurfaceTexture = null;
261            if (mSurface != null) {
262                mSurface.release();
263                mSurface = null;
264            }
265            SurfaceControl.destroyDisplay(getDisplayTokenLocked());
266        }
267
268        @Override
269        public boolean hasStableUniqueId() {
270            return false;
271        }
272
273        @Override
274        public void performTraversalInTransactionLocked() {
275            if (mSurfaceTexture != null) {
276                if (mSurface == null) {
277                    mSurface = new Surface(mSurfaceTexture);
278                }
279                setSurfaceInTransactionLocked(mSurface);
280            }
281        }
282
283        public void setStateLocked(int state) {
284            mState = state;
285            mInfo = null;
286        }
287
288        @Override
289        public DisplayDeviceInfo getDisplayDeviceInfoLocked() {
290            if (mInfo == null) {
291                Display.Mode mode = mModes[mActiveMode];
292                OverlayMode rawMode = mRawModes.get(mActiveMode);
293                mInfo = new DisplayDeviceInfo();
294                mInfo.name = mName;
295                mInfo.uniqueId = getUniqueId();
296                mInfo.width = mode.getPhysicalWidth();
297                mInfo.height = mode.getPhysicalHeight();
298                mInfo.modeId = mode.getModeId();
299                mInfo.defaultModeId = mModes[0].getModeId();
300                mInfo.supportedModes = mModes;
301                mInfo.densityDpi = rawMode.mDensityDpi;
302                mInfo.xDpi = rawMode.mDensityDpi;
303                mInfo.yDpi = rawMode.mDensityDpi;
304                mInfo.presentationDeadlineNanos = mDisplayPresentationDeadlineNanos +
305                        1000000000L / (int) mRefreshRate;   // display's deadline + 1 frame
306                mInfo.flags = DisplayDeviceInfo.FLAG_PRESENTATION;
307                if (mSecure) {
308                    mInfo.flags |= DisplayDeviceInfo.FLAG_SECURE;
309                }
310                mInfo.type = Display.TYPE_OVERLAY;
311                mInfo.touch = DisplayDeviceInfo.TOUCH_NONE;
312                mInfo.state = mState;
313            }
314            return mInfo;
315        }
316
317        @Override
318        public void requestDisplayModesInTransactionLocked(int color, int id) {
319            int index = -1;
320            if (id == 0) {
321                // Use the default.
322                index = 0;
323            } else {
324                for (int i = 0; i < mModes.length; i++) {
325                    if (mModes[i].getModeId() == id) {
326                        index = i;
327                        break;
328                    }
329                }
330            }
331            if (index == -1) {
332                Slog.w(TAG, "Unable to locate mode " + id + ", reverting to default.");
333                index = mDefaultMode;
334            }
335            if (mActiveMode == index) {
336                return;
337            }
338            mActiveMode = index;
339            mInfo = null;
340            sendDisplayDeviceEventLocked(this, DISPLAY_DEVICE_EVENT_CHANGED);
341            onModeChangedLocked(index);
342        }
343
344        /**
345         * Called when the device switched to a new mode.
346         *
347         * @param index index of the mode in the list of modes
348         */
349        public abstract void onModeChangedLocked(int index);
350    }
351
352    /**
353     * Functions as a handle for overlay display devices which are created and
354     * destroyed asynchronously.
355     *
356     * Guarded by the {@link DisplayManagerService.SyncRoot} lock.
357     */
358    private final class OverlayDisplayHandle implements OverlayDisplayWindow.Listener {
359        private static final int DEFAULT_MODE_INDEX = 0;
360
361        private final String mName;
362        private final List<OverlayMode> mModes;
363        private final int mGravity;
364        private final boolean mSecure;
365        private final int mNumber;
366
367        private OverlayDisplayWindow mWindow;
368        private OverlayDisplayDevice mDevice;
369        private int mActiveMode;
370
371        public OverlayDisplayHandle(String name, List<OverlayMode> modes, int gravity,
372                boolean secure, int number) {
373            mName = name;
374            mModes = modes;
375            mGravity = gravity;
376            mSecure = secure;
377            mNumber = number;
378
379            mActiveMode = 0;
380
381            showLocked();
382        }
383
384        private void showLocked() {
385            mUiHandler.post(mShowRunnable);
386        }
387
388        public void dismissLocked() {
389            mUiHandler.removeCallbacks(mShowRunnable);
390            mUiHandler.post(mDismissRunnable);
391        }
392
393        private void onActiveModeChangedLocked(int index) {
394            mUiHandler.removeCallbacks(mResizeRunnable);
395            mActiveMode = index;
396            if (mWindow != null) {
397                mUiHandler.post(mResizeRunnable);
398            }
399        }
400
401        // Called on the UI thread.
402        @Override
403        public void onWindowCreated(SurfaceTexture surfaceTexture, float refreshRate,
404                long presentationDeadlineNanos, int state) {
405            synchronized (getSyncRoot()) {
406                IBinder displayToken = SurfaceControl.createDisplay(mName, mSecure);
407                mDevice = new OverlayDisplayDevice(displayToken, mName, mModes, mActiveMode,
408                        DEFAULT_MODE_INDEX, refreshRate, presentationDeadlineNanos,
409                        mSecure, state, surfaceTexture, mNumber) {
410                    @Override
411                    public void onModeChangedLocked(int index) {
412                        onActiveModeChangedLocked(index);
413                    }
414                };
415
416                sendDisplayDeviceEventLocked(mDevice, DISPLAY_DEVICE_EVENT_ADDED);
417            }
418        }
419
420        // Called on the UI thread.
421        @Override
422        public void onWindowDestroyed() {
423            synchronized (getSyncRoot()) {
424                if (mDevice != null) {
425                    mDevice.destroyLocked();
426                    sendDisplayDeviceEventLocked(mDevice, DISPLAY_DEVICE_EVENT_REMOVED);
427                }
428            }
429        }
430
431        // Called on the UI thread.
432        @Override
433        public void onStateChanged(int state) {
434            synchronized (getSyncRoot()) {
435                if (mDevice != null) {
436                    mDevice.setStateLocked(state);
437                    sendDisplayDeviceEventLocked(mDevice, DISPLAY_DEVICE_EVENT_CHANGED);
438                }
439            }
440        }
441
442        public void dumpLocked(PrintWriter pw) {
443            pw.println("  " + mName + ":");
444            pw.println("    mModes=" + Arrays.toString(mModes.toArray()));
445            pw.println("    mActiveMode=" + mActiveMode);
446            pw.println("    mGravity=" + mGravity);
447            pw.println("    mSecure=" + mSecure);
448            pw.println("    mNumber=" + mNumber);
449
450            // Try to dump the window state.
451            if (mWindow != null) {
452                final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "    ");
453                ipw.increaseIndent();
454                DumpUtils.dumpAsync(mUiHandler, mWindow, ipw, "", 200);
455            }
456        }
457
458        // Runs on the UI thread.
459        private final Runnable mShowRunnable = new Runnable() {
460            @Override
461            public void run() {
462                OverlayMode mode = mModes.get(mActiveMode);
463                OverlayDisplayWindow window = new OverlayDisplayWindow(getContext(),
464                        mName, mode.mWidth, mode.mHeight, mode.mDensityDpi, mGravity, mSecure,
465                        OverlayDisplayHandle.this);
466                window.show();
467
468                synchronized (getSyncRoot()) {
469                    mWindow = window;
470                }
471            }
472        };
473
474        // Runs on the UI thread.
475        private final Runnable mDismissRunnable = new Runnable() {
476            @Override
477            public void run() {
478                OverlayDisplayWindow window;
479                synchronized (getSyncRoot()) {
480                    window = mWindow;
481                    mWindow = null;
482                }
483
484                if (window != null) {
485                    window.dismiss();
486                }
487            }
488        };
489
490        // Runs on the UI thread.
491        private final Runnable mResizeRunnable = new Runnable() {
492            @Override
493            public void run() {
494                OverlayMode mode;
495                OverlayDisplayWindow window;
496                synchronized (getSyncRoot()) {
497                    if (mWindow == null) {
498                        return;
499                    }
500                    mode = mModes.get(mActiveMode);
501                    window = mWindow;
502                }
503                window.resize(mode.mWidth, mode.mHeight, mode.mDensityDpi);
504            }
505        };
506    }
507
508    /**
509     * A display mode for an overlay display.
510     */
511    private static final class OverlayMode {
512        final int mWidth;
513        final int mHeight;
514        final int mDensityDpi;
515
516        OverlayMode(int width, int height, int densityDpi) {
517            mWidth = width;
518            mHeight = height;
519            mDensityDpi = densityDpi;
520        }
521
522        @Override
523        public String toString() {
524            return new StringBuilder("{")
525                    .append("width=").append(mWidth)
526                    .append(", height=").append(mHeight)
527                    .append(", densityDpi=").append(mDensityDpi)
528                    .append("}")
529                    .toString();
530        }
531    }
532}
533