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.regex.Matcher;
38import java.util.regex.Pattern;
39
40/**
41 * A display adapter that uses overlay windows to simulate secondary displays
42 * for development purposes.  Use Development Settings to enable one or more
43 * overlay displays.
44 * <p>
45 * This object has two different handlers (which may be the same) which must not
46 * get confused.  The main handler is used to posting messages to the display manager
47 * service as usual.  The UI handler is only used by the {@link OverlayDisplayWindow}.
48 * </p><p>
49 * Display adapters are guarded by the {@link DisplayManagerService.SyncRoot} lock.
50 * </p>
51 */
52final class OverlayDisplayAdapter extends DisplayAdapter {
53    static final String TAG = "OverlayDisplayAdapter";
54    static final boolean DEBUG = false;
55
56    private static final int MIN_WIDTH = 100;
57    private static final int MIN_HEIGHT = 100;
58    private static final int MAX_WIDTH = 4096;
59    private static final int MAX_HEIGHT = 4096;
60
61    private static final Pattern SETTING_PATTERN =
62            Pattern.compile("(\\d+)x(\\d+)/(\\d+)(,[a-z]+)*");
63
64    // Unique id prefix for overlay displays.
65    private static final String UNIQUE_ID_PREFIX = "overlay:";
66
67    private final Handler mUiHandler;
68    private final ArrayList<OverlayDisplayHandle> mOverlays =
69            new ArrayList<OverlayDisplayHandle>();
70    private String mCurrentOverlaySetting = "";
71
72    // Called with SyncRoot lock held.
73    public OverlayDisplayAdapter(DisplayManagerService.SyncRoot syncRoot,
74            Context context, Handler handler, Listener listener, Handler uiHandler) {
75        super(syncRoot, context, handler, listener, TAG);
76        mUiHandler = uiHandler;
77    }
78
79    @Override
80    public void dumpLocked(PrintWriter pw) {
81        super.dumpLocked(pw);
82
83        pw.println("mCurrentOverlaySetting=" + mCurrentOverlaySetting);
84        pw.println("mOverlays: size=" + mOverlays.size());
85        for (OverlayDisplayHandle overlay : mOverlays) {
86            overlay.dumpLocked(pw);
87        }
88    }
89
90    @Override
91    public void registerLocked() {
92        super.registerLocked();
93
94        getHandler().post(new Runnable() {
95            @Override
96            public void run() {
97                getContext().getContentResolver().registerContentObserver(
98                        Settings.Global.getUriFor(Settings.Global.OVERLAY_DISPLAY_DEVICES),
99                        true, new ContentObserver(getHandler()) {
100                            @Override
101                            public void onChange(boolean selfChange) {
102                                updateOverlayDisplayDevices();
103                            }
104                        });
105
106                updateOverlayDisplayDevices();
107            }
108        });
109    }
110
111    private void updateOverlayDisplayDevices() {
112        synchronized (getSyncRoot()) {
113            updateOverlayDisplayDevicesLocked();
114        }
115    }
116
117    private void updateOverlayDisplayDevicesLocked() {
118        String value = Settings.Global.getString(getContext().getContentResolver(),
119                Settings.Global.OVERLAY_DISPLAY_DEVICES);
120        if (value == null) {
121            value = "";
122        }
123
124        if (value.equals(mCurrentOverlaySetting)) {
125            return;
126        }
127        mCurrentOverlaySetting = value;
128
129        if (!mOverlays.isEmpty()) {
130            Slog.i(TAG, "Dismissing all overlay display devices.");
131            for (OverlayDisplayHandle overlay : mOverlays) {
132                overlay.dismissLocked();
133            }
134            mOverlays.clear();
135        }
136
137        int count = 0;
138        for (String part : value.split(";")) {
139            Matcher matcher = SETTING_PATTERN.matcher(part);
140            if (matcher.matches()) {
141                if (count >= 4) {
142                    Slog.w(TAG, "Too many overlay display devices specified: " + value);
143                    break;
144                }
145                try {
146                    int width = Integer.parseInt(matcher.group(1), 10);
147                    int height = Integer.parseInt(matcher.group(2), 10);
148                    int densityDpi = Integer.parseInt(matcher.group(3), 10);
149                    String flagString = matcher.group(4);
150                    if (width >= MIN_WIDTH && width <= MAX_WIDTH
151                            && height >= MIN_HEIGHT && height <= MAX_HEIGHT
152                            && densityDpi >= DisplayMetrics.DENSITY_LOW
153                            && densityDpi <= DisplayMetrics.DENSITY_XXHIGH) {
154                        int number = ++count;
155                        String name = getContext().getResources().getString(
156                                com.android.internal.R.string.display_manager_overlay_display_name,
157                                number);
158                        int gravity = chooseOverlayGravity(number);
159                        boolean secure = flagString != null && flagString.contains(",secure");
160
161                        Slog.i(TAG, "Showing overlay display device #" + number
162                                + ": name=" + name + ", width=" + width + ", height=" + height
163                                + ", densityDpi=" + densityDpi + ", secure=" + secure);
164
165                        mOverlays.add(new OverlayDisplayHandle(name,
166                                width, height, densityDpi, gravity, secure, number));
167                        continue;
168                    }
169                } catch (NumberFormatException ex) {
170                }
171            } else if (part.isEmpty()) {
172                continue;
173            }
174            Slog.w(TAG, "Malformed overlay display devices setting: " + value);
175        }
176    }
177
178    private static int chooseOverlayGravity(int overlayNumber) {
179        switch (overlayNumber) {
180            case 1:
181                return Gravity.TOP | Gravity.LEFT;
182            case 2:
183                return Gravity.BOTTOM | Gravity.RIGHT;
184            case 3:
185                return Gravity.TOP | Gravity.RIGHT;
186            case 4:
187            default:
188                return Gravity.BOTTOM | Gravity.LEFT;
189        }
190    }
191
192    private final class OverlayDisplayDevice extends DisplayDevice {
193        private final String mName;
194        private final int mWidth;
195        private final int mHeight;
196        private final float mRefreshRate;
197        private final long mDisplayPresentationDeadlineNanos;
198        private final int mDensityDpi;
199        private final boolean mSecure;
200
201        private int mState;
202        private SurfaceTexture mSurfaceTexture;
203        private Surface mSurface;
204        private DisplayDeviceInfo mInfo;
205
206        public OverlayDisplayDevice(IBinder displayToken, String name,
207                int width, int height, float refreshRate, long presentationDeadlineNanos,
208                int densityDpi, boolean secure, int state,
209                SurfaceTexture surfaceTexture, int number) {
210            super(OverlayDisplayAdapter.this, displayToken, UNIQUE_ID_PREFIX + number);
211            mName = name;
212            mWidth = width;
213            mHeight = height;
214            mRefreshRate = refreshRate;
215            mDisplayPresentationDeadlineNanos = presentationDeadlineNanos;
216            mDensityDpi = densityDpi;
217            mSecure = secure;
218            mState = state;
219            mSurfaceTexture = surfaceTexture;
220        }
221
222        public void destroyLocked() {
223            mSurfaceTexture = null;
224            if (mSurface != null) {
225                mSurface.release();
226                mSurface = null;
227            }
228            SurfaceControl.destroyDisplay(getDisplayTokenLocked());
229        }
230
231        @Override
232        public void performTraversalInTransactionLocked() {
233            if (mSurfaceTexture != null) {
234                if (mSurface == null) {
235                    mSurface = new Surface(mSurfaceTexture);
236                }
237                setSurfaceInTransactionLocked(mSurface);
238            }
239        }
240
241        public void setStateLocked(int state) {
242            mState = state;
243            mInfo = null;
244        }
245
246        @Override
247        public DisplayDeviceInfo getDisplayDeviceInfoLocked() {
248            if (mInfo == null) {
249                mInfo = new DisplayDeviceInfo();
250                mInfo.name = mName;
251                mInfo.uniqueId = getUniqueId();
252                mInfo.width = mWidth;
253                mInfo.height = mHeight;
254                mInfo.refreshRate = mRefreshRate;
255                mInfo.supportedRefreshRates = new float[] { mRefreshRate };
256                mInfo.densityDpi = mDensityDpi;
257                mInfo.xDpi = mDensityDpi;
258                mInfo.yDpi = mDensityDpi;
259                mInfo.presentationDeadlineNanos = mDisplayPresentationDeadlineNanos +
260                        1000000000L / (int) mRefreshRate;   // display's deadline + 1 frame
261                mInfo.flags = DisplayDeviceInfo.FLAG_PRESENTATION;
262                if (mSecure) {
263                    mInfo.flags |= DisplayDeviceInfo.FLAG_SECURE;
264                }
265                mInfo.type = Display.TYPE_OVERLAY;
266                mInfo.touch = DisplayDeviceInfo.TOUCH_NONE;
267                mInfo.state = mState;
268            }
269            return mInfo;
270        }
271    }
272
273    /**
274     * Functions as a handle for overlay display devices which are created and
275     * destroyed asynchronously.
276     *
277     * Guarded by the {@link DisplayManagerService.SyncRoot} lock.
278     */
279    private final class OverlayDisplayHandle implements OverlayDisplayWindow.Listener {
280        private final String mName;
281        private final int mWidth;
282        private final int mHeight;
283        private final int mDensityDpi;
284        private final int mGravity;
285        private final boolean mSecure;
286        private final int mNumber;
287
288        private OverlayDisplayWindow mWindow;
289        private OverlayDisplayDevice mDevice;
290
291        public OverlayDisplayHandle(String name, int width,
292                int height, int densityDpi, int gravity, boolean secure, int number) {
293            mName = name;
294            mWidth = width;
295            mHeight = height;
296            mDensityDpi = densityDpi;
297            mGravity = gravity;
298            mSecure = secure;
299            mNumber = number;
300
301            mUiHandler.post(mShowRunnable);
302        }
303
304        public void dismissLocked() {
305            mUiHandler.removeCallbacks(mShowRunnable);
306            mUiHandler.post(mDismissRunnable);
307        }
308
309        // Called on the UI thread.
310        @Override
311        public void onWindowCreated(SurfaceTexture surfaceTexture, float refreshRate,
312                long presentationDeadlineNanos, int state) {
313            synchronized (getSyncRoot()) {
314                IBinder displayToken = SurfaceControl.createDisplay(mName, mSecure);
315                mDevice = new OverlayDisplayDevice(displayToken, mName,
316                        mWidth, mHeight, refreshRate, presentationDeadlineNanos,
317                        mDensityDpi, mSecure, state, surfaceTexture, mNumber);
318
319                sendDisplayDeviceEventLocked(mDevice, DISPLAY_DEVICE_EVENT_ADDED);
320            }
321        }
322
323        // Called on the UI thread.
324        @Override
325        public void onWindowDestroyed() {
326            synchronized (getSyncRoot()) {
327                if (mDevice != null) {
328                    mDevice.destroyLocked();
329                    sendDisplayDeviceEventLocked(mDevice, DISPLAY_DEVICE_EVENT_REMOVED);
330                }
331            }
332        }
333
334        // Called on the UI thread.
335        @Override
336        public void onStateChanged(int state) {
337            synchronized (getSyncRoot()) {
338                if (mDevice != null) {
339                    mDevice.setStateLocked(state);
340                    sendDisplayDeviceEventLocked(mDevice, DISPLAY_DEVICE_EVENT_CHANGED);
341                }
342            }
343        }
344
345        public void dumpLocked(PrintWriter pw) {
346            pw.println("  " + mName + ":");
347            pw.println("    mWidth=" + mWidth);
348            pw.println("    mHeight=" + mHeight);
349            pw.println("    mDensityDpi=" + mDensityDpi);
350            pw.println("    mGravity=" + mGravity);
351            pw.println("    mSecure=" + mSecure);
352            pw.println("    mNumber=" + mNumber);
353
354            // Try to dump the window state.
355            if (mWindow != null) {
356                final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "    ");
357                ipw.increaseIndent();
358                DumpUtils.dumpAsync(mUiHandler, mWindow, ipw, 200);
359            }
360        }
361
362        // Runs on the UI thread.
363        private final Runnable mShowRunnable = new Runnable() {
364            @Override
365            public void run() {
366                OverlayDisplayWindow window = new OverlayDisplayWindow(getContext(),
367                        mName, mWidth, mHeight, mDensityDpi, mGravity, mSecure,
368                        OverlayDisplayHandle.this);
369                window.show();
370
371                synchronized (getSyncRoot()) {
372                    mWindow = window;
373                }
374            }
375        };
376
377        // Runs on the UI thread.
378        private final Runnable mDismissRunnable = new Runnable() {
379            @Override
380            public void run() {
381                OverlayDisplayWindow window;
382                synchronized (getSyncRoot()) {
383                    window = mWindow;
384                    mWindow = null;
385                }
386
387                if (window != null) {
388                    window.dismiss();
389                }
390            }
391        };
392    }
393}
394