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