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