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 17 package android.view.autofill; 18 19 import static android.view.autofill.Helper.sVerbose; 20 21 import android.annotation.NonNull; 22 import android.graphics.Rect; 23 import android.graphics.drawable.Drawable; 24 import android.os.IBinder; 25 import android.os.RemoteException; 26 import android.transition.Transition; 27 import android.util.Log; 28 import android.view.View; 29 import android.view.View.OnTouchListener; 30 import android.view.ViewTreeObserver; 31 import android.view.WindowManager; 32 import android.view.WindowManager.LayoutParams; 33 import 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 */ 43 public class AutofillPopupWindow extends PopupWindow { 44 45 private static final String TAG = "AutofillPopupWindow"; 46 47 private final WindowPresenter mWindowPresenter; 48 private WindowManager.LayoutParams mWindowLayoutParams; 49 50 /** 51 * Creates a popup window with a presenter owning the window and responsible for 52 * showing/hiding/updating the backing window. This can be useful of the window is 53 * being shown by another process while the popup logic is in the process hosting 54 * the anchor view. 55 * <p> 56 * Using this constructor means that the presenter completely owns the content of 57 * the window and the following methods manipulating the window content shouldn't 58 * be used: {@link #getEnterTransition()}, {@link #setEnterTransition(Transition)}, 59 * {@link #getExitTransition()}, {@link #setExitTransition(Transition)}, 60 * {@link #getContentView()}, {@link #setContentView(View)}, {@link #getBackground()}, 61 * {@link #setBackgroundDrawable(Drawable)}, {@link #getElevation()}, 62 * {@link #setElevation(float)}, ({@link #getAnimationStyle()}, 63 * {@link #setAnimationStyle(int)}, {@link #setTouchInterceptor(OnTouchListener)}.</p> 64 */ AutofillPopupWindow(@onNull IAutofillWindowPresenter presenter)65 public AutofillPopupWindow(@NonNull IAutofillWindowPresenter presenter) { 66 mWindowPresenter = new WindowPresenter(presenter); 67 68 setOutsideTouchable(true); 69 setInputMethodMode(INPUT_METHOD_NEEDED); 70 } 71 72 @Override hasContentView()73 protected boolean hasContentView() { 74 return true; 75 } 76 77 @Override hasDecorView()78 protected boolean hasDecorView() { 79 return true; 80 } 81 82 @Override getDecorViewLayoutParams()83 protected LayoutParams getDecorViewLayoutParams() { 84 return mWindowLayoutParams; 85 } 86 87 /** 88 * The effective {@code update} method that should be called by its clients. 89 */ update(View anchor, int offsetX, int offsetY, int width, int height, Rect virtualBounds)90 public void update(View anchor, int offsetX, int offsetY, int width, int height, 91 Rect virtualBounds) { 92 // If we are showing the popup for a virtual view we use a fake view which 93 // delegates to the anchor but present itself with the same bounds as the 94 // virtual view. This ensures that the location logic in popup works 95 // symmetrically when the dropdown is below and above the anchor. 96 final View actualAnchor; 97 if (virtualBounds != null) { 98 actualAnchor = new View(anchor.getContext()) { 99 @Override 100 public void getLocationOnScreen(int[] location) { 101 location[0] = virtualBounds.left; 102 location[1] = virtualBounds.top; 103 } 104 105 @Override 106 public int getAccessibilityViewId() { 107 return anchor.getAccessibilityViewId(); 108 } 109 110 @Override 111 public ViewTreeObserver getViewTreeObserver() { 112 return anchor.getViewTreeObserver(); 113 } 114 115 @Override 116 public IBinder getApplicationWindowToken() { 117 return anchor.getApplicationWindowToken(); 118 } 119 120 @Override 121 public View getRootView() { 122 return anchor.getRootView(); 123 } 124 125 @Override 126 public int getLayoutDirection() { 127 return anchor.getLayoutDirection(); 128 } 129 130 @Override 131 public void getWindowDisplayFrame(Rect outRect) { 132 anchor.getWindowDisplayFrame(outRect); 133 } 134 135 @Override 136 public void addOnAttachStateChangeListener( 137 OnAttachStateChangeListener listener) { 138 anchor.addOnAttachStateChangeListener(listener); 139 } 140 141 @Override 142 public void removeOnAttachStateChangeListener( 143 OnAttachStateChangeListener listener) { 144 anchor.removeOnAttachStateChangeListener(listener); 145 } 146 147 @Override 148 public boolean isAttachedToWindow() { 149 return anchor.isAttachedToWindow(); 150 } 151 152 @Override 153 public boolean requestRectangleOnScreen(Rect rectangle, boolean immediate) { 154 return anchor.requestRectangleOnScreen(rectangle, immediate); 155 } 156 157 @Override 158 public IBinder getWindowToken() { 159 return anchor.getWindowToken(); 160 } 161 }; 162 163 actualAnchor.setLeftTopRightBottom( 164 virtualBounds.left, virtualBounds.top, 165 virtualBounds.right, virtualBounds.bottom); 166 actualAnchor.setScrollX(anchor.getScrollX()); 167 actualAnchor.setScrollY(anchor.getScrollY()); 168 } else { 169 actualAnchor = anchor; 170 } 171 172 if (!isShowing()) { 173 setWidth(width); 174 setHeight(height); 175 showAsDropDown(actualAnchor, offsetX, offsetY); 176 } else { 177 update(actualAnchor, offsetX, offsetY, width, height); 178 } 179 } 180 181 @Override update(View anchor, WindowManager.LayoutParams params)182 protected void update(View anchor, WindowManager.LayoutParams params) { 183 final int layoutDirection = anchor != null ? anchor.getLayoutDirection() 184 : View.LAYOUT_DIRECTION_LOCALE; 185 mWindowPresenter.show(params, getTransitionEpicenter(), isLayoutInsetDecor(), 186 layoutDirection); 187 } 188 189 @Override showAsDropDown(View anchor, int xoff, int yoff, int gravity)190 public void showAsDropDown(View anchor, int xoff, int yoff, int gravity) { 191 if (sVerbose) { 192 Log.v(TAG, "showAsDropDown(): anchor=" + anchor + ", xoff=" + xoff + ", yoff=" + yoff 193 + ", isShowing(): " + isShowing()); 194 } 195 if (isShowing()) { 196 return; 197 } 198 199 setShowing(true); 200 setDropDown(true); 201 attachToAnchor(anchor, xoff, yoff, gravity); 202 final WindowManager.LayoutParams p = mWindowLayoutParams = createPopupLayoutParams( 203 anchor.getWindowToken()); 204 final boolean aboveAnchor = findDropDownPosition(anchor, p, xoff, yoff, 205 p.width, p.height, gravity, getAllowScrollingAnchorParent()); 206 updateAboveAnchor(aboveAnchor); 207 p.accessibilityIdOfAnchor = anchor.getAccessibilityViewId(); 208 p.packageName = anchor.getContext().getPackageName(); 209 mWindowPresenter.show(p, getTransitionEpicenter(), isLayoutInsetDecor(), 210 anchor.getLayoutDirection()); 211 return; 212 } 213 214 @Override dismiss()215 public void dismiss() { 216 if (!isShowing() || isTransitioningToDismiss()) { 217 return; 218 } 219 220 setShowing(false); 221 setTransitioningToDismiss(true); 222 223 mWindowPresenter.hide(getTransitionEpicenter()); 224 detachFromAnchor(); 225 if (getOnDismissListener() != null) { 226 getOnDismissListener().onDismiss(); 227 } 228 } 229 230 @Override getAnimationStyle()231 public int getAnimationStyle() { 232 throw new IllegalStateException("You can't call this!"); 233 } 234 235 @Override getBackground()236 public Drawable getBackground() { 237 throw new IllegalStateException("You can't call this!"); 238 } 239 240 @Override getContentView()241 public View getContentView() { 242 throw new IllegalStateException("You can't call this!"); 243 } 244 245 @Override getElevation()246 public float getElevation() { 247 throw new IllegalStateException("You can't call this!"); 248 } 249 250 @Override getEnterTransition()251 public Transition getEnterTransition() { 252 throw new IllegalStateException("You can't call this!"); 253 } 254 255 @Override getExitTransition()256 public Transition getExitTransition() { 257 throw new IllegalStateException("You can't call this!"); 258 } 259 260 @Override setAnimationStyle(int animationStyle)261 public void setAnimationStyle(int animationStyle) { 262 throw new IllegalStateException("You can't call this!"); 263 } 264 265 @Override setBackgroundDrawable(Drawable background)266 public void setBackgroundDrawable(Drawable background) { 267 throw new IllegalStateException("You can't call this!"); 268 } 269 270 @Override setContentView(View contentView)271 public void setContentView(View contentView) { 272 if (contentView != null) { 273 throw new IllegalStateException("You can't call this!"); 274 } 275 } 276 277 @Override setElevation(float elevation)278 public void setElevation(float elevation) { 279 throw new IllegalStateException("You can't call this!"); 280 } 281 282 @Override setEnterTransition(Transition enterTransition)283 public void setEnterTransition(Transition enterTransition) { 284 throw new IllegalStateException("You can't call this!"); 285 } 286 287 @Override setExitTransition(Transition exitTransition)288 public void setExitTransition(Transition exitTransition) { 289 throw new IllegalStateException("You can't call this!"); 290 } 291 292 @Override setTouchInterceptor(OnTouchListener l)293 public void setTouchInterceptor(OnTouchListener l) { 294 throw new IllegalStateException("You can't call this!"); 295 } 296 297 /** 298 * Contract between the popup window and a presenter that is responsible for 299 * showing/hiding/updating the actual window. 300 * 301 * <p>This can be useful if the anchor is in one process and the backing window is owned by 302 * another process. 303 */ 304 private class WindowPresenter { 305 final IAutofillWindowPresenter mPresenter; 306 WindowPresenter(IAutofillWindowPresenter presenter)307 WindowPresenter(IAutofillWindowPresenter presenter) { 308 mPresenter = presenter; 309 } 310 311 /** 312 * Shows the backing window. 313 * 314 * @param p The window layout params. 315 * @param transitionEpicenter The transition epicenter if animating. 316 * @param fitsSystemWindows Whether the content view should account for system decorations. 317 * @param layoutDirection The content layout direction to be consistent with the anchor. 318 */ show(WindowManager.LayoutParams p, Rect transitionEpicenter, boolean fitsSystemWindows, int layoutDirection)319 void show(WindowManager.LayoutParams p, Rect transitionEpicenter, boolean fitsSystemWindows, 320 int layoutDirection) { 321 try { 322 mPresenter.show(p, transitionEpicenter, fitsSystemWindows, layoutDirection); 323 } catch (RemoteException e) { 324 Log.w(TAG, "Error showing fill window", e); 325 e.rethrowFromSystemServer(); 326 } 327 } 328 329 /** 330 * Hides the backing window. 331 * 332 * @param transitionEpicenter The transition epicenter if animating. 333 */ hide(Rect transitionEpicenter)334 void hide(Rect transitionEpicenter) { 335 try { 336 mPresenter.hide(transitionEpicenter); 337 } catch (RemoteException e) { 338 Log.w(TAG, "Error hiding fill window", e); 339 e.rethrowFromSystemServer(); 340 } 341 } 342 } 343 } 344