OverlayDisplayWindow.java revision 9158825f9c41869689d6b1786d7c7aa8bdd524ce
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;
20
21import android.content.Context;
22import android.graphics.SurfaceTexture;
23import android.hardware.display.DisplayManager;
24import android.util.Slog;
25import android.view.Display;
26import android.view.DisplayInfo;
27import android.view.GestureDetector;
28import android.view.Gravity;
29import android.view.LayoutInflater;
30import android.view.MotionEvent;
31import android.view.ScaleGestureDetector;
32import android.view.TextureView;
33import android.view.View;
34import android.view.WindowManager;
35import android.view.TextureView.SurfaceTextureListener;
36import android.widget.TextView;
37
38import java.io.PrintWriter;
39
40/**
41 * Manages an overlay window on behalf of {@link OverlayDisplayAdapter}.
42 * <p>
43 * This object must only be accessed on the UI thread.
44 * No locks are held by this object and locks must not be held while making called into it.
45 * </p>
46 */
47final class OverlayDisplayWindow implements DumpUtils.Dump {
48    private static final String TAG = "OverlayDisplayWindow";
49    private static final boolean DEBUG = false;
50
51    private final float INITIAL_SCALE = 0.5f;
52    private final float MIN_SCALE = 0.3f;
53    private final float MAX_SCALE = 1.0f;
54    private final float WINDOW_ALPHA = 0.8f;
55
56    // When true, disables support for moving and resizing the overlay.
57    // The window is made non-touchable, which makes it possible to
58    // directly interact with the content underneath.
59    private final boolean DISABLE_MOVE_AND_RESIZE = false;
60
61    private final Context mContext;
62    private final String mName;
63    private final int mWidth;
64    private final int mHeight;
65    private final int mDensityDpi;
66    private final int mGravity;
67    private final boolean mSecure;
68    private final Listener mListener;
69    private String mTitle;
70
71    private final DisplayManager mDisplayManager;
72    private final WindowManager mWindowManager;
73
74
75    private final Display mDefaultDisplay;
76    private final DisplayInfo mDefaultDisplayInfo = new DisplayInfo();
77
78    private View mWindowContent;
79    private WindowManager.LayoutParams mWindowParams;
80    private TextureView mTextureView;
81    private TextView mTitleTextView;
82
83    private GestureDetector mGestureDetector;
84    private ScaleGestureDetector mScaleGestureDetector;
85
86    private boolean mWindowVisible;
87    private int mWindowX;
88    private int mWindowY;
89    private float mWindowScale;
90
91    private float mLiveTranslationX;
92    private float mLiveTranslationY;
93    private float mLiveScale = 1.0f;
94
95    public OverlayDisplayWindow(Context context, String name,
96            int width, int height, int densityDpi, int gravity, boolean secure,
97            Listener listener) {
98        mContext = context;
99        mName = name;
100        mWidth = width;
101        mHeight = height;
102        mDensityDpi = densityDpi;
103        mGravity = gravity;
104        mSecure = secure;
105        mListener = listener;
106        mTitle = context.getResources().getString(
107                com.android.internal.R.string.display_manager_overlay_display_title,
108                mName, mWidth, mHeight, mDensityDpi);
109        if (secure) {
110            mTitle += context.getResources().getString(
111                    com.android.internal.R.string.display_manager_overlay_display_secure_suffix);
112        }
113
114        mDisplayManager = (DisplayManager)context.getSystemService(
115                Context.DISPLAY_SERVICE);
116        mWindowManager = (WindowManager)context.getSystemService(
117                Context.WINDOW_SERVICE);
118
119        mDefaultDisplay = mWindowManager.getDefaultDisplay();
120        updateDefaultDisplayInfo();
121
122        createWindow();
123    }
124
125    public void show() {
126        if (!mWindowVisible) {
127            mDisplayManager.registerDisplayListener(mDisplayListener, null);
128            if (!updateDefaultDisplayInfo()) {
129                mDisplayManager.unregisterDisplayListener(mDisplayListener);
130                return;
131            }
132
133            clearLiveState();
134            updateWindowParams();
135            mWindowManager.addView(mWindowContent, mWindowParams);
136            mWindowVisible = true;
137        }
138    }
139
140    public void dismiss() {
141        if (mWindowVisible) {
142            mDisplayManager.unregisterDisplayListener(mDisplayListener);
143            mWindowManager.removeView(mWindowContent);
144            mWindowVisible = false;
145        }
146    }
147
148    public void relayout() {
149        if (mWindowVisible) {
150            updateWindowParams();
151            mWindowManager.updateViewLayout(mWindowContent, mWindowParams);
152        }
153    }
154
155    @Override
156    public void dump(PrintWriter pw) {
157        pw.println("mWindowVisible=" + mWindowVisible);
158        pw.println("mWindowX=" + mWindowX);
159        pw.println("mWindowY=" + mWindowY);
160        pw.println("mWindowScale=" + mWindowScale);
161        pw.println("mWindowParams=" + mWindowParams);
162        if (mTextureView != null) {
163            pw.println("mTextureView.getScaleX()=" + mTextureView.getScaleX());
164            pw.println("mTextureView.getScaleY()=" + mTextureView.getScaleY());
165        }
166        pw.println("mLiveTranslationX=" + mLiveTranslationX);
167        pw.println("mLiveTranslationY=" + mLiveTranslationY);
168        pw.println("mLiveScale=" + mLiveScale);
169    }
170
171    private boolean updateDefaultDisplayInfo() {
172        if (!mDefaultDisplay.getDisplayInfo(mDefaultDisplayInfo)) {
173            Slog.w(TAG, "Cannot show overlay display because there is no "
174                    + "default display upon which to show it.");
175            return false;
176        }
177        return true;
178    }
179
180    private void createWindow() {
181        LayoutInflater inflater = LayoutInflater.from(mContext);
182
183        mWindowContent = inflater.inflate(
184                com.android.internal.R.layout.overlay_display_window, null);
185        mWindowContent.setOnTouchListener(mOnTouchListener);
186
187        mTextureView = (TextureView)mWindowContent.findViewById(
188                com.android.internal.R.id.overlay_display_window_texture);
189        mTextureView.setPivotX(0);
190        mTextureView.setPivotY(0);
191        mTextureView.getLayoutParams().width = mWidth;
192        mTextureView.getLayoutParams().height = mHeight;
193        mTextureView.setOpaque(false);
194        mTextureView.setSurfaceTextureListener(mSurfaceTextureListener);
195
196        mTitleTextView = (TextView)mWindowContent.findViewById(
197                com.android.internal.R.id.overlay_display_window_title);
198        mTitleTextView.setText(mTitle);
199
200        mWindowParams = new WindowManager.LayoutParams(
201                WindowManager.LayoutParams.TYPE_DISPLAY_OVERLAY);
202        mWindowParams.flags |= WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
203                | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
204                | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
205                | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
206                | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
207        if (mSecure) {
208            mWindowParams.flags |= WindowManager.LayoutParams.FLAG_SECURE;
209        }
210        if (DISABLE_MOVE_AND_RESIZE) {
211            mWindowParams.flags |= WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
212        }
213        mWindowParams.privateFlags |=
214                WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_HARDWARE_ACCELERATED;
215        mWindowParams.alpha = WINDOW_ALPHA;
216        mWindowParams.gravity = Gravity.TOP | Gravity.LEFT;
217        mWindowParams.setTitle(mTitle);
218
219        mGestureDetector = new GestureDetector(mContext, mOnGestureListener);
220        mScaleGestureDetector = new ScaleGestureDetector(mContext, mOnScaleGestureListener);
221
222        // Set the initial position and scale.
223        // The position and scale will be clamped when the display is first shown.
224        mWindowX = (mGravity & Gravity.LEFT) == Gravity.LEFT ?
225                0 : mDefaultDisplayInfo.logicalWidth;
226        mWindowY = (mGravity & Gravity.TOP) == Gravity.TOP ?
227                0 : mDefaultDisplayInfo.logicalHeight;
228        mWindowScale = INITIAL_SCALE;
229    }
230
231    private void updateWindowParams() {
232        float scale = mWindowScale * mLiveScale;
233        scale = Math.min(scale, (float)mDefaultDisplayInfo.logicalWidth / mWidth);
234        scale = Math.min(scale, (float)mDefaultDisplayInfo.logicalHeight / mHeight);
235        scale = Math.max(MIN_SCALE, Math.min(MAX_SCALE, scale));
236
237        float offsetScale = (scale / mWindowScale - 1.0f) * 0.5f;
238        int width = (int)(mWidth * scale);
239        int height = (int)(mHeight * scale);
240        int x = (int)(mWindowX + mLiveTranslationX - width * offsetScale);
241        int y = (int)(mWindowY + mLiveTranslationY - height * offsetScale);
242        x = Math.max(0, Math.min(x, mDefaultDisplayInfo.logicalWidth - width));
243        y = Math.max(0, Math.min(y, mDefaultDisplayInfo.logicalHeight - height));
244
245        if (DEBUG) {
246            Slog.d(TAG, "updateWindowParams: scale=" + scale
247                    + ", offsetScale=" + offsetScale
248                    + ", x=" + x + ", y=" + y
249                    + ", width=" + width + ", height=" + height);
250        }
251
252        mTextureView.setScaleX(scale);
253        mTextureView.setScaleY(scale);
254
255        mWindowParams.x = x;
256        mWindowParams.y = y;
257        mWindowParams.width = width;
258        mWindowParams.height = height;
259    }
260
261    private void saveWindowParams() {
262        mWindowX = mWindowParams.x;
263        mWindowY = mWindowParams.y;
264        mWindowScale = mTextureView.getScaleX();
265        clearLiveState();
266    }
267
268    private void clearLiveState() {
269        mLiveTranslationX = 0f;
270        mLiveTranslationY = 0f;
271        mLiveScale = 1.0f;
272    }
273
274    private final DisplayManager.DisplayListener mDisplayListener =
275            new DisplayManager.DisplayListener() {
276        @Override
277        public void onDisplayAdded(int displayId) {
278        }
279
280        @Override
281        public void onDisplayChanged(int displayId) {
282            if (displayId == mDefaultDisplay.getDisplayId()) {
283                if (updateDefaultDisplayInfo()) {
284                    relayout();
285                } else {
286                    dismiss();
287                }
288            }
289        }
290
291        @Override
292        public void onDisplayRemoved(int displayId) {
293            if (displayId == mDefaultDisplay.getDisplayId()) {
294                dismiss();
295            }
296        }
297    };
298
299    private final SurfaceTextureListener mSurfaceTextureListener =
300            new SurfaceTextureListener() {
301        @Override
302        public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture,
303                int width, int height) {
304            mListener.onWindowCreated(surfaceTexture, mDefaultDisplayInfo.refreshRate);
305        }
306
307        @Override
308        public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) {
309            mListener.onWindowDestroyed();
310            return true;
311        }
312
313        @Override
314        public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture,
315                int width, int height) {
316        }
317
318        @Override
319        public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) {
320        }
321    };
322
323    private final View.OnTouchListener mOnTouchListener = new View.OnTouchListener() {
324        @Override
325        public boolean onTouch(View view, MotionEvent event) {
326            // Work in screen coordinates.
327            final float oldX = event.getX();
328            final float oldY = event.getY();
329            event.setLocation(event.getRawX(), event.getRawY());
330
331            mGestureDetector.onTouchEvent(event);
332            mScaleGestureDetector.onTouchEvent(event);
333
334            switch (event.getActionMasked()) {
335                case MotionEvent.ACTION_UP:
336                case MotionEvent.ACTION_CANCEL:
337                    saveWindowParams();
338                    break;
339            }
340
341            // Revert to window coordinates.
342            event.setLocation(oldX, oldY);
343            return true;
344        }
345    };
346
347    private final GestureDetector.OnGestureListener mOnGestureListener =
348            new GestureDetector.SimpleOnGestureListener() {
349        @Override
350        public boolean onScroll(MotionEvent e1, MotionEvent e2,
351                float distanceX, float distanceY) {
352            mLiveTranslationX -= distanceX;
353            mLiveTranslationY -= distanceY;
354            relayout();
355            return true;
356        }
357    };
358
359    private final ScaleGestureDetector.OnScaleGestureListener mOnScaleGestureListener =
360            new ScaleGestureDetector.SimpleOnScaleGestureListener() {
361        @Override
362        public boolean onScale(ScaleGestureDetector detector) {
363            mLiveScale *= detector.getScaleFactor();
364            relayout();
365            return true;
366        }
367    };
368
369    /**
370     * Watches for significant changes in the overlay display window lifecycle.
371     */
372    public interface Listener {
373        public void onWindowCreated(SurfaceTexture surfaceTexture, float refreshRate);
374        public void onWindowDestroyed();
375    }
376}