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