blob: 9e3aba416f9c2ba6568086e352d4ae89df110c71 [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;
29import android.view.View;
30import android.view.ViewGroup;
31import android.view.Window;
32import android.view.WindowManager;
33
34import com.android.internal.annotations.GuardedBy;
35import com.android.internal.util.Preconditions;
36
Felipe Lemebf93cdc2018-12-03 12:05:58 -080037import dalvik.system.CloseGuard;
38
Felipe Leme284ad1c2018-11-15 18:16:12 -080039import java.io.PrintWriter;
40import java.lang.annotation.Retention;
41import java.lang.annotation.RetentionPolicy;
42
43/**
44 * Handle to a window used to display the augmented autofill UI.
45 *
46 * <p>The steps to create an augmented autofill UI are:
47 *
48 * <ol>
49 * <li>Gets the {@link PresentationParams} from the {@link FillRequest}.
50 * <li>Gets the {@link Area} to display the UI (for example, through
51 * {@link PresentationParams#getSuggestionArea()}.
52 * <li>Creates a {@link View} that must fit in the {@link Area#getBounds() area boundaries}.
53 * <li>Set the proper listeners to the view (for example, a click listener that
54 * triggers {@link FillController#autofill(java.util.List)}
55 * <li>Call {@link #update(Area, View, long)} with these arguments.
56 * <li>Create a {@link FillResponse} with the {@link FillWindow}.
57 * <li>Pass such {@link FillResponse} to {@link FillCallback#onSuccess(FillResponse)}.
58 * </ol>
59 *
60 * @hide
61 */
62@SystemApi
Felipe Leme749b8892018-12-03 16:30:30 -080063public final class FillWindow implements AutoCloseable {
Felipe Leme284ad1c2018-11-15 18:16:12 -080064 private static final String TAG = "FillWindow";
65
66 /** Indicates the data being shown is a physical address */
67 public static final long FLAG_METADATA_ADDRESS = 0x1;
68
69 // TODO(b/111330312): add moar flags
70
71 /** @hide */
72 @LongDef(prefix = { "FLAG" }, value = {
73 FLAG_METADATA_ADDRESS,
74 })
75 @Retention(RetentionPolicy.SOURCE)
76 @interface Flags{}
77
78 private final Object mLock = new Object();
Felipe Lemebf93cdc2018-12-03 12:05:58 -080079 private final CloseGuard mCloseGuard = CloseGuard.get();
Felipe Leme284ad1c2018-11-15 18:16:12 -080080
81 @GuardedBy("mLock")
82 private Dialog mDialog;
83
84 @GuardedBy("mLock")
85 private boolean mDestroyed;
86
Felipe Lemebf93cdc2018-12-03 12:05:58 -080087 private AutofillProxy mProxy;
88
Felipe Leme284ad1c2018-11-15 18:16:12 -080089 /**
90 * Updates the content of the window.
91 *
92 * @param rootView new root view
93 * @param area coordinates to render the view.
94 * @param flags optional flags such as metadata of what will be rendered in the window. The
95 * Smart Suggestion host might decide whether or not to render the UI based on them.
96 *
97 * @return boolean whether the window was updated or not.
98 *
99 * @throws IllegalArgumentException if the area is not compatible with this window
100 */
101 public boolean update(@NonNull Area area, @NonNull View rootView, @Flags long flags) {
102 if (DEBUG) {
103 Log.d(TAG, "Updating " + area + " + with " + rootView);
104 }
105 // TODO(b/111330312): add test case for null
106 Preconditions.checkNotNull(area);
107 Preconditions.checkNotNull(rootView);
108 // TODO(b/111330312): must check the area is a valid object returned by
109 // SmartSuggestionParams, throw IAE if not
110
111 // TODO(b/111330312): must some how pass metadata to the SmartSuggestiongs provider
112
113
114 // TODO(b/111330312): use a SurfaceControl approach; for now, we're manually creating
115 // the window underneath the existing view.
116
117 final PresentationParams smartSuggestion = area.proxy.getSmartSuggestionParams();
118 if (smartSuggestion == null) {
119 Log.w(TAG, "No SmartSuggestionParams");
120 return false;
121 }
122
123 final Rect rect = area.getBounds();
124 if (rect == null) {
125 Log.wtf(TAG, "No Rect on SmartSuggestionParams");
126 return false;
127 }
128
129 synchronized (mLock) {
130 checkNotDestroyedLocked();
131
Felipe Lemebf93cdc2018-12-03 12:05:58 -0800132 mProxy = area.proxy;
133
Felipe Leme284ad1c2018-11-15 18:16:12 -0800134 // TODO(b/111330312): once we have the SurfaceControl approach, we should update the
135 // window instead of destroying. In fact, it might be better to allocate a full window
136 // initially, which is transparent (and let touches get through) everywhere but in the
137 // rect boundaries.
138 destroy();
139
140 // TODO(b/111330312): make sure all touch events are handled, window is always closed,
141 // etc.
142
143 mDialog = new Dialog(rootView.getContext());
Felipe Lemebf93cdc2018-12-03 12:05:58 -0800144 mCloseGuard.open("destroy");
Felipe Leme284ad1c2018-11-15 18:16:12 -0800145 final Window window = mDialog.getWindow();
146 window.setType(WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY);
147
148 final int height = rect.bottom - rect.top;
149 final int width = rect.right - rect.left;
150 final WindowManager.LayoutParams windowParams = window.getAttributes();
151 windowParams.gravity = Gravity.TOP | Gravity.CENTER_HORIZONTAL;
152 windowParams.y = rect.top - height;
153 windowParams.height = height;
154 windowParams.x = rect.left;
155 windowParams.width = width;
156
157 window.setAttributes(windowParams);
158 window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
159
160 mDialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
161 final ViewGroup.LayoutParams diagParams = new ViewGroup.LayoutParams(width, height);
162 mDialog.setContentView(rootView, diagParams);
163
164 if (DEBUG) {
165 Log.d(TAG, "Created FillWindow: params= " + smartSuggestion + " view=" + rootView);
166 }
167
Felipe Lemebf93cdc2018-12-03 12:05:58 -0800168 mProxy.setFillWindow(this);
Felipe Leme284ad1c2018-11-15 18:16:12 -0800169 return true;
170 }
171 }
172
173 /** @hide */
174 void show() {
175 // TODO(b/111330312): check if updated first / throw exception
176 if (DEBUG) Log.d(TAG, "show()");
177
178 synchronized (mLock) {
179 checkNotDestroyedLocked();
180 if (mDialog == null) {
181 throw new IllegalStateException("update() not called yet, or already destroyed()");
182 }
183
184 mDialog.show();
Felipe Lemebf93cdc2018-12-03 12:05:58 -0800185 if (mProxy != null) {
186 mProxy.report(AutofillProxy.REPORT_EVENT_UI_SHOWN);
187 }
Felipe Leme284ad1c2018-11-15 18:16:12 -0800188 }
189 }
190
191 /**
192 * Destroys the window.
193 *
194 * <p>Once destroyed, this window cannot be used anymore
195 */
196 public void destroy() {
Felipe Lemebf93cdc2018-12-03 12:05:58 -0800197 if (DEBUG) Log.d(TAG, "destroy(): mDestroyed=" + mDestroyed + " mDialog=" + mDialog);
Felipe Leme284ad1c2018-11-15 18:16:12 -0800198
199 synchronized (this) {
Felipe Lemebf93cdc2018-12-03 12:05:58 -0800200 if (mDestroyed || mDialog == null) return;
Felipe Leme284ad1c2018-11-15 18:16:12 -0800201
Felipe Lemebf93cdc2018-12-03 12:05:58 -0800202 mDialog.dismiss();
203 mDialog = null;
204 if (mProxy != null) {
205 mProxy.report(AutofillProxy.REPORT_EVENT_UI_DESTROYED);
Felipe Leme284ad1c2018-11-15 18:16:12 -0800206 }
Felipe Lemebf93cdc2018-12-03 12:05:58 -0800207 mCloseGuard.close();
208 }
209 }
210
211 @Override
212 protected void finalize() throws Throwable {
213 try {
214 if (mCloseGuard != null) {
215 mCloseGuard.warnIfOpen();
216 }
217 destroy();
218 } finally {
219 super.finalize();
Felipe Leme284ad1c2018-11-15 18:16:12 -0800220 }
221 }
222
223 private void checkNotDestroyedLocked() {
224 if (mDestroyed) {
225 throw new IllegalStateException("already destroyed()");
226 }
227 }
228
229 /** @hide */
230 public void dump(@NonNull String prefix, @NonNull PrintWriter pw) {
231 synchronized (this) {
232 pw.print(prefix); pw.print("destroyed: "); pw.println(mDestroyed);
233 if (mDialog != null) {
234 pw.print(prefix); pw.print("dialog: ");
235 pw.println(mDialog.isShowing() ? "shown" : "hidden");
236 pw.print(prefix); pw.print("window: ");
237 pw.println(mDialog.getWindow().getAttributes());
238 }
239 }
240 }
Felipe Leme749b8892018-12-03 16:30:30 -0800241
242 /** @hide */
243 @Override
244 public void close() throws Exception {
245 destroy();
246 }
Felipe Leme284ad1c2018-11-15 18:16:12 -0800247}