blob: 58348308e8246fad27df8ff89f90aef78229f7f4 [file] [log] [blame]
Alexander Lucas97842ff2014-03-07 14:56:55 -08001/*
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.mediarouter.player;
18
19import android.content.Context;
20import android.graphics.SurfaceTexture;
21import android.hardware.display.DisplayManager;
22import android.os.Build;
23import android.util.DisplayMetrics;
24import android.util.Log;
25import android.view.Display;
26import android.view.GestureDetector;
27import android.view.Gravity;
28import android.view.LayoutInflater;
29import android.view.MotionEvent;
30import android.view.ScaleGestureDetector;
31import android.view.Surface;
32import android.view.SurfaceHolder;
33import android.view.SurfaceView;
34import android.view.TextureView;
35import android.view.TextureView.SurfaceTextureListener;
36import android.view.View;
37import android.view.WindowManager;
38import android.widget.TextView;
39
40import com.example.android.mediarouter.R;
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}