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