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