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