OverlayDisplayAdapter.java revision 4ed8fe75e1dde1a2b9576f3862aecc5a572c56b5
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 android.content.Context;
20import android.database.ContentObserver;
21import android.graphics.SurfaceTexture;
22import android.os.Handler;
23import android.os.IBinder;
24import android.provider.Settings;
25import android.util.DisplayMetrics;
26import android.util.Slog;
27import android.view.Gravity;
28import android.view.Surface;
29
30import java.io.PrintWriter;
31import java.io.StringWriter;
32import java.util.ArrayList;
33import java.util.regex.Matcher;
34import java.util.regex.Pattern;
35
36/**
37 * A display adapter that uses overlay windows to simulate secondary displays
38 * for development purposes.  Use Development Settings to enable one or more
39 * overlay displays.
40 * <p>
41 * This object has two different handlers (which may be the same) which must not
42 * get confused.  The main handler is used to posting messages to the display manager
43 * service as usual.  The UI handler is only used by the {@link OverlayDisplayWindow}.
44 * </p><p>
45 * Display adapters are guarded by the {@link DisplayManagerService.SyncRoot} lock.
46 * </p>
47 */
48final class OverlayDisplayAdapter extends DisplayAdapter {
49    static final String TAG = "OverlayDisplayAdapter";
50    static final boolean DEBUG = false;
51
52    private static final int MIN_WIDTH = 100;
53    private static final int MIN_HEIGHT = 100;
54    private static final int MAX_WIDTH = 4096;
55    private static final int MAX_HEIGHT = 4096;
56
57    private static final Pattern SETTING_PATTERN =
58            Pattern.compile("(\\d+)x(\\d+)/(\\d+)");
59
60    private final Handler mUiHandler;
61    private final ArrayList<OverlayDisplayHandle> mOverlays =
62            new ArrayList<OverlayDisplayHandle>();
63    private String mCurrentOverlaySetting = "";
64
65    public OverlayDisplayAdapter(DisplayManagerService.SyncRoot syncRoot,
66            Context context, Handler handler, Listener listener, Handler uiHandler) {
67        super(syncRoot, context, handler, listener, TAG);
68        mUiHandler = uiHandler;
69    }
70
71    @Override
72    public void dumpLocked(PrintWriter pw) {
73        super.dumpLocked(pw);
74        pw.println("mCurrentOverlaySetting=" + mCurrentOverlaySetting);
75        pw.println("mOverlays: size=" + mOverlays.size());
76        for (OverlayDisplayHandle overlay : mOverlays) {
77            overlay.dumpLocked(pw);
78        }
79    }
80
81    @Override
82    public void registerLocked() {
83        super.registerLocked();
84        getContext().getContentResolver().registerContentObserver(
85                Settings.System.getUriFor(Settings.Secure.OVERLAY_DISPLAY_DEVICES), true,
86                new ContentObserver(getHandler()) {
87                    @Override
88                    public void onChange(boolean selfChange) {
89                        synchronized (getSyncRoot()) {
90                            updateOverlayDisplayDevicesLocked();
91                        }
92                    }
93                });
94        updateOverlayDisplayDevicesLocked();
95    }
96
97    private void updateOverlayDisplayDevicesLocked() {
98        String value = Settings.System.getString(getContext().getContentResolver(),
99                Settings.Secure.OVERLAY_DISPLAY_DEVICES);
100        if (value == null) {
101            value = "";
102        }
103
104        if (value.equals(mCurrentOverlaySetting)) {
105            return;
106        }
107        mCurrentOverlaySetting = value;
108
109        if (!mOverlays.isEmpty()) {
110            Slog.i(TAG, "Dismissing all overlay display devices.");
111            for (OverlayDisplayHandle overlay : mOverlays) {
112                overlay.dismissLocked();
113            }
114            mOverlays.clear();
115        }
116
117        int count = 0;
118        for (String part : value.split(";")) {
119            Matcher matcher = SETTING_PATTERN.matcher(part);
120            if (matcher.matches()) {
121                if (count >= 4) {
122                    Slog.w(TAG, "Too many overlay display devices specified: " + value);
123                    break;
124                }
125                try {
126                    int width = Integer.parseInt(matcher.group(1), 10);
127                    int height = Integer.parseInt(matcher.group(2), 10);
128                    int densityDpi = Integer.parseInt(matcher.group(3), 10);
129                    if (width >= MIN_WIDTH && width <= MAX_WIDTH
130                            && height >= MIN_HEIGHT && height <= MAX_HEIGHT
131                            && densityDpi >= DisplayMetrics.DENSITY_LOW
132                            && densityDpi <= DisplayMetrics.DENSITY_XXHIGH) {
133                        int number = ++count;
134                        String name = getContext().getResources().getString(
135                                com.android.internal.R.string.display_manager_overlay_display_name,
136                                number);
137                        int gravity = chooseOverlayGravity(number);
138
139                        Slog.i(TAG, "Showing overlay display device #" + number
140                                + ": name=" + name + ", width=" + width + ", height=" + height
141                                + ", densityDpi=" + densityDpi);
142
143                        mOverlays.add(new OverlayDisplayHandle(name,
144                                width, height, densityDpi, gravity));
145                        continue;
146                    }
147                } catch (NumberFormatException ex) {
148                }
149            } else if (part.isEmpty()) {
150                continue;
151            }
152            Slog.w(TAG, "Malformed overlay display devices setting: " + value);
153        }
154    }
155
156    private static int chooseOverlayGravity(int overlayNumber) {
157        switch (overlayNumber) {
158            case 1:
159                return Gravity.TOP | Gravity.LEFT;
160            case 2:
161                return Gravity.BOTTOM | Gravity.RIGHT;
162            case 3:
163                return Gravity.TOP | Gravity.RIGHT;
164            case 4:
165            default:
166                return Gravity.BOTTOM | Gravity.LEFT;
167        }
168    }
169
170    private final class OverlayDisplayDevice extends DisplayDevice {
171        private final String mName;
172        private final int mWidth;
173        private final int mHeight;
174        private final float mRefreshRate;
175        private final int mDensityDpi;
176
177        private SurfaceTexture mSurfaceTexture;
178        private boolean mSurfaceTextureChanged;
179
180        private DisplayDeviceInfo mInfo;
181
182        public OverlayDisplayDevice(IBinder displayToken, String name,
183                int width, int height, float refreshRate, int densityDpi) {
184            super(OverlayDisplayAdapter.this, displayToken);
185            mName = name;
186            mWidth = width;
187            mHeight = height;
188            mRefreshRate = refreshRate;
189            mDensityDpi = densityDpi;
190        }
191
192        public void setSurfaceTextureLocked(SurfaceTexture surfaceTexture) {
193            if (surfaceTexture != mSurfaceTexture) {
194                mSurfaceTexture = surfaceTexture;
195                mSurfaceTextureChanged = true;
196                sendTraversalRequestLocked();
197            }
198        }
199
200        @Override
201        public void performTraversalInTransactionLocked() {
202            if (mSurfaceTextureChanged) {
203                setSurfaceTextureInTransactionLocked(mSurfaceTexture);
204                mSurfaceTextureChanged = false;
205            }
206        }
207
208        @Override
209        public DisplayDeviceInfo getDisplayDeviceInfoLocked() {
210            if (mInfo == null) {
211                mInfo = new DisplayDeviceInfo();
212                mInfo.name = mName;
213                mInfo.width = mWidth;
214                mInfo.height = mHeight;
215                mInfo.refreshRate = mRefreshRate;
216                mInfo.densityDpi = mDensityDpi;
217                mInfo.xDpi = mDensityDpi;
218                mInfo.yDpi = mDensityDpi;
219                mInfo.flags = DisplayDeviceInfo.FLAG_SECURE;
220            }
221            return mInfo;
222        }
223    }
224
225    /**
226     * Functions as a handle for overlay display devices which are created and
227     * destroyed asynchronously.
228     *
229     * Guarded by the {@link DisplayManagerService.SyncRoot} lock.
230     */
231    private final class OverlayDisplayHandle implements OverlayDisplayWindow.Listener {
232        private final String mName;
233        private final int mWidth;
234        private final int mHeight;
235        private final int mDensityDpi;
236        private final int mGravity;
237
238        private OverlayDisplayWindow mWindow;
239        private OverlayDisplayDevice mDevice;
240
241        public OverlayDisplayHandle(String name,
242                int width, int height, int densityDpi, int gravity) {
243            mName = name;
244            mWidth = width;
245            mHeight = height;
246            mDensityDpi = densityDpi;
247            mGravity = gravity;
248
249            mUiHandler.post(mShowRunnable);
250        }
251
252        public void dismissLocked() {
253            mUiHandler.removeCallbacks(mShowRunnable);
254            mUiHandler.post(mDismissRunnable);
255        }
256
257        // Called on the UI thread.
258        @Override
259        public void onWindowCreated(SurfaceTexture surfaceTexture, float refreshRate) {
260            synchronized (getSyncRoot()) {
261                IBinder displayToken = Surface.createDisplay(mName);
262                mDevice = new OverlayDisplayDevice(displayToken, mName,
263                        mWidth, mHeight, refreshRate, mDensityDpi);
264                mDevice.setSurfaceTextureLocked(surfaceTexture);
265
266                sendDisplayDeviceEventLocked(mDevice, DISPLAY_DEVICE_EVENT_ADDED);
267            }
268        }
269
270        // Called on the UI thread.
271        @Override
272        public void onWindowDestroyed() {
273            synchronized (getSyncRoot()) {
274                if (mDevice != null) {
275                    mDevice.setSurfaceTextureLocked(null);
276
277                    sendDisplayDeviceEventLocked(mDevice, DISPLAY_DEVICE_EVENT_REMOVED);
278                }
279            }
280        }
281
282        public void dumpLocked(PrintWriter pw) {
283            pw.println("  " + mName + ": ");
284            pw.println("    mWidth=" + mWidth);
285            pw.println("    mHeight=" + mHeight);
286            pw.println("    mDensityDpi=" + mDensityDpi);
287            pw.println("    mGravity=" + mGravity);
288
289            // Try to dump the window state.
290            // This call may hang if the UI thread is waiting to acquire our lock so
291            // we use a short timeout to recover just in case.
292            if (mWindow != null) {
293                final StringWriter sw = new StringWriter();
294                final OverlayDisplayWindow window = mWindow;
295                if (mUiHandler.runWithScissors(new Runnable() {
296                    @Override
297                    public void run() {
298                        PrintWriter lpw = new PrintWriter(sw);
299                        window.dump(lpw);
300                        lpw.close();
301                    }
302                }, 200)) {
303                    for (String line : sw.toString().split("\n")) {
304                        pw.println(line);
305                    }
306                } else {
307                    pw.println("    ... timed out while attempting to dump window state");
308                }
309            }
310        }
311
312        // Runs on the UI thread.
313        private final Runnable mShowRunnable = new Runnable() {
314            @Override
315            public void run() {
316                OverlayDisplayWindow window = new OverlayDisplayWindow(getContext(),
317                        mName, mWidth, mHeight, mDensityDpi, mGravity,
318                        OverlayDisplayHandle.this);
319                window.show();
320
321                synchronized (getSyncRoot()) {
322                    mWindow = window;
323                }
324            }
325        };
326
327        // Runs on the UI thread.
328        private final Runnable mDismissRunnable = new Runnable() {
329            @Override
330            public void run() {
331                OverlayDisplayWindow window;
332                synchronized (getSyncRoot()) {
333                    window = mWindow;
334                    mWindow = null;
335                }
336
337                if (window != null) {
338                    window.dismiss();
339                }
340            }
341        };
342    }
343}
344