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