blob: bad7ddd3193b6f7aae3f4e8128c358b0ee689446 [file] [log] [blame]
Felipe Leme284ad1c2018-11-15 18:16:12 -08001/*
2 * Copyright (C) 2018 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 */
Felipe Leme749b8892018-12-03 16:30:30 -080016package android.service.autofill.augmented;
Felipe Leme284ad1c2018-11-15 18:16:12 -080017
Felipe Leme749b8892018-12-03 16:30:30 -080018import static android.service.autofill.augmented.AugmentedAutofillService.DEBUG;
Felipe Leme284ad1c2018-11-15 18:16:12 -080019
20import android.annotation.LongDef;
21import android.annotation.NonNull;
22import android.annotation.SystemApi;
23import android.app.Dialog;
24import android.graphics.Rect;
Felipe Leme749b8892018-12-03 16:30:30 -080025import android.service.autofill.augmented.AugmentedAutofillService.AutofillProxy;
26import android.service.autofill.augmented.PresentationParams.Area;
Felipe Leme284ad1c2018-11-15 18:16:12 -080027import android.util.Log;
28import android.view.Gravity;
Feng Cao010b9932018-12-05 18:11:41 -080029import android.view.MotionEvent;
Felipe Leme284ad1c2018-11-15 18:16:12 -080030import android.view.View;
31import android.view.ViewGroup;
32import android.view.Window;
33import android.view.WindowManager;
34
35import com.android.internal.annotations.GuardedBy;
36import com.android.internal.util.Preconditions;
37
Felipe Lemebf93cdc2018-12-03 12:05:58 -080038import dalvik.system.CloseGuard;
39
Felipe Leme284ad1c2018-11-15 18:16:12 -080040import java.io.PrintWriter;
41import java.lang.annotation.Retention;
42import java.lang.annotation.RetentionPolicy;
43
44/**
45 * Handle to a window used to display the augmented autofill UI.
46 *
47 * <p>The steps to create an augmented autofill UI are:
48 *
49 * <ol>
50 * <li>Gets the {@link PresentationParams} from the {@link FillRequest}.
51 * <li>Gets the {@link Area} to display the UI (for example, through
52 * {@link PresentationParams#getSuggestionArea()}.
53 * <li>Creates a {@link View} that must fit in the {@link Area#getBounds() area boundaries}.
54 * <li>Set the proper listeners to the view (for example, a click listener that
55 * triggers {@link FillController#autofill(java.util.List)}
56 * <li>Call {@link #update(Area, View, long)} with these arguments.
57 * <li>Create a {@link FillResponse} with the {@link FillWindow}.
58 * <li>Pass such {@link FillResponse} to {@link FillCallback#onSuccess(FillResponse)}.
59 * </ol>
60 *
61 * @hide
62 */
63@SystemApi
Felipe Leme749b8892018-12-03 16:30:30 -080064public final class FillWindow implements AutoCloseable {
Felipe Leme284ad1c2018-11-15 18:16:12 -080065 private static final String TAG = "FillWindow";
66
67 /** Indicates the data being shown is a physical address */
68 public static final long FLAG_METADATA_ADDRESS = 0x1;
69
70 // TODO(b/111330312): add moar flags
71
72 /** @hide */
73 @LongDef(prefix = { "FLAG" }, value = {
74 FLAG_METADATA_ADDRESS,
75 })
76 @Retention(RetentionPolicy.SOURCE)
77 @interface Flags{}
78
79 private final Object mLock = new Object();
Felipe Lemebf93cdc2018-12-03 12:05:58 -080080 private final CloseGuard mCloseGuard = CloseGuard.get();
Felipe Leme284ad1c2018-11-15 18:16:12 -080081
82 @GuardedBy("mLock")
83 private Dialog mDialog;
84
85 @GuardedBy("mLock")
86 private boolean mDestroyed;
87
Felipe Lemebf93cdc2018-12-03 12:05:58 -080088 private AutofillProxy mProxy;
89
Felipe Leme284ad1c2018-11-15 18:16:12 -080090 /**
91 * Updates the content of the window.
92 *
93 * @param rootView new root view
94 * @param area coordinates to render the view.
95 * @param flags optional flags such as metadata of what will be rendered in the window. The
96 * Smart Suggestion host might decide whether or not to render the UI based on them.
97 *
98 * @return boolean whether the window was updated or not.
99 *
100 * @throws IllegalArgumentException if the area is not compatible with this window
101 */
102 public boolean update(@NonNull Area area, @NonNull View rootView, @Flags long flags) {
103 if (DEBUG) {
104 Log.d(TAG, "Updating " + area + " + with " + rootView);
105 }
106 // TODO(b/111330312): add test case for null
107 Preconditions.checkNotNull(area);
108 Preconditions.checkNotNull(rootView);
109 // TODO(b/111330312): must check the area is a valid object returned by
110 // SmartSuggestionParams, throw IAE if not
111
112 // TODO(b/111330312): must some how pass metadata to the SmartSuggestiongs provider
113
114
115 // TODO(b/111330312): use a SurfaceControl approach; for now, we're manually creating
116 // the window underneath the existing view.
117
118 final PresentationParams smartSuggestion = area.proxy.getSmartSuggestionParams();
119 if (smartSuggestion == null) {
120 Log.w(TAG, "No SmartSuggestionParams");
121 return false;
122 }
123
124 final Rect rect = area.getBounds();
125 if (rect == null) {
126 Log.wtf(TAG, "No Rect on SmartSuggestionParams");
127 return false;
128 }
129
130 synchronized (mLock) {
131 checkNotDestroyedLocked();
132
Felipe Lemebf93cdc2018-12-03 12:05:58 -0800133 mProxy = area.proxy;
134
Felipe Leme284ad1c2018-11-15 18:16:12 -0800135 // TODO(b/111330312): once we have the SurfaceControl approach, we should update the
136 // window instead of destroying. In fact, it might be better to allocate a full window
137 // initially, which is transparent (and let touches get through) everywhere but in the
138 // rect boundaries.
139 destroy();
140
141 // TODO(b/111330312): make sure all touch events are handled, window is always closed,
142 // etc.
143
Feng Cao010b9932018-12-05 18:11:41 -0800144 mDialog = new Dialog(rootView.getContext()) {
145 @Override
146 public boolean onTouchEvent(MotionEvent event) {
147 if (event.getAction() == MotionEvent.ACTION_OUTSIDE) {
148 FillWindow.this.destroy();
149 }
150 return false;
151 }
152 };
Felipe Lemebf93cdc2018-12-03 12:05:58 -0800153 mCloseGuard.open("destroy");
Felipe Leme284ad1c2018-11-15 18:16:12 -0800154 final Window window = mDialog.getWindow();
155 window.setType(WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY);
Feng Cao010b9932018-12-05 18:11:41 -0800156 // Makes sure touch outside the dialog is received by the window behind the dialog.
157 window.addFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL);
158 // Makes sure the touch outside the dialog is received by the dialog to dismiss it.
159 window.addFlags(WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH);
160 // Makes sure keyboard shows up.
161 window.addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
Felipe Leme284ad1c2018-11-15 18:16:12 -0800162
163 final int height = rect.bottom - rect.top;
164 final int width = rect.right - rect.left;
165 final WindowManager.LayoutParams windowParams = window.getAttributes();
Feng Cao023b84c2018-12-14 15:51:17 -0800166 windowParams.gravity = Gravity.TOP | Gravity.LEFT;
167 windowParams.y = rect.top + height;
Felipe Leme284ad1c2018-11-15 18:16:12 -0800168 windowParams.height = height;
169 windowParams.x = rect.left;
170 windowParams.width = width;
171
172 window.setAttributes(windowParams);
173 window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
Feng Cao023b84c2018-12-14 15:51:17 -0800174 window.setBackgroundDrawableResource(android.R.color.transparent);
Felipe Leme284ad1c2018-11-15 18:16:12 -0800175
176 mDialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
177 final ViewGroup.LayoutParams diagParams = new ViewGroup.LayoutParams(width, height);
178 mDialog.setContentView(rootView, diagParams);
179
180 if (DEBUG) {
181 Log.d(TAG, "Created FillWindow: params= " + smartSuggestion + " view=" + rootView);
182 }
183
Felipe Lemebf93cdc2018-12-03 12:05:58 -0800184 mProxy.setFillWindow(this);
Felipe Leme284ad1c2018-11-15 18:16:12 -0800185 return true;
186 }
187 }
188
189 /** @hide */
190 void show() {
191 // TODO(b/111330312): check if updated first / throw exception
192 if (DEBUG) Log.d(TAG, "show()");
193
194 synchronized (mLock) {
195 checkNotDestroyedLocked();
196 if (mDialog == null) {
197 throw new IllegalStateException("update() not called yet, or already destroyed()");
198 }
199
200 mDialog.show();
Felipe Lemebf93cdc2018-12-03 12:05:58 -0800201 if (mProxy != null) {
202 mProxy.report(AutofillProxy.REPORT_EVENT_UI_SHOWN);
203 }
Felipe Leme284ad1c2018-11-15 18:16:12 -0800204 }
205 }
206
207 /**
208 * Destroys the window.
209 *
210 * <p>Once destroyed, this window cannot be used anymore
211 */
212 public void destroy() {
Felipe Lemebf93cdc2018-12-03 12:05:58 -0800213 if (DEBUG) Log.d(TAG, "destroy(): mDestroyed=" + mDestroyed + " mDialog=" + mDialog);
Felipe Leme284ad1c2018-11-15 18:16:12 -0800214
215 synchronized (this) {
Felipe Lemebf93cdc2018-12-03 12:05:58 -0800216 if (mDestroyed || mDialog == null) return;
Felipe Leme284ad1c2018-11-15 18:16:12 -0800217
Felipe Lemebf93cdc2018-12-03 12:05:58 -0800218 mDialog.dismiss();
219 mDialog = null;
220 if (mProxy != null) {
221 mProxy.report(AutofillProxy.REPORT_EVENT_UI_DESTROYED);
Felipe Leme284ad1c2018-11-15 18:16:12 -0800222 }
Felipe Lemebf93cdc2018-12-03 12:05:58 -0800223 mCloseGuard.close();
224 }
225 }
226
227 @Override
228 protected void finalize() throws Throwable {
229 try {
230 if (mCloseGuard != null) {
231 mCloseGuard.warnIfOpen();
232 }
233 destroy();
234 } finally {
235 super.finalize();
Felipe Leme284ad1c2018-11-15 18:16:12 -0800236 }
237 }
238
239 private void checkNotDestroyedLocked() {
240 if (mDestroyed) {
241 throw new IllegalStateException("already destroyed()");
242 }
243 }
244
245 /** @hide */
246 public void dump(@NonNull String prefix, @NonNull PrintWriter pw) {
247 synchronized (this) {
248 pw.print(prefix); pw.print("destroyed: "); pw.println(mDestroyed);
249 if (mDialog != null) {
250 pw.print(prefix); pw.print("dialog: ");
251 pw.println(mDialog.isShowing() ? "shown" : "hidden");
252 pw.print(prefix); pw.print("window: ");
253 pw.println(mDialog.getWindow().getAttributes());
254 }
255 }
256 }
Felipe Leme749b8892018-12-03 16:30:30 -0800257
258 /** @hide */
259 @Override
260 public void close() throws Exception {
261 destroy();
262 }
Felipe Leme284ad1c2018-11-15 18:16:12 -0800263}