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