OverlayDisplayAdapter.java revision 77aebfdbae489c3712ae3f9bca29d01fb1f09dc2
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.Global.getUriFor(Settings.Global.OVERLAY_DISPLAY_DEVICES),
92                        true, new ContentObserver(getHandler()) {
93                            @Override
94                            public void onChange(boolean selfChange) {
95                                updateOverlayDisplayDevices();
96                            }
97                        });
98
99                updateOverlayDisplayDevices();
100            }
101        });
102    }
103
104    private void updateOverlayDisplayDevices() {
105        synchronized (getSyncRoot()) {
106            updateOverlayDisplayDevicesLocked();
107        }
108    }
109
110    private void updateOverlayDisplayDevicesLocked() {
111        String value = Settings.Global.getString(getContext().getContentResolver(),
112                Settings.Global.OVERLAY_DISPLAY_DEVICES);
113        if (value == null) {
114            value = "";
115        }
116
117        if (value.equals(mCurrentOverlaySetting)) {
118            return;
119        }
120        mCurrentOverlaySetting = value;
121
122        if (!mOverlays.isEmpty()) {
123            Slog.i(TAG, "Dismissing all overlay display devices.");
124            for (OverlayDisplayHandle overlay : mOverlays) {
125                overlay.dismissLocked();
126            }
127            mOverlays.clear();
128        }
129
130        int count = 0;
131        for (String part : value.split(";")) {
132            Matcher matcher = SETTING_PATTERN.matcher(part);
133            if (matcher.matches()) {
134                if (count >= 4) {
135                    Slog.w(TAG, "Too many overlay display devices specified: " + value);
136                    break;
137                }
138                try {
139                    int width = Integer.parseInt(matcher.group(1), 10);
140                    int height = Integer.parseInt(matcher.group(2), 10);
141                    int densityDpi = Integer.parseInt(matcher.group(3), 10);
142                    if (width >= MIN_WIDTH && width <= MAX_WIDTH
143                            && height >= MIN_HEIGHT && height <= MAX_HEIGHT
144                            && densityDpi >= DisplayMetrics.DENSITY_LOW
145                            && densityDpi <= DisplayMetrics.DENSITY_XXHIGH) {
146                        int number = ++count;
147                        String name = getContext().getResources().getString(
148                                com.android.internal.R.string.display_manager_overlay_display_name,
149                                number);
150                        int gravity = chooseOverlayGravity(number);
151
152                        Slog.i(TAG, "Showing overlay display device #" + number
153                                + ": name=" + name + ", width=" + width + ", height=" + height
154                                + ", densityDpi=" + densityDpi);
155
156                        mOverlays.add(new OverlayDisplayHandle(name,
157                                width, height, densityDpi, gravity));
158                        continue;
159                    }
160                } catch (NumberFormatException ex) {
161                }
162            } else if (part.isEmpty()) {
163                continue;
164            }
165            Slog.w(TAG, "Malformed overlay display devices setting: " + value);
166        }
167    }
168
169    private static int chooseOverlayGravity(int overlayNumber) {
170        switch (overlayNumber) {
171            case 1:
172                return Gravity.TOP | Gravity.LEFT;
173            case 2:
174                return Gravity.BOTTOM | Gravity.RIGHT;
175            case 3:
176                return Gravity.TOP | Gravity.RIGHT;
177            case 4:
178            default:
179                return Gravity.BOTTOM | Gravity.LEFT;
180        }
181    }
182
183    private final class OverlayDisplayDevice extends DisplayDevice {
184        private final String mName;
185        private final int mWidth;
186        private final int mHeight;
187        private final float mRefreshRate;
188        private final int mDensityDpi;
189
190        private Surface mSurface;
191        private DisplayDeviceInfo mInfo;
192
193        public OverlayDisplayDevice(IBinder displayToken, String name,
194                int width, int height, float refreshRate, int densityDpi,
195                Surface surface) {
196            super(OverlayDisplayAdapter.this, displayToken);
197            mName = name;
198            mWidth = width;
199            mHeight = height;
200            mRefreshRate = refreshRate;
201            mDensityDpi = densityDpi;
202            mSurface = surface;
203        }
204
205        public void clearSurfaceLocked() {
206            mSurface = null;
207            sendTraversalRequestLocked();
208        }
209
210        @Override
211        public void performTraversalInTransactionLocked() {
212            setSurfaceInTransactionLocked(mSurface);
213        }
214
215        @Override
216        public DisplayDeviceInfo getDisplayDeviceInfoLocked() {
217            if (mInfo == null) {
218                mInfo = new DisplayDeviceInfo();
219                mInfo.name = mName;
220                mInfo.width = mWidth;
221                mInfo.height = mHeight;
222                mInfo.refreshRate = mRefreshRate;
223                mInfo.densityDpi = mDensityDpi;
224                mInfo.xDpi = mDensityDpi;
225                mInfo.yDpi = mDensityDpi;
226                mInfo.flags = 0;
227                mInfo.touch = DisplayDeviceInfo.TOUCH_NONE;
228            }
229            return mInfo;
230        }
231    }
232
233    /**
234     * Functions as a handle for overlay display devices which are created and
235     * destroyed asynchronously.
236     *
237     * Guarded by the {@link DisplayManagerService.SyncRoot} lock.
238     */
239    private final class OverlayDisplayHandle implements OverlayDisplayWindow.Listener {
240        private final String mName;
241        private final int mWidth;
242        private final int mHeight;
243        private final int mDensityDpi;
244        private final int mGravity;
245
246        private OverlayDisplayWindow mWindow;
247        private OverlayDisplayDevice mDevice;
248
249        public OverlayDisplayHandle(String name,
250                int width, int height, int densityDpi, int gravity) {
251            mName = name;
252            mWidth = width;
253            mHeight = height;
254            mDensityDpi = densityDpi;
255            mGravity = gravity;
256
257            mUiHandler.post(mShowRunnable);
258        }
259
260        public void dismissLocked() {
261            mUiHandler.removeCallbacks(mShowRunnable);
262            mUiHandler.post(mDismissRunnable);
263        }
264
265        // Called on the UI thread.
266        @Override
267        public void onWindowCreated(Surface surface, float refreshRate) {
268            synchronized (getSyncRoot()) {
269                IBinder displayToken = Surface.createDisplay(mName);
270                mDevice = new OverlayDisplayDevice(displayToken, mName,
271                        mWidth, mHeight, refreshRate, mDensityDpi, surface);
272
273                sendDisplayDeviceEventLocked(mDevice, DISPLAY_DEVICE_EVENT_ADDED);
274            }
275        }
276
277        // Called on the UI thread.
278        @Override
279        public void onWindowDestroyed() {
280            synchronized (getSyncRoot()) {
281                if (mDevice != null) {
282                    mDevice.clearSurfaceLocked();
283                    sendDisplayDeviceEventLocked(mDevice, DISPLAY_DEVICE_EVENT_REMOVED);
284                }
285            }
286        }
287
288        public void dumpLocked(PrintWriter pw) {
289            pw.println("  " + mName + ":");
290            pw.println("    mWidth=" + mWidth);
291            pw.println("    mHeight=" + mHeight);
292            pw.println("    mDensityDpi=" + mDensityDpi);
293            pw.println("    mGravity=" + mGravity);
294
295            // Try to dump the window state.
296            if (mWindow != null) {
297                final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "    ");
298                ipw.increaseIndent();
299                DumpUtils.dumpAsync(mUiHandler, mWindow, ipw, 200);
300            }
301        }
302
303        // Runs on the UI thread.
304        private final Runnable mShowRunnable = new Runnable() {
305            @Override
306            public void run() {
307                OverlayDisplayWindow window = new OverlayDisplayWindow(getContext(),
308                        mName, mWidth, mHeight, mDensityDpi, mGravity,
309                        OverlayDisplayHandle.this);
310                window.show();
311
312                synchronized (getSyncRoot()) {
313                    mWindow = window;
314                }
315            }
316        };
317
318        // Runs on the UI thread.
319        private final Runnable mDismissRunnable = new Runnable() {
320            @Override
321            public void run() {
322                OverlayDisplayWindow window;
323                synchronized (getSyncRoot()) {
324                    window = mWindow;
325                    mWindow = null;
326                }
327
328                if (window != null) {
329                    window.dismiss();
330                }
331            }
332        };
333    }
334}
335