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
}