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.android.server.display; 18 19 import com.android.internal.util.DumpUtils; 20 21 import android.content.Context; 22 import android.graphics.SurfaceTexture; 23 import android.hardware.display.DisplayManager; 24 import android.util.Slog; 25 import android.view.Display; 26 import android.view.DisplayInfo; 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.TextureView; 33 import android.view.ThreadedRenderer; 34 import android.view.View; 35 import android.view.WindowManager; 36 import android.view.TextureView.SurfaceTextureListener; 37 import android.widget.TextView; 38 39 import java.io.PrintWriter; 40 41 /** 42 * Manages an overlay window on behalf of {@link OverlayDisplayAdapter}. 43 * <p> 44 * This object must only be accessed on the UI thread. 45 * No locks are held by this object and locks must not be held while making called into it. 46 * </p> 47 */ 48 final class OverlayDisplayWindow implements DumpUtils.Dump { 49 private static final String TAG = "OverlayDisplayWindow"; 50 private static final boolean DEBUG = false; 51 52 private final float INITIAL_SCALE = 0.5f; 53 private final float MIN_SCALE = 0.3f; 54 private final float MAX_SCALE = 1.0f; 55 private final float WINDOW_ALPHA = 0.8f; 56 57 // When true, disables support for moving and resizing the overlay. 58 // The window is made non-touchable, which makes it possible to 59 // directly interact with the content underneath. 60 private final boolean DISABLE_MOVE_AND_RESIZE = false; 61 62 private final Context mContext; 63 private final String mName; 64 private int mWidth; 65 private int mHeight; 66 private int mDensityDpi; 67 private final int mGravity; 68 private final boolean mSecure; 69 private final Listener mListener; 70 private String mTitle; 71 72 private final DisplayManager mDisplayManager; 73 private final WindowManager mWindowManager; 74 75 76 private final Display mDefaultDisplay; 77 private final DisplayInfo mDefaultDisplayInfo = new DisplayInfo(); 78 79 private View mWindowContent; 80 private WindowManager.LayoutParams mWindowParams; 81 private TextureView mTextureView; 82 private TextView mTitleTextView; 83 84 private GestureDetector mGestureDetector; 85 private ScaleGestureDetector mScaleGestureDetector; 86 87 private boolean mWindowVisible; 88 private int mWindowX; 89 private int mWindowY; 90 private float mWindowScale; 91 92 private float mLiveTranslationX; 93 private float mLiveTranslationY; 94 private float mLiveScale = 1.0f; 95 OverlayDisplayWindow(Context context, String name, int width, int height, int densityDpi, int gravity, boolean secure, Listener listener)96 public OverlayDisplayWindow(Context context, String name, 97 int width, int height, int densityDpi, int gravity, boolean secure, 98 Listener listener) { 99 // Workaround device freeze (b/38372997) 100 ThreadedRenderer.disableVsync(); 101 mContext = context; 102 mName = name; 103 mGravity = gravity; 104 mSecure = secure; 105 mListener = listener; 106 107 mDisplayManager = (DisplayManager)context.getSystemService( 108 Context.DISPLAY_SERVICE); 109 mWindowManager = (WindowManager)context.getSystemService( 110 Context.WINDOW_SERVICE); 111 112 mDefaultDisplay = mWindowManager.getDefaultDisplay(); 113 updateDefaultDisplayInfo(); 114 115 resize(width, height, densityDpi, false /* doLayout */); 116 117 createWindow(); 118 } 119 show()120 public void show() { 121 if (!mWindowVisible) { 122 mDisplayManager.registerDisplayListener(mDisplayListener, null); 123 if (!updateDefaultDisplayInfo()) { 124 mDisplayManager.unregisterDisplayListener(mDisplayListener); 125 return; 126 } 127 128 clearLiveState(); 129 updateWindowParams(); 130 mWindowManager.addView(mWindowContent, mWindowParams); 131 mWindowVisible = true; 132 } 133 } 134 dismiss()135 public void dismiss() { 136 if (mWindowVisible) { 137 mDisplayManager.unregisterDisplayListener(mDisplayListener); 138 mWindowManager.removeView(mWindowContent); 139 mWindowVisible = false; 140 } 141 } 142 resize(int width, int height, int densityDpi)143 public void resize(int width, int height, int densityDpi) { 144 resize(width, height, densityDpi, true /* doLayout */); 145 } 146 resize(int width, int height, int densityDpi, boolean doLayout)147 private void resize(int width, int height, int densityDpi, boolean doLayout) { 148 mWidth = width; 149 mHeight = height; 150 mDensityDpi = densityDpi; 151 mTitle = mContext.getResources().getString( 152 com.android.internal.R.string.display_manager_overlay_display_title, 153 mName, mWidth, mHeight, mDensityDpi); 154 if (mSecure) { 155 mTitle += mContext.getResources().getString( 156 com.android.internal.R.string.display_manager_overlay_display_secure_suffix); 157 } 158 if (doLayout) { 159 relayout(); 160 } 161 } 162 relayout()163 public void relayout() { 164 if (mWindowVisible) { 165 updateWindowParams(); 166 mWindowManager.updateViewLayout(mWindowContent, mWindowParams); 167 } 168 } 169 170 @Override dump(PrintWriter pw, String prefix)171 public void dump(PrintWriter pw, String prefix) { 172 pw.println("mWindowVisible=" + mWindowVisible); 173 pw.println("mWindowX=" + mWindowX); 174 pw.println("mWindowY=" + mWindowY); 175 pw.println("mWindowScale=" + mWindowScale); 176 pw.println("mWindowParams=" + mWindowParams); 177 if (mTextureView != null) { 178 pw.println("mTextureView.getScaleX()=" + mTextureView.getScaleX()); 179 pw.println("mTextureView.getScaleY()=" + mTextureView.getScaleY()); 180 } 181 pw.println("mLiveTranslationX=" + mLiveTranslationX); 182 pw.println("mLiveTranslationY=" + mLiveTranslationY); 183 pw.println("mLiveScale=" + mLiveScale); 184 } 185 updateDefaultDisplayInfo()186 private boolean updateDefaultDisplayInfo() { 187 if (!mDefaultDisplay.getDisplayInfo(mDefaultDisplayInfo)) { 188 Slog.w(TAG, "Cannot show overlay display because there is no " 189 + "default display upon which to show it."); 190 return false; 191 } 192 return true; 193 } 194 createWindow()195 private void createWindow() { 196 LayoutInflater inflater = LayoutInflater.from(mContext); 197 198 mWindowContent = inflater.inflate( 199 com.android.internal.R.layout.overlay_display_window, null); 200 mWindowContent.setOnTouchListener(mOnTouchListener); 201 202 mTextureView = (TextureView)mWindowContent.findViewById( 203 com.android.internal.R.id.overlay_display_window_texture); 204 mTextureView.setPivotX(0); 205 mTextureView.setPivotY(0); 206 mTextureView.getLayoutParams().width = mWidth; 207 mTextureView.getLayoutParams().height = mHeight; 208 mTextureView.setOpaque(false); 209 mTextureView.setSurfaceTextureListener(mSurfaceTextureListener); 210 211 mTitleTextView = (TextView)mWindowContent.findViewById( 212 com.android.internal.R.id.overlay_display_window_title); 213 mTitleTextView.setText(mTitle); 214 215 mWindowParams = new WindowManager.LayoutParams( 216 WindowManager.LayoutParams.TYPE_DISPLAY_OVERLAY); 217 mWindowParams.flags |= WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN 218 | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS 219 | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE 220 | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL 221 | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED; 222 if (mSecure) { 223 mWindowParams.flags |= WindowManager.LayoutParams.FLAG_SECURE; 224 } 225 if (DISABLE_MOVE_AND_RESIZE) { 226 mWindowParams.flags |= WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; 227 } 228 mWindowParams.privateFlags |= 229 WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_HARDWARE_ACCELERATED; 230 mWindowParams.alpha = WINDOW_ALPHA; 231 mWindowParams.gravity = Gravity.TOP | Gravity.LEFT; 232 mWindowParams.setTitle(mTitle); 233 234 mGestureDetector = new GestureDetector(mContext, mOnGestureListener); 235 mScaleGestureDetector = new ScaleGestureDetector(mContext, mOnScaleGestureListener); 236 237 // Set the initial position and scale. 238 // The position and scale will be clamped when the display is first shown. 239 mWindowX = (mGravity & Gravity.LEFT) == Gravity.LEFT ? 240 0 : mDefaultDisplayInfo.logicalWidth; 241 mWindowY = (mGravity & Gravity.TOP) == Gravity.TOP ? 242 0 : mDefaultDisplayInfo.logicalHeight; 243 mWindowScale = INITIAL_SCALE; 244 } 245 updateWindowParams()246 private void updateWindowParams() { 247 float scale = mWindowScale * mLiveScale; 248 scale = Math.min(scale, (float)mDefaultDisplayInfo.logicalWidth / mWidth); 249 scale = Math.min(scale, (float)mDefaultDisplayInfo.logicalHeight / mHeight); 250 scale = Math.max(MIN_SCALE, Math.min(MAX_SCALE, scale)); 251 252 float offsetScale = (scale / mWindowScale - 1.0f) * 0.5f; 253 int width = (int)(mWidth * scale); 254 int height = (int)(mHeight * scale); 255 int x = (int)(mWindowX + mLiveTranslationX - width * offsetScale); 256 int y = (int)(mWindowY + mLiveTranslationY - height * offsetScale); 257 x = Math.max(0, Math.min(x, mDefaultDisplayInfo.logicalWidth - width)); 258 y = Math.max(0, Math.min(y, mDefaultDisplayInfo.logicalHeight - height)); 259 260 if (DEBUG) { 261 Slog.d(TAG, "updateWindowParams: scale=" + scale 262 + ", offsetScale=" + offsetScale 263 + ", x=" + x + ", y=" + y 264 + ", width=" + width + ", height=" + height); 265 } 266 267 mTextureView.setScaleX(scale); 268 mTextureView.setScaleY(scale); 269 270 mWindowParams.x = x; 271 mWindowParams.y = y; 272 mWindowParams.width = width; 273 mWindowParams.height = height; 274 } 275 saveWindowParams()276 private void saveWindowParams() { 277 mWindowX = mWindowParams.x; 278 mWindowY = mWindowParams.y; 279 mWindowScale = mTextureView.getScaleX(); 280 clearLiveState(); 281 } 282 clearLiveState()283 private void clearLiveState() { 284 mLiveTranslationX = 0f; 285 mLiveTranslationY = 0f; 286 mLiveScale = 1.0f; 287 } 288 289 private final DisplayManager.DisplayListener mDisplayListener = 290 new DisplayManager.DisplayListener() { 291 @Override 292 public void onDisplayAdded(int displayId) { 293 } 294 295 @Override 296 public void onDisplayChanged(int displayId) { 297 if (displayId == mDefaultDisplay.getDisplayId()) { 298 if (updateDefaultDisplayInfo()) { 299 relayout(); 300 mListener.onStateChanged(mDefaultDisplayInfo.state); 301 } else { 302 dismiss(); 303 } 304 } 305 } 306 307 @Override 308 public void onDisplayRemoved(int displayId) { 309 if (displayId == mDefaultDisplay.getDisplayId()) { 310 dismiss(); 311 } 312 } 313 }; 314 315 private final SurfaceTextureListener mSurfaceTextureListener = 316 new SurfaceTextureListener() { 317 @Override 318 public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, 319 int width, int height) { 320 mListener.onWindowCreated(surfaceTexture, 321 mDefaultDisplayInfo.getMode().getRefreshRate(), 322 mDefaultDisplayInfo.presentationDeadlineNanos, mDefaultDisplayInfo.state); 323 } 324 325 @Override 326 public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) { 327 mListener.onWindowDestroyed(); 328 return true; 329 } 330 331 @Override 332 public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, 333 int width, int height) { 334 } 335 336 @Override 337 public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) { 338 } 339 }; 340 341 private final View.OnTouchListener mOnTouchListener = new View.OnTouchListener() { 342 @Override 343 public boolean onTouch(View view, MotionEvent event) { 344 // Work in screen coordinates. 345 final float oldX = event.getX(); 346 final float oldY = event.getY(); 347 event.setLocation(event.getRawX(), event.getRawY()); 348 349 mGestureDetector.onTouchEvent(event); 350 mScaleGestureDetector.onTouchEvent(event); 351 352 switch (event.getActionMasked()) { 353 case MotionEvent.ACTION_UP: 354 case MotionEvent.ACTION_CANCEL: 355 saveWindowParams(); 356 break; 357 } 358 359 // Revert to window coordinates. 360 event.setLocation(oldX, oldY); 361 return true; 362 } 363 }; 364 365 private final GestureDetector.OnGestureListener mOnGestureListener = 366 new GestureDetector.SimpleOnGestureListener() { 367 @Override 368 public boolean onScroll(MotionEvent e1, MotionEvent e2, 369 float distanceX, float distanceY) { 370 mLiveTranslationX -= distanceX; 371 mLiveTranslationY -= distanceY; 372 relayout(); 373 return true; 374 } 375 }; 376 377 private final ScaleGestureDetector.OnScaleGestureListener mOnScaleGestureListener = 378 new ScaleGestureDetector.SimpleOnScaleGestureListener() { 379 @Override 380 public boolean onScale(ScaleGestureDetector detector) { 381 mLiveScale *= detector.getScaleFactor(); 382 relayout(); 383 return true; 384 } 385 }; 386 387 /** 388 * Watches for significant changes in the overlay display window lifecycle. 389 */ 390 public interface Listener { onWindowCreated(SurfaceTexture surfaceTexture, float refreshRate, long presentationDeadlineNanos, int state)391 public void onWindowCreated(SurfaceTexture surfaceTexture, 392 float refreshRate, long presentationDeadlineNanos, int state); onWindowDestroyed()393 public void onWindowDestroyed(); onStateChanged(int state)394 public void onStateChanged(int state); 395 } 396 } 397