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