blob: e80fdd93542cc06ef889040f95f23335b4792ec1 [file] [log] [blame]
Felipe Leme4753bb02017-03-22 20:24:00 -07001/*
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
Felipe Leme8697a312017-05-30 09:35:17 -070019import static android.view.autofill.Helper.sVerbose;
20
Felipe Leme4753bb02017-03-22 20:24:00 -070021import android.annotation.NonNull;
22import android.graphics.Rect;
23import android.graphics.drawable.Drawable;
Svet Ganov374cae12017-05-10 13:42:33 -070024import android.os.IBinder;
Felipe Leme4753bb02017-03-22 20:24:00 -070025import android.os.RemoteException;
26import android.transition.Transition;
27import android.util.Log;
28import android.view.View;
29import android.view.View.OnTouchListener;
Svet Ganov374cae12017-05-10 13:42:33 -070030import android.view.ViewTreeObserver;
Felipe Leme4753bb02017-03-22 20:24:00 -070031import 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
Svet Ganov77150c52017-09-22 17:19:04 -070050 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
Felipe Leme4753bb02017-03-22 20:24:00 -070063 /**
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 */
78 public AutofillPopupWindow(@NonNull IAutofillWindowPresenter presenter) {
79 mWindowPresenter = new WindowPresenter(presenter);
80
Dake Gu67decfa2017-12-27 11:48:08 -080081 setTouchModal(false);
Felipe Leme4753bb02017-03-22 20:24:00 -070082 setOutsideTouchable(true);
Dake Gu67decfa2017-12-27 11:48:08 -080083 setInputMethodMode(INPUT_METHOD_NOT_NEEDED);
84 setFocusable(true);
Felipe Leme4753bb02017-03-22 20:24:00 -070085 }
86
87 @Override
88 protected boolean hasContentView() {
89 return true;
90 }
91
92 @Override
93 protected boolean hasDecorView() {
94 return true;
95 }
96
97 @Override
98 protected LayoutParams getDecorViewLayoutParams() {
99 return mWindowLayoutParams;
100 }
101
102 /**
103 * The effective {@code update} method that should be called by its clients.
104 */
105 public void update(View anchor, int offsetX, int offsetY, int width, int height,
Svet Ganov374cae12017-05-10 13:42:33 -0700106 Rect virtualBounds) {
107 // If we are showing the popup for a virtual view we use a fake view which
108 // delegates to the anchor but present itself with the same bounds as the
109 // virtual view. This ensures that the location logic in popup works
110 // symmetrically when the dropdown is below and above the anchor.
111 final View actualAnchor;
112 if (virtualBounds != null) {
Svet Ganov05b7b462017-11-29 22:45:11 -0800113 final int[] mLocationOnScreen = new int[] {virtualBounds.left, virtualBounds.top};
Svet Ganov374cae12017-05-10 13:42:33 -0700114 actualAnchor = new View(anchor.getContext()) {
115 @Override
116 public void getLocationOnScreen(int[] location) {
Svet Ganov05b7b462017-11-29 22:45:11 -0800117 location[0] = mLocationOnScreen[0];
118 location[1] = mLocationOnScreen[1];
Svet Ganov374cae12017-05-10 13:42:33 -0700119 }
120
121 @Override
122 public int getAccessibilityViewId() {
123 return anchor.getAccessibilityViewId();
124 }
125
126 @Override
127 public ViewTreeObserver getViewTreeObserver() {
128 return anchor.getViewTreeObserver();
129 }
130
131 @Override
132 public IBinder getApplicationWindowToken() {
133 return anchor.getApplicationWindowToken();
134 }
135
136 @Override
137 public View getRootView() {
138 return anchor.getRootView();
139 }
140
141 @Override
142 public int getLayoutDirection() {
143 return anchor.getLayoutDirection();
144 }
145
146 @Override
147 public void getWindowDisplayFrame(Rect outRect) {
148 anchor.getWindowDisplayFrame(outRect);
149 }
150
151 @Override
152 public void addOnAttachStateChangeListener(
153 OnAttachStateChangeListener listener) {
154 anchor.addOnAttachStateChangeListener(listener);
155 }
156
157 @Override
158 public void removeOnAttachStateChangeListener(
159 OnAttachStateChangeListener listener) {
160 anchor.removeOnAttachStateChangeListener(listener);
161 }
162
163 @Override
164 public boolean isAttachedToWindow() {
165 return anchor.isAttachedToWindow();
166 }
167
168 @Override
169 public boolean requestRectangleOnScreen(Rect rectangle, boolean immediate) {
170 return anchor.requestRectangleOnScreen(rectangle, immediate);
171 }
172
173 @Override
174 public IBinder getWindowToken() {
175 return anchor.getWindowToken();
176 }
177 };
178
179 actualAnchor.setLeftTopRightBottom(
180 virtualBounds.left, virtualBounds.top,
181 virtualBounds.right, virtualBounds.bottom);
182 actualAnchor.setScrollX(anchor.getScrollX());
183 actualAnchor.setScrollY(anchor.getScrollY());
Svet Ganov05b7b462017-11-29 22:45:11 -0800184
185 anchor.setOnScrollChangeListener((v, scrollX, scrollY, oldScrollX, oldScrollY) -> {
186 mLocationOnScreen[0] = mLocationOnScreen[0] - (scrollX - oldScrollX);
187 mLocationOnScreen[1] = mLocationOnScreen[1] - (scrollY - oldScrollY);
188 });
189 actualAnchor.setWillNotDraw(true);
Svet Ganov374cae12017-05-10 13:42:33 -0700190 } else {
191 actualAnchor = anchor;
192 }
193
Felipe Leme4753bb02017-03-22 20:24:00 -0700194 if (!isShowing()) {
195 setWidth(width);
196 setHeight(height);
Svet Ganov374cae12017-05-10 13:42:33 -0700197 showAsDropDown(actualAnchor, offsetX, offsetY);
Felipe Leme4753bb02017-03-22 20:24:00 -0700198 } else {
Svet Ganov374cae12017-05-10 13:42:33 -0700199 update(actualAnchor, offsetX, offsetY, width, height);
Felipe Leme4753bb02017-03-22 20:24:00 -0700200 }
201 }
202
203 @Override
204 protected void update(View anchor, WindowManager.LayoutParams params) {
205 final int layoutDirection = anchor != null ? anchor.getLayoutDirection()
206 : View.LAYOUT_DIRECTION_LOCALE;
207 mWindowPresenter.show(params, getTransitionEpicenter(), isLayoutInsetDecor(),
208 layoutDirection);
209 }
210
211 @Override
212 public void showAsDropDown(View anchor, int xoff, int yoff, int gravity) {
Felipe Leme8697a312017-05-30 09:35:17 -0700213 if (sVerbose) {
214 Log.v(TAG, "showAsDropDown(): anchor=" + anchor + ", xoff=" + xoff + ", yoff=" + yoff
215 + ", isShowing(): " + isShowing());
216 }
Felipe Leme4753bb02017-03-22 20:24:00 -0700217 if (isShowing()) {
218 return;
219 }
220
221 setShowing(true);
222 setDropDown(true);
223 attachToAnchor(anchor, xoff, yoff, gravity);
224 final WindowManager.LayoutParams p = mWindowLayoutParams = createPopupLayoutParams(
225 anchor.getWindowToken());
226 final boolean aboveAnchor = findDropDownPosition(anchor, p, xoff, yoff,
227 p.width, p.height, gravity, getAllowScrollingAnchorParent());
228 updateAboveAnchor(aboveAnchor);
229 p.accessibilityIdOfAnchor = anchor.getAccessibilityViewId();
230 p.packageName = anchor.getContext().getPackageName();
231 mWindowPresenter.show(p, getTransitionEpicenter(), isLayoutInsetDecor(),
232 anchor.getLayoutDirection());
Svet Ganov77150c52017-09-22 17:19:04 -0700233 }
234
235 @Override
236 protected void attachToAnchor(View anchor, int xoff, int yoff, int gravity) {
237 super.attachToAnchor(anchor, xoff, yoff, gravity);
238 anchor.addOnAttachStateChangeListener(mOnAttachStateChangeListener);
239 }
240
241 @Override
242 protected void detachFromAnchor() {
243 final View anchor = getAnchor();
244 if (anchor != null) {
245 anchor.removeOnAttachStateChangeListener(mOnAttachStateChangeListener);
246 }
247 super.detachFromAnchor();
Felipe Leme4753bb02017-03-22 20:24:00 -0700248 }
249
250 @Override
251 public void dismiss() {
252 if (!isShowing() || isTransitioningToDismiss()) {
253 return;
254 }
255
256 setShowing(false);
257 setTransitioningToDismiss(true);
258
259 mWindowPresenter.hide(getTransitionEpicenter());
260 detachFromAnchor();
261 if (getOnDismissListener() != null) {
262 getOnDismissListener().onDismiss();
263 }
264 }
265
266 @Override
267 public int getAnimationStyle() {
268 throw new IllegalStateException("You can't call this!");
269 }
270
271 @Override
272 public Drawable getBackground() {
273 throw new IllegalStateException("You can't call this!");
274 }
275
276 @Override
277 public View getContentView() {
278 throw new IllegalStateException("You can't call this!");
279 }
280
281 @Override
282 public float getElevation() {
283 throw new IllegalStateException("You can't call this!");
284 }
285
286 @Override
287 public Transition getEnterTransition() {
288 throw new IllegalStateException("You can't call this!");
289 }
290
291 @Override
292 public Transition getExitTransition() {
293 throw new IllegalStateException("You can't call this!");
294 }
295
296 @Override
297 public void setAnimationStyle(int animationStyle) {
298 throw new IllegalStateException("You can't call this!");
299 }
300
301 @Override
302 public void setBackgroundDrawable(Drawable background) {
303 throw new IllegalStateException("You can't call this!");
304 }
305
306 @Override
307 public void setContentView(View contentView) {
308 if (contentView != null) {
309 throw new IllegalStateException("You can't call this!");
310 }
311 }
312
313 @Override
314 public void setElevation(float elevation) {
315 throw new IllegalStateException("You can't call this!");
316 }
317
318 @Override
319 public void setEnterTransition(Transition enterTransition) {
320 throw new IllegalStateException("You can't call this!");
321 }
322
323 @Override
324 public void setExitTransition(Transition exitTransition) {
325 throw new IllegalStateException("You can't call this!");
326 }
327
328 @Override
329 public void setTouchInterceptor(OnTouchListener l) {
330 throw new IllegalStateException("You can't call this!");
331 }
332
333 /**
334 * Contract between the popup window and a presenter that is responsible for
335 * showing/hiding/updating the actual window.
336 *
337 * <p>This can be useful if the anchor is in one process and the backing window is owned by
338 * another process.
339 */
340 private class WindowPresenter {
341 final IAutofillWindowPresenter mPresenter;
342
343 WindowPresenter(IAutofillWindowPresenter presenter) {
344 mPresenter = presenter;
345 }
346
347 /**
348 * Shows the backing window.
349 *
350 * @param p The window layout params.
351 * @param transitionEpicenter The transition epicenter if animating.
352 * @param fitsSystemWindows Whether the content view should account for system decorations.
353 * @param layoutDirection The content layout direction to be consistent with the anchor.
354 */
355 void show(WindowManager.LayoutParams p, Rect transitionEpicenter, boolean fitsSystemWindows,
356 int layoutDirection) {
357 try {
358 mPresenter.show(p, transitionEpicenter, fitsSystemWindows, layoutDirection);
359 } catch (RemoteException e) {
360 Log.w(TAG, "Error showing fill window", e);
361 e.rethrowFromSystemServer();
362 }
363 }
364
365 /**
366 * Hides the backing window.
367 *
368 * @param transitionEpicenter The transition epicenter if animating.
369 */
370 void hide(Rect transitionEpicenter) {
371 try {
372 mPresenter.hide(transitionEpicenter);
373 } catch (RemoteException e) {
374 Log.w(TAG, "Error hiding fill window", e);
375 e.rethrowFromSystemServer();
376 }
377 }
378 }
379}