OverlayDisplayWindow.java revision 4523ef115a83bf0f655dc58262bd156d7555c91b
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.example.android.supportv7.media;
18import com.example.android.supportv7.R;
19
20import android.content.Context;
21import android.graphics.Point;
22import android.graphics.SurfaceTexture;
23import android.hardware.display.DisplayManager;
24import android.os.Build;
25import android.util.Log;
26import android.view.Display;
27import android.util.DisplayMetrics;
28import android.view.GestureDetector;
29import android.view.Gravity;
30import android.view.LayoutInflater;
31import android.view.MotionEvent;
32import android.view.ScaleGestureDetector;
33import android.view.SurfaceHolder;
34import android.view.SurfaceView;
35import android.view.TextureView;
36import android.view.View;
37import android.view.Surface;
38import android.view.WindowManager;
39import android.view.TextureView.SurfaceTextureListener;
40import android.widget.TextView;
41
42/**
43 * Manages an overlay display window, used for simulating remote playback.
44 */
45public abstract class OverlayDisplayWindow {
46    private static final String TAG = "OverlayDisplayWindow";
47    private static final boolean DEBUG = false;
48
49    private static final float WINDOW_ALPHA = 0.8f;
50    private static final float INITIAL_SCALE = 0.5f;
51    private static final float MIN_SCALE = 0.3f;
52    private static final float MAX_SCALE = 1.0f;
53
54    protected final Context mContext;
55    protected final String mName;
56    protected final int mWidth;
57    protected final int mHeight;
58    protected final int mGravity;
59    protected OverlayWindowListener mListener;
60
61    protected OverlayDisplayWindow(Context context, String name,
62            int width, int height, int gravity) {
63        mContext = context;
64        mName = name;
65        mWidth = width;
66        mHeight = height;
67        mGravity = gravity;
68    }
69
70    public static OverlayDisplayWindow create(Context context, String name,
71            int width, int height, int gravity) {
72        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
73            return new JellybeanMr1Impl(context, name, width, height, gravity);
74        } else {
75            return new LegacyImpl(context, name, width, height, gravity);
76        }
77    }
78
79    public void setOverlayWindowListener(OverlayWindowListener listener) {
80        mListener = listener;
81    }
82
83    public Context getContext() {
84        return mContext;
85    }
86
87    public abstract void show();
88
89    public abstract void dismiss();
90
91    public abstract void updateAspectRatio(int width, int height);
92
93    // Watches for significant changes in the overlay display window lifecycle.
94    public interface OverlayWindowListener {
95        public void onWindowCreated(Surface surface);
96        public void onWindowCreated(SurfaceHolder surfaceHolder);
97        public void onWindowDestroyed();
98    }
99
100    /**
101     * Implementation for older versions.
102     */
103    private static final class LegacyImpl extends OverlayDisplayWindow {
104        private final WindowManager mWindowManager;
105
106        private boolean mWindowVisible;
107        private SurfaceView mSurfaceView;
108
109        public LegacyImpl(Context context, String name,
110                int width, int height, int gravity) {
111            super(context, name, width, height, gravity);
112
113            mWindowManager = (WindowManager)context.getSystemService(
114                    Context.WINDOW_SERVICE);
115        }
116
117        @Override
118        public void show() {
119            if (!mWindowVisible) {
120                mSurfaceView = new SurfaceView(mContext);
121
122                Display display = mWindowManager.getDefaultDisplay();
123
124                WindowManager.LayoutParams params = new WindowManager.LayoutParams(
125                        WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
126                params.flags |= WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
127                        | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
128                        | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
129                        | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
130                        | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
131                params.alpha = WINDOW_ALPHA;
132                params.gravity = Gravity.LEFT | Gravity.BOTTOM;
133                params.setTitle(mName);
134
135                int width = (int)(display.getWidth() * INITIAL_SCALE);
136                int height = (int)(display.getHeight() * INITIAL_SCALE);
137                if (mWidth > mHeight) {
138                    height = mHeight * width / mWidth;
139                } else {
140                    width = mWidth * height / mHeight;
141                }
142                params.width = width;
143                params.height = height;
144
145                mWindowManager.addView(mSurfaceView, params);
146                mWindowVisible = true;
147
148                SurfaceHolder holder = mSurfaceView.getHolder();
149                holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
150                mListener.onWindowCreated(holder);
151            }
152        }
153
154        @Override
155        public void dismiss() {
156            if (mWindowVisible) {
157                mListener.onWindowDestroyed();
158
159                mWindowManager.removeView(mSurfaceView);
160                mWindowVisible = false;
161            }
162        }
163
164        @Override
165        public void updateAspectRatio(int width, int height) {
166        }
167    }
168
169    /**
170     * Implementation for API version 17+.
171     */
172    private static final class JellybeanMr1Impl extends OverlayDisplayWindow {
173        // When true, disables support for moving and resizing the overlay.
174        // The window is made non-touchable, which makes it possible to
175        // directly interact with the content underneath.
176        private static final boolean DISABLE_MOVE_AND_RESIZE = false;
177
178        private final DisplayManager mDisplayManager;
179        private final WindowManager mWindowManager;
180
181        private final Display mDefaultDisplay;
182        private final DisplayMetrics mDefaultDisplayMetrics = new DisplayMetrics();
183
184        private View mWindowContent;
185        private WindowManager.LayoutParams mWindowParams;
186        private TextureView mTextureView;
187        private TextView mNameTextView;
188
189        private GestureDetector mGestureDetector;
190        private ScaleGestureDetector mScaleGestureDetector;
191
192        private boolean mWindowVisible;
193        private int mWindowX;
194        private int mWindowY;
195        private float mWindowScale;
196
197        private float mLiveTranslationX;
198        private float mLiveTranslationY;
199        private float mLiveScale = 1.0f;
200
201        public JellybeanMr1Impl(Context context, String name,
202                int width, int height, int gravity) {
203            super(context, name, width, height, gravity);
204
205            mDisplayManager = (DisplayManager)context.getSystemService(
206                    Context.DISPLAY_SERVICE);
207            mWindowManager = (WindowManager)context.getSystemService(
208                    Context.WINDOW_SERVICE);
209
210            mDefaultDisplay = mWindowManager.getDefaultDisplay();
211            updateDefaultDisplayInfo();
212
213            createWindow();
214        }
215
216        @Override
217        public void show() {
218            if (!mWindowVisible) {
219                mDisplayManager.registerDisplayListener(mDisplayListener, null);
220                if (!updateDefaultDisplayInfo()) {
221                    mDisplayManager.unregisterDisplayListener(mDisplayListener);
222                    return;
223                }
224
225                clearLiveState();
226                updateWindowParams();
227                mWindowManager.addView(mWindowContent, mWindowParams);
228                mWindowVisible = true;
229            }
230        }
231
232        @Override
233        public void dismiss() {
234            if (mWindowVisible) {
235                mDisplayManager.unregisterDisplayListener(mDisplayListener);
236                mWindowManager.removeView(mWindowContent);
237                mWindowVisible = false;
238            }
239        }
240
241        @Override
242        public void updateAspectRatio(int width, int height) {
243            if (mWidth * height < mHeight * width) {
244                mTextureView.getLayoutParams().width = mWidth;
245                mTextureView.getLayoutParams().height = mWidth * height / width;
246            } else {
247                mTextureView.getLayoutParams().width = mHeight * width / height;
248                mTextureView.getLayoutParams().height = mHeight;
249            }
250            relayout();
251        }
252
253        private void relayout() {
254            if (mWindowVisible) {
255                updateWindowParams();
256                mWindowManager.updateViewLayout(mWindowContent, mWindowParams);
257            }
258        }
259
260        private boolean updateDefaultDisplayInfo() {
261            mDefaultDisplay.getMetrics(mDefaultDisplayMetrics);
262            return true;
263        }
264
265        private void createWindow() {
266            LayoutInflater inflater = LayoutInflater.from(mContext);
267
268            mWindowContent = inflater.inflate(
269                    R.layout.overlay_display_window, null);
270            mWindowContent.setOnTouchListener(mOnTouchListener);
271
272            mTextureView = (TextureView)mWindowContent.findViewById(
273                    R.id.overlay_display_window_texture);
274            mTextureView.setPivotX(0);
275            mTextureView.setPivotY(0);
276            mTextureView.getLayoutParams().width = mWidth;
277            mTextureView.getLayoutParams().height = mHeight;
278            mTextureView.setOpaque(false);
279            mTextureView.setSurfaceTextureListener(mSurfaceTextureListener);
280
281            mNameTextView = (TextView)mWindowContent.findViewById(
282                    R.id.overlay_display_window_title);
283            mNameTextView.setText(mName);
284
285            mWindowParams = new WindowManager.LayoutParams(
286                    WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
287            mWindowParams.flags |= WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
288                    | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
289                    | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
290                    | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
291                    | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
292            if (DISABLE_MOVE_AND_RESIZE) {
293                mWindowParams.flags |= WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
294            }
295            mWindowParams.alpha = WINDOW_ALPHA;
296            mWindowParams.gravity = Gravity.TOP | Gravity.LEFT;
297            mWindowParams.setTitle(mName);
298
299            mGestureDetector = new GestureDetector(mContext, mOnGestureListener);
300            mScaleGestureDetector = new ScaleGestureDetector(mContext, mOnScaleGestureListener);
301
302            // Set the initial position and scale.
303            // The position and scale will be clamped when the display is first shown.
304            mWindowX = (mGravity & Gravity.LEFT) == Gravity.LEFT ?
305                    0 : mDefaultDisplayMetrics.widthPixels;
306            mWindowY = (mGravity & Gravity.TOP) == Gravity.TOP ?
307                    0 : mDefaultDisplayMetrics.heightPixels;
308            Log.d(TAG, mDefaultDisplayMetrics.toString());
309            mWindowScale = INITIAL_SCALE;
310
311            // calculate and save initial settings
312            updateWindowParams();
313            saveWindowParams();
314        }
315
316        private void updateWindowParams() {
317            float scale = mWindowScale * mLiveScale;
318            scale = Math.min(scale, (float)mDefaultDisplayMetrics.widthPixels / mWidth);
319            scale = Math.min(scale, (float)mDefaultDisplayMetrics.heightPixels / mHeight);
320            scale = Math.max(MIN_SCALE, Math.min(MAX_SCALE, scale));
321
322            float offsetScale = (scale / mWindowScale - 1.0f) * 0.5f;
323            int width = (int)(mWidth * scale);
324            int height = (int)(mHeight * scale);
325            int x = (int)(mWindowX + mLiveTranslationX - width * offsetScale);
326            int y = (int)(mWindowY + mLiveTranslationY - height * offsetScale);
327            x = Math.max(0, Math.min(x, mDefaultDisplayMetrics.widthPixels - width));
328            y = Math.max(0, Math.min(y, mDefaultDisplayMetrics.heightPixels - height));
329
330            if (DEBUG) {
331                Log.d(TAG, "updateWindowParams: scale=" + scale
332                        + ", offsetScale=" + offsetScale
333                        + ", x=" + x + ", y=" + y
334                        + ", width=" + width + ", height=" + height);
335            }
336
337            mTextureView.setScaleX(scale);
338            mTextureView.setScaleY(scale);
339
340            mTextureView.setTranslationX(
341                    (mWidth - mTextureView.getLayoutParams().width) * scale / 2);
342            mTextureView.setTranslationY(
343                    (mHeight - mTextureView.getLayoutParams().height) * scale / 2);
344
345            mWindowParams.x = x;
346            mWindowParams.y = y;
347            mWindowParams.width = width;
348            mWindowParams.height = height;
349        }
350
351        private void saveWindowParams() {
352            mWindowX = mWindowParams.x;
353            mWindowY = mWindowParams.y;
354            mWindowScale = mTextureView.getScaleX();
355            clearLiveState();
356        }
357
358        private void clearLiveState() {
359            mLiveTranslationX = 0f;
360            mLiveTranslationY = 0f;
361            mLiveScale = 1.0f;
362        }
363
364        private final DisplayManager.DisplayListener mDisplayListener =
365                new DisplayManager.DisplayListener() {
366            @Override
367            public void onDisplayAdded(int displayId) {
368            }
369
370            @Override
371            public void onDisplayChanged(int displayId) {
372                if (displayId == mDefaultDisplay.getDisplayId()) {
373                    if (updateDefaultDisplayInfo()) {
374                        relayout();
375                    } else {
376                        dismiss();
377                    }
378                }
379            }
380
381            @Override
382            public void onDisplayRemoved(int displayId) {
383                if (displayId == mDefaultDisplay.getDisplayId()) {
384                    dismiss();
385                }
386            }
387        };
388
389        private final SurfaceTextureListener mSurfaceTextureListener =
390                new SurfaceTextureListener() {
391            @Override
392            public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture,
393                    int width, int height) {
394                if (mListener != null) {
395                    mListener.onWindowCreated(new Surface(surfaceTexture));
396                }
397            }
398
399            @Override
400            public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) {
401                if (mListener != null) {
402                    mListener.onWindowDestroyed();
403                }
404                return true;
405            }
406
407            @Override
408            public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture,
409                    int width, int height) {
410            }
411
412            @Override
413            public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) {
414            }
415        };
416
417        private final View.OnTouchListener mOnTouchListener = new View.OnTouchListener() {
418            @Override
419            public boolean onTouch(View view, MotionEvent event) {
420                // Work in screen coordinates.
421                final float oldX = event.getX();
422                final float oldY = event.getY();
423                event.setLocation(event.getRawX(), event.getRawY());
424
425                mGestureDetector.onTouchEvent(event);
426                mScaleGestureDetector.onTouchEvent(event);
427
428                switch (event.getActionMasked()) {
429                    case MotionEvent.ACTION_UP:
430                    case MotionEvent.ACTION_CANCEL:
431                        saveWindowParams();
432                        break;
433                }
434
435                // Revert to window coordinates.
436                event.setLocation(oldX, oldY);
437                return true;
438            }
439        };
440
441        private final GestureDetector.OnGestureListener mOnGestureListener =
442                new GestureDetector.SimpleOnGestureListener() {
443            @Override
444            public boolean onScroll(MotionEvent e1, MotionEvent e2,
445                    float distanceX, float distanceY) {
446                mLiveTranslationX -= distanceX;
447                mLiveTranslationY -= distanceY;
448                relayout();
449                return true;
450            }
451        };
452
453        private final ScaleGestureDetector.OnScaleGestureListener mOnScaleGestureListener =
454                new ScaleGestureDetector.SimpleOnScaleGestureListener() {
455            @Override
456            public boolean onScale(ScaleGestureDetector detector) {
457                mLiveScale *= detector.getScaleFactor();
458                relayout();
459                return true;
460            }
461        };
462    }
463}
464