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                    mListener.onStateChanged(mDefaultDisplayInfo.state);
286                } else {
287                    dismiss();
288                }
289            }
290        }
291
292        @Override
293        public void onDisplayRemoved(int displayId) {
294            if (displayId == mDefaultDisplay.getDisplayId()) {
295                dismiss();
296            }
297        }
298    };
299
300    private final SurfaceTextureListener mSurfaceTextureListener =
301            new SurfaceTextureListener() {
302        @Override
303        public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture,
304                int width, int height) {
305            mListener.onWindowCreated(surfaceTexture, mDefaultDisplayInfo.refreshRate,
306                    mDefaultDisplayInfo.presentationDeadlineNanos, mDefaultDisplayInfo.state);
307        }
308
309        @Override
310        public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) {
311            mListener.onWindowDestroyed();
312            return true;
313        }
314
315        @Override
316        public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture,
317                int width, int height) {
318        }
319
320        @Override
321        public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) {
322        }
323    };
324
325    private final View.OnTouchListener mOnTouchListener = new View.OnTouchListener() {
326        @Override
327        public boolean onTouch(View view, MotionEvent event) {
328            // Work in screen coordinates.
329            final float oldX = event.getX();
330            final float oldY = event.getY();
331            event.setLocation(event.getRawX(), event.getRawY());
332
333            mGestureDetector.onTouchEvent(event);
334            mScaleGestureDetector.onTouchEvent(event);
335
336            switch (event.getActionMasked()) {
337                case MotionEvent.ACTION_UP:
338                case MotionEvent.ACTION_CANCEL:
339                    saveWindowParams();
340                    break;
341            }
342
343            // Revert to window coordinates.
344            event.setLocation(oldX, oldY);
345            return true;
346        }
347    };
348
349    private final GestureDetector.OnGestureListener mOnGestureListener =
350            new GestureDetector.SimpleOnGestureListener() {
351        @Override
352        public boolean onScroll(MotionEvent e1, MotionEvent e2,
353                float distanceX, float distanceY) {
354            mLiveTranslationX -= distanceX;
355            mLiveTranslationY -= distanceY;
356            relayout();
357            return true;
358        }
359    };
360
361    private final ScaleGestureDetector.OnScaleGestureListener mOnScaleGestureListener =
362            new ScaleGestureDetector.SimpleOnScaleGestureListener() {
363        @Override
364        public boolean onScale(ScaleGestureDetector detector) {
365            mLiveScale *= detector.getScaleFactor();
366            relayout();
367            return true;
368        }
369    };
370
371    /**
372     * Watches for significant changes in the overlay display window lifecycle.
373     */
374    public interface Listener {
375        public void onWindowCreated(SurfaceTexture surfaceTexture,
376                float refreshRate, long presentationDeadlineNanos, int state);
377        public void onWindowDestroyed();
378        public void onStateChanged(int state);
379    }
380}