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