AutofillPopupWindow.java revision 36b86c28f88e4c7853a4255a0fd9b754cbb547c4
1/* 2 * Copyright (C) 2017 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 android.view.autofill; 18 19import static android.view.autofill.Helper.sVerbose; 20 21import android.annotation.NonNull; 22import android.graphics.Point; 23import android.graphics.Rect; 24import android.graphics.drawable.Drawable; 25import android.os.IBinder; 26import android.os.RemoteException; 27import android.transition.Transition; 28import android.util.Log; 29import android.view.View; 30import android.view.View.OnTouchListener; 31import android.view.ViewTreeObserver; 32import android.view.WindowManager; 33import android.view.WindowManager.LayoutParams; 34import android.widget.PopupWindow; 35 36/** 37 * Custom {@link PopupWindow} used to isolate its content from the autofilled app - the 38 * UI is rendered in a framework process, but it's controlled by the app. 39 * 40 * TODO(b/34943932): use an app surface control solution. 41 * 42 * @hide 43 */ 44public class AutofillPopupWindow extends PopupWindow { 45 46 private static final String TAG = "AutofillPopupWindow"; 47 48 private final WindowPresenter mWindowPresenter; 49 private WindowManager.LayoutParams mWindowLayoutParams; 50 private boolean mFullScreen; 51 52 private final View.OnAttachStateChangeListener mOnAttachStateChangeListener = 53 new View.OnAttachStateChangeListener() { 54 @Override 55 public void onViewAttachedToWindow(View v) { 56 /* ignore - handled by the super class */ 57 } 58 59 @Override 60 public void onViewDetachedFromWindow(View v) { 61 dismiss(); 62 } 63 }; 64 65 /** 66 * Creates a popup window with a presenter owning the window and responsible for 67 * showing/hiding/updating the backing window. This can be useful of the window is 68 * being shown by another process while the popup logic is in the process hosting 69 * the anchor view. 70 * <p> 71 * Using this constructor means that the presenter completely owns the content of 72 * the window and the following methods manipulating the window content shouldn't 73 * be used: {@link #getEnterTransition()}, {@link #setEnterTransition(Transition)}, 74 * {@link #getExitTransition()}, {@link #setExitTransition(Transition)}, 75 * {@link #getContentView()}, {@link #setContentView(View)}, {@link #getBackground()}, 76 * {@link #setBackgroundDrawable(Drawable)}, {@link #getElevation()}, 77 * {@link #setElevation(float)}, ({@link #getAnimationStyle()}, 78 * {@link #setAnimationStyle(int)}, {@link #setTouchInterceptor(OnTouchListener)}.</p> 79 */ 80 public AutofillPopupWindow(@NonNull IAutofillWindowPresenter presenter) { 81 mWindowPresenter = new WindowPresenter(presenter); 82 83 setTouchModal(false); 84 setOutsideTouchable(true); 85 setInputMethodMode(INPUT_METHOD_NOT_NEEDED); 86 setFocusable(true); 87 } 88 89 @Override 90 protected boolean hasContentView() { 91 return true; 92 } 93 94 @Override 95 protected boolean hasDecorView() { 96 return true; 97 } 98 99 @Override 100 protected LayoutParams getDecorViewLayoutParams() { 101 return mWindowLayoutParams; 102 } 103 104 /** 105 * The effective {@code update} method that should be called by its clients. 106 */ 107 public void update(View anchor, int offsetX, int offsetY, int width, int height, 108 Rect virtualBounds) { 109 mFullScreen = width == LayoutParams.MATCH_PARENT; 110 // For no fullscreen autofill window, we want to show the window as system controlled one 111 // so it covers app windows, but it has to be an application type (so it's contained inside 112 // the application area). Hence, we set it to the application type with the highest z-order, 113 // which currently is TYPE_APPLICATION_ABOVE_SUB_PANEL. 114 // For fullscreen mode, autofill window is at the bottom of screen, it should not be 115 // clipped by app activity window. Fullscreen autofill window does not need to follow app 116 // anchor view position. 117 setWindowLayoutType(mFullScreen ? WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG 118 : WindowManager.LayoutParams.TYPE_APPLICATION_ABOVE_SUB_PANEL); 119 // If we are showing the popup for a virtual view we use a fake view which 120 // delegates to the anchor but present itself with the same bounds as the 121 // virtual view. This ensures that the location logic in popup works 122 // symmetrically when the dropdown is below and above the anchor. 123 final View actualAnchor; 124 if (mFullScreen) { 125 offsetX = 0; 126 offsetY = 0; 127 // If it is not fullscreen height, put window at bottom. Computes absolute position. 128 // Note that we cannot easily change default gravity from Gravity.TOP to 129 // Gravity.BOTTOM because PopupWindow base class does not expose computeGravity(). 130 final Point outPoint = new Point(); 131 anchor.getContext().getDisplay().getSize(outPoint); 132 width = outPoint.x; 133 if (height != LayoutParams.MATCH_PARENT) { 134 offsetY = outPoint.y - height; 135 } 136 actualAnchor = anchor; 137 } else if (virtualBounds != null) { 138 final int[] mLocationOnScreen = new int[] {virtualBounds.left, virtualBounds.top}; 139 actualAnchor = new View(anchor.getContext()) { 140 @Override 141 public void getLocationOnScreen(int[] location) { 142 location[0] = mLocationOnScreen[0]; 143 location[1] = mLocationOnScreen[1]; 144 } 145 146 @Override 147 public int getAccessibilityViewId() { 148 return anchor.getAccessibilityViewId(); 149 } 150 151 @Override 152 public ViewTreeObserver getViewTreeObserver() { 153 return anchor.getViewTreeObserver(); 154 } 155 156 @Override 157 public IBinder getApplicationWindowToken() { 158 return anchor.getApplicationWindowToken(); 159 } 160 161 @Override 162 public View getRootView() { 163 return anchor.getRootView(); 164 } 165 166 @Override 167 public int getLayoutDirection() { 168 return anchor.getLayoutDirection(); 169 } 170 171 @Override 172 public void getWindowDisplayFrame(Rect outRect) { 173 anchor.getWindowDisplayFrame(outRect); 174 } 175 176 @Override 177 public void addOnAttachStateChangeListener( 178 OnAttachStateChangeListener listener) { 179 anchor.addOnAttachStateChangeListener(listener); 180 } 181 182 @Override 183 public void removeOnAttachStateChangeListener( 184 OnAttachStateChangeListener listener) { 185 anchor.removeOnAttachStateChangeListener(listener); 186 } 187 188 @Override 189 public boolean isAttachedToWindow() { 190 return anchor.isAttachedToWindow(); 191 } 192 193 @Override 194 public boolean requestRectangleOnScreen(Rect rectangle, boolean immediate) { 195 return anchor.requestRectangleOnScreen(rectangle, immediate); 196 } 197 198 @Override 199 public IBinder getWindowToken() { 200 return anchor.getWindowToken(); 201 } 202 }; 203 204 actualAnchor.setLeftTopRightBottom( 205 virtualBounds.left, virtualBounds.top, 206 virtualBounds.right, virtualBounds.bottom); 207 actualAnchor.setScrollX(anchor.getScrollX()); 208 actualAnchor.setScrollY(anchor.getScrollY()); 209 210 anchor.setOnScrollChangeListener((v, scrollX, scrollY, oldScrollX, oldScrollY) -> { 211 mLocationOnScreen[0] = mLocationOnScreen[0] - (scrollX - oldScrollX); 212 mLocationOnScreen[1] = mLocationOnScreen[1] - (scrollY - oldScrollY); 213 }); 214 actualAnchor.setWillNotDraw(true); 215 } else { 216 actualAnchor = anchor; 217 } 218 219 if (!mFullScreen) { 220 // No fullscreen window animation is controlled by PopupWindow. 221 setAnimationStyle(-1); 222 } else if (height == LayoutParams.MATCH_PARENT) { 223 // Complete fullscreen autofill window has no animation. 224 setAnimationStyle(0); 225 } else { 226 // Slide half screen height autofill window from bottom. 227 setAnimationStyle(com.android.internal.R.style.AutofillHalfScreenAnimation); 228 } 229 if (!isShowing()) { 230 setWidth(width); 231 setHeight(height); 232 showAsDropDown(actualAnchor, offsetX, offsetY); 233 } else { 234 update(actualAnchor, offsetX, offsetY, width, height); 235 } 236 } 237 238 @Override 239 protected void update(View anchor, WindowManager.LayoutParams params) { 240 final int layoutDirection = anchor != null ? anchor.getLayoutDirection() 241 : View.LAYOUT_DIRECTION_LOCALE; 242 mWindowPresenter.show(params, getTransitionEpicenter(), isLayoutInsetDecor(), 243 layoutDirection); 244 } 245 246 @Override 247 protected boolean findDropDownPosition(View anchor, LayoutParams outParams, 248 int xOffset, int yOffset, int width, int height, int gravity, boolean allowScroll) { 249 if (mFullScreen) { 250 // In fullscreen mode, don't need consider the anchor view. 251 outParams.x = xOffset; 252 outParams.y = yOffset; 253 outParams.width = width; 254 outParams.height = height; 255 outParams.gravity = gravity; 256 return false; 257 } 258 return super.findDropDownPosition(anchor, outParams, xOffset, yOffset, 259 width, height, gravity, allowScroll); 260 } 261 262 @Override 263 public void showAsDropDown(View anchor, int xoff, int yoff, int gravity) { 264 if (sVerbose) { 265 Log.v(TAG, "showAsDropDown(): anchor=" + anchor + ", xoff=" + xoff + ", yoff=" + yoff 266 + ", isShowing(): " + isShowing()); 267 } 268 if (isShowing()) { 269 return; 270 } 271 272 setShowing(true); 273 setDropDown(true); 274 attachToAnchor(anchor, xoff, yoff, gravity); 275 final WindowManager.LayoutParams p = mWindowLayoutParams = createPopupLayoutParams( 276 anchor.getWindowToken()); 277 final boolean aboveAnchor = findDropDownPosition(anchor, p, xoff, yoff, 278 p.width, p.height, gravity, getAllowScrollingAnchorParent()); 279 updateAboveAnchor(aboveAnchor); 280 p.accessibilityIdOfAnchor = anchor.getAccessibilityViewId(); 281 p.packageName = anchor.getContext().getPackageName(); 282 mWindowPresenter.show(p, getTransitionEpicenter(), isLayoutInsetDecor(), 283 anchor.getLayoutDirection()); 284 } 285 286 @Override 287 protected void attachToAnchor(View anchor, int xoff, int yoff, int gravity) { 288 super.attachToAnchor(anchor, xoff, yoff, gravity); 289 anchor.addOnAttachStateChangeListener(mOnAttachStateChangeListener); 290 } 291 292 @Override 293 protected void detachFromAnchor() { 294 final View anchor = getAnchor(); 295 if (anchor != null) { 296 anchor.removeOnAttachStateChangeListener(mOnAttachStateChangeListener); 297 } 298 super.detachFromAnchor(); 299 } 300 301 @Override 302 public void dismiss() { 303 if (!isShowing() || isTransitioningToDismiss()) { 304 return; 305 } 306 307 setShowing(false); 308 setTransitioningToDismiss(true); 309 310 mWindowPresenter.hide(getTransitionEpicenter()); 311 detachFromAnchor(); 312 if (getOnDismissListener() != null) { 313 getOnDismissListener().onDismiss(); 314 } 315 } 316 317 @Override 318 public int getAnimationStyle() { 319 throw new IllegalStateException("You can't call this!"); 320 } 321 322 @Override 323 public Drawable getBackground() { 324 throw new IllegalStateException("You can't call this!"); 325 } 326 327 @Override 328 public View getContentView() { 329 throw new IllegalStateException("You can't call this!"); 330 } 331 332 @Override 333 public float getElevation() { 334 throw new IllegalStateException("You can't call this!"); 335 } 336 337 @Override 338 public Transition getEnterTransition() { 339 throw new IllegalStateException("You can't call this!"); 340 } 341 342 @Override 343 public Transition getExitTransition() { 344 throw new IllegalStateException("You can't call this!"); 345 } 346 347 @Override 348 public void setBackgroundDrawable(Drawable background) { 349 throw new IllegalStateException("You can't call this!"); 350 } 351 352 @Override 353 public void setContentView(View contentView) { 354 if (contentView != null) { 355 throw new IllegalStateException("You can't call this!"); 356 } 357 } 358 359 @Override 360 public void setElevation(float elevation) { 361 throw new IllegalStateException("You can't call this!"); 362 } 363 364 @Override 365 public void setEnterTransition(Transition enterTransition) { 366 throw new IllegalStateException("You can't call this!"); 367 } 368 369 @Override 370 public void setExitTransition(Transition exitTransition) { 371 throw new IllegalStateException("You can't call this!"); 372 } 373 374 @Override 375 public void setTouchInterceptor(OnTouchListener l) { 376 throw new IllegalStateException("You can't call this!"); 377 } 378 379 /** 380 * Contract between the popup window and a presenter that is responsible for 381 * showing/hiding/updating the actual window. 382 * 383 * <p>This can be useful if the anchor is in one process and the backing window is owned by 384 * another process. 385 */ 386 private class WindowPresenter { 387 final IAutofillWindowPresenter mPresenter; 388 389 WindowPresenter(IAutofillWindowPresenter presenter) { 390 mPresenter = presenter; 391 } 392 393 /** 394 * Shows the backing window. 395 * 396 * @param p The window layout params. 397 * @param transitionEpicenter The transition epicenter if animating. 398 * @param fitsSystemWindows Whether the content view should account for system decorations. 399 * @param layoutDirection The content layout direction to be consistent with the anchor. 400 */ 401 void show(WindowManager.LayoutParams p, Rect transitionEpicenter, boolean fitsSystemWindows, 402 int layoutDirection) { 403 try { 404 mPresenter.show(p, transitionEpicenter, fitsSystemWindows, layoutDirection); 405 } catch (RemoteException e) { 406 Log.w(TAG, "Error showing fill window", e); 407 e.rethrowFromSystemServer(); 408 } 409 } 410 411 /** 412 * Hides the backing window. 413 * 414 * @param transitionEpicenter The transition epicenter if animating. 415 */ 416 void hide(Rect transitionEpicenter) { 417 try { 418 mPresenter.hide(transitionEpicenter); 419 } catch (RemoteException e) { 420 Log.w(TAG, "Error hiding fill window", e); 421 e.rethrowFromSystemServer(); 422 } 423 } 424 } 425} 426