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