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