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 17package com.android.server.display; 18 19import com.android.internal.util.DumpUtils; 20 21import android.content.Context; 22import android.graphics.SurfaceTexture; 23import android.hardware.display.DisplayManager; 24import android.util.Slog; 25import android.view.Display; 26import android.view.DisplayInfo; 27import android.view.GestureDetector; 28import android.view.Gravity; 29import android.view.LayoutInflater; 30import android.view.MotionEvent; 31import android.view.ScaleGestureDetector; 32import android.view.TextureView; 33import android.view.ThreadedRenderer; 34import android.view.View; 35import android.view.WindowManager; 36import android.view.TextureView.SurfaceTextureListener; 37import android.widget.TextView; 38 39import 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 */ 48final 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 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 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 135 public void dismiss() { 136 if (mWindowVisible) { 137 mDisplayManager.unregisterDisplayListener(mDisplayListener); 138 mWindowManager.removeView(mWindowContent); 139 mWindowVisible = false; 140 } 141 } 142 143 public void resize(int width, int height, int densityDpi) { 144 resize(width, height, densityDpi, true /* doLayout */); 145 } 146 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 163 public void relayout() { 164 if (mWindowVisible) { 165 updateWindowParams(); 166 mWindowManager.updateViewLayout(mWindowContent, mWindowParams); 167 } 168 } 169 170 @Override 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 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 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 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 276 private void saveWindowParams() { 277 mWindowX = mWindowParams.x; 278 mWindowY = mWindowParams.y; 279 mWindowScale = mTextureView.getScaleX(); 280 clearLiveState(); 281 } 282 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 { 391 public void onWindowCreated(SurfaceTexture surfaceTexture, 392 float refreshRate, long presentationDeadlineNanos, int state); 393 public void onWindowDestroyed(); 394 public void onStateChanged(int state); 395 } 396} 397