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.Point;
23 import android.graphics.Rect;
24 import android.graphics.drawable.Drawable;
25 import android.os.IBinder;
26 import android.os.RemoteException;
27 import android.transition.Transition;
28 import android.util.Log;
29 import android.view.View;
30 import android.view.View.OnTouchListener;
31 import android.view.ViewTreeObserver;
32 import android.view.WindowManager;
33 import android.view.WindowManager.LayoutParams;
34 import 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  */
44 public 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      */
AutofillPopupWindow(@onNull IAutofillWindowPresenter presenter)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
hasContentView()90     protected boolean hasContentView() {
91         return true;
92     }
93 
94     @Override
hasDecorView()95     protected boolean hasDecorView() {
96         return true;
97     }
98 
99     @Override
getDecorViewLayoutParams()100     protected LayoutParams getDecorViewLayoutParams() {
101         return mWindowLayoutParams;
102     }
103 
104     /**
105      * The effective {@code update} method that should be called by its clients.
106      */
update(View anchor, int offsetX, int offsetY, int width, int height, Rect virtualBounds)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
update(View anchor, WindowManager.LayoutParams params)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
findDropDownPosition(View anchor, LayoutParams outParams, int xOffset, int yOffset, int width, int height, int gravity, boolean allowScroll)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
showAsDropDown(View anchor, int xoff, int yoff, int gravity)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
attachToAnchor(View anchor, int xoff, int yoff, int gravity)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
detachFromAnchor()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
dismiss()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
getAnimationStyle()318     public int getAnimationStyle() {
319         throw new IllegalStateException("You can't call this!");
320     }
321 
322     @Override
getBackground()323     public Drawable getBackground() {
324         throw new IllegalStateException("You can't call this!");
325     }
326 
327     @Override
getContentView()328     public View getContentView() {
329         throw new IllegalStateException("You can't call this!");
330     }
331 
332     @Override
getElevation()333     public float getElevation() {
334         throw new IllegalStateException("You can't call this!");
335     }
336 
337     @Override
getEnterTransition()338     public Transition getEnterTransition() {
339         throw new IllegalStateException("You can't call this!");
340     }
341 
342     @Override
getExitTransition()343     public Transition getExitTransition() {
344         throw new IllegalStateException("You can't call this!");
345     }
346 
347     @Override
setBackgroundDrawable(Drawable background)348     public void setBackgroundDrawable(Drawable background) {
349         throw new IllegalStateException("You can't call this!");
350     }
351 
352     @Override
setContentView(View contentView)353     public void setContentView(View contentView) {
354         if (contentView != null) {
355             throw new IllegalStateException("You can't call this!");
356         }
357     }
358 
359     @Override
setElevation(float elevation)360     public void setElevation(float elevation) {
361         throw new IllegalStateException("You can't call this!");
362     }
363 
364     @Override
setEnterTransition(Transition enterTransition)365     public void setEnterTransition(Transition enterTransition) {
366         throw new IllegalStateException("You can't call this!");
367     }
368 
369     @Override
setExitTransition(Transition exitTransition)370     public void setExitTransition(Transition exitTransition) {
371         throw new IllegalStateException("You can't call this!");
372     }
373 
374     @Override
setTouchInterceptor(OnTouchListener l)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 
WindowPresenter(IAutofillWindowPresenter presenter)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          */
show(WindowManager.LayoutParams p, Rect transitionEpicenter, boolean fitsSystemWindows, int layoutDirection)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          */
hide(Rect transitionEpicenter)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