MediaRouter / src / com.example.android.mediarouter / player /

OverlayDisplayWindow.java

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.example.android.mediarouter.player;
18
 
19
import android.content.Context;
20
import android.graphics.SurfaceTexture;
21
import android.hardware.display.DisplayManager;
22
import android.os.Build;
23
import android.util.DisplayMetrics;
24
import android.util.Log;
25
import android.view.Display;
26
import android.view.GestureDetector;
27
import android.view.Gravity;
28
import android.view.LayoutInflater;
29
import android.view.MotionEvent;
30
import android.view.ScaleGestureDetector;
31
import android.view.Surface;
32
import android.view.SurfaceHolder;
33
import android.view.SurfaceView;
34
import android.view.TextureView;
35
import android.view.TextureView.SurfaceTextureListener;
36
import android.view.View;
37
import android.view.WindowManager;
38
import android.widget.TextView;
39
 
40
import com.example.android.mediarouter.R;
41
 
42
/**
43
 * Manages an overlay display window, used for simulating remote playback.
44
 */
45
public 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
}