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 void performTraversalInTransactionLocked() {
270            if (mSurfaceTexture != null) {
271                if (mSurface == null) {
272                    mSurface = new Surface(mSurfaceTexture);
273                }
274                setSurfaceInTransactionLocked(mSurface);
275            }
276        }
277
278        public void setStateLocked(int state) {
279            mState = state;
280            mInfo = null;
281        }
282
283        @Override
284        public DisplayDeviceInfo getDisplayDeviceInfoLocked() {
285            if (mInfo == null) {
286                Display.Mode mode = mModes[mActiveMode];
287                OverlayMode rawMode = mRawModes.get(mActiveMode);
288                mInfo = new DisplayDeviceInfo();
289                mInfo.name = mName;
290                mInfo.uniqueId = getUniqueId();
291                mInfo.width = mode.getPhysicalWidth();
292                mInfo.height = mode.getPhysicalHeight();
293                mInfo.modeId = mode.getModeId();
294                mInfo.defaultModeId = mModes[0].getModeId();
295                mInfo.supportedModes = mModes;
296                mInfo.densityDpi = rawMode.mDensityDpi;
297                mInfo.xDpi = rawMode.mDensityDpi;
298                mInfo.yDpi = rawMode.mDensityDpi;
299                mInfo.presentationDeadlineNanos = mDisplayPresentationDeadlineNanos +
300                        1000000000L / (int) mRefreshRate;   // display's deadline + 1 frame
301                mInfo.flags = DisplayDeviceInfo.FLAG_PRESENTATION;
302                if (mSecure) {
303                    mInfo.flags |= DisplayDeviceInfo.FLAG_SECURE;
304                }
305                mInfo.type = Display.TYPE_OVERLAY;
306                mInfo.touch = DisplayDeviceInfo.TOUCH_NONE;
307                mInfo.state = mState;
308            }
309            return mInfo;
310        }
311
312        @Override
313        public void requestColorTransformAndModeInTransactionLocked(int color, int id) {
314            int index = -1;
315            if (id == 0) {
316                // Use the default.
317                index = 0;
318            } else {
319                for (int i = 0; i < mModes.length; i++) {
320                    if (mModes[i].getModeId() == id) {
321                        index = i;
322                        break;
323                    }
324                }
325            }
326            if (index == -1) {
327                Slog.w(TAG, "Unable to locate mode " + id + ", reverting to default.");
328                index = mDefaultMode;
329            }
330            if (mActiveMode == index) {
331                return;
332            }
333            mActiveMode = index;
334            mInfo = null;
335            sendDisplayDeviceEventLocked(this, DISPLAY_DEVICE_EVENT_CHANGED);
336            onModeChangedLocked(index);
337        }
338
339        /**
340         * Called when the device switched to a new mode.
341         *
342         * @param index index of the mode in the list of modes
343         */
344        public abstract void onModeChangedLocked(int index);
345    }
346
347    /**
348     * Functions as a handle for overlay display devices which are created and
349     * destroyed asynchronously.
350     *
351     * Guarded by the {@link DisplayManagerService.SyncRoot} lock.
352     */
353    private final class OverlayDisplayHandle implements OverlayDisplayWindow.Listener {
354        private static final int DEFAULT_MODE_INDEX = 0;
355
356        private final String mName;
357        private final List<OverlayMode> mModes;
358        private final int mGravity;
359        private final boolean mSecure;
360        private final int mNumber;
361
362        private OverlayDisplayWindow mWindow;
363        private OverlayDisplayDevice mDevice;
364        private int mActiveMode;
365
366        public OverlayDisplayHandle(String name, List<OverlayMode> modes, int gravity,
367                boolean secure, int number) {
368            mName = name;
369            mModes = modes;
370            mGravity = gravity;
371            mSecure = secure;
372            mNumber = number;
373
374            mActiveMode = 0;
375
376            showLocked();
377        }
378
379        private void showLocked() {
380            mUiHandler.post(mShowRunnable);
381        }
382
383        public void dismissLocked() {
384            mUiHandler.removeCallbacks(mShowRunnable);
385            mUiHandler.post(mDismissRunnable);
386        }
387
388        private void onActiveModeChangedLocked(int index) {
389            mUiHandler.removeCallbacks(mResizeRunnable);
390            mActiveMode = index;
391            if (mWindow != null) {
392                mUiHandler.post(mResizeRunnable);
393            }
394        }
395
396        // Called on the UI thread.
397        @Override
398        public void onWindowCreated(SurfaceTexture surfaceTexture, float refreshRate,
399                long presentationDeadlineNanos, int state) {
400            synchronized (getSyncRoot()) {
401                IBinder displayToken = SurfaceControl.createDisplay(mName, mSecure);
402                mDevice = new OverlayDisplayDevice(displayToken, mName, mModes, mActiveMode,
403                        DEFAULT_MODE_INDEX, refreshRate, presentationDeadlineNanos,
404                        mSecure, state, surfaceTexture, mNumber) {
405                    @Override
406                    public void onModeChangedLocked(int index) {
407                        onActiveModeChangedLocked(index);
408                    }
409                };
410
411                sendDisplayDeviceEventLocked(mDevice, DISPLAY_DEVICE_EVENT_ADDED);
412            }
413        }
414
415        // Called on the UI thread.
416        @Override
417        public void onWindowDestroyed() {
418            synchronized (getSyncRoot()) {
419                if (mDevice != null) {
420                    mDevice.destroyLocked();
421                    sendDisplayDeviceEventLocked(mDevice, DISPLAY_DEVICE_EVENT_REMOVED);
422                }
423            }
424        }
425
426        // Called on the UI thread.
427        @Override
428        public void onStateChanged(int state) {
429            synchronized (getSyncRoot()) {
430                if (mDevice != null) {
431                    mDevice.setStateLocked(state);
432                    sendDisplayDeviceEventLocked(mDevice, DISPLAY_DEVICE_EVENT_CHANGED);
433                }
434            }
435        }
436
437        public void dumpLocked(PrintWriter pw) {
438            pw.println("  " + mName + ":");
439            pw.println("    mModes=" + Arrays.toString(mModes.toArray()));
440            pw.println("    mActiveMode=" + mActiveMode);
441            pw.println("    mGravity=" + mGravity);
442            pw.println("    mSecure=" + mSecure);
443            pw.println("    mNumber=" + mNumber);
444
445            // Try to dump the window state.
446            if (mWindow != null) {
447                final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "    ");
448                ipw.increaseIndent();
449                DumpUtils.dumpAsync(mUiHandler, mWindow, ipw, "", 200);
450            }
451        }
452
453        // Runs on the UI thread.
454        private final Runnable mShowRunnable = new Runnable() {
455            @Override
456            public void run() {
457                OverlayMode mode = mModes.get(mActiveMode);
458                OverlayDisplayWindow window = new OverlayDisplayWindow(getContext(),
459                        mName, mode.mWidth, mode.mHeight, mode.mDensityDpi, mGravity, mSecure,
460                        OverlayDisplayHandle.this);
461                window.show();
462
463                synchronized (getSyncRoot()) {
464                    mWindow = window;
465                }
466            }
467        };
468
469        // Runs on the UI thread.
470        private final Runnable mDismissRunnable = new Runnable() {
471            @Override
472            public void run() {
473                OverlayDisplayWindow window;
474                synchronized (getSyncRoot()) {
475                    window = mWindow;
476                    mWindow = null;
477                }
478
479                if (window != null) {
480                    window.dismiss();
481                }
482            }
483        };
484
485        // Runs on the UI thread.
486        private final Runnable mResizeRunnable = new Runnable() {
487            @Override
488            public void run() {
489                OverlayMode mode;
490                OverlayDisplayWindow window;
491                synchronized (getSyncRoot()) {
492                    if (mWindow == null) {
493                        return;
494                    }
495                    mode = mModes.get(mActiveMode);
496                    window = mWindow;
497                }
498                window.resize(mode.mWidth, mode.mHeight, mode.mDensityDpi);
499            }
500        };
501    }
502
503    /**
504     * A display mode for an overlay display.
505     */
506    private static final class OverlayMode {
507        final int mWidth;
508        final int mHeight;
509        final int mDensityDpi;
510
511        OverlayMode(int width, int height, int densityDpi) {
512            mWidth = width;
513            mHeight = height;
514            mDensityDpi = densityDpi;
515        }
516
517        @Override
518        public String toString() {
519            return new StringBuilder("{")
520                    .append("width=").append(mWidth)
521                    .append(", height=").append(mHeight)
522                    .append(", densityDpi=").append(mDensityDpi)
523                    .append("}")
524                    .toString();
525        }
526    }
527}
528