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