• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
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  */
16 package android.service.autofill.augmented;
17 
18 import static android.service.autofill.augmented.AugmentedAutofillService.sDebug;
19 import static android.service.autofill.augmented.AugmentedAutofillService.sVerbose;
20 
21 import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
22 
23 import android.annotation.NonNull;
24 import android.annotation.SystemApi;
25 import android.annotation.TestApi;
26 import android.graphics.Rect;
27 import android.os.Handler;
28 import android.os.Looper;
29 import android.os.RemoteException;
30 import android.service.autofill.augmented.AugmentedAutofillService.AutofillProxy;
31 import android.service.autofill.augmented.PresentationParams.Area;
32 import android.util.Log;
33 import android.view.MotionEvent;
34 import android.view.View;
35 import android.view.WindowManager;
36 import android.view.autofill.IAutofillWindowPresenter;
37 
38 import com.android.internal.annotations.GuardedBy;
39 import com.android.internal.util.Preconditions;
40 
41 import dalvik.system.CloseGuard;
42 
43 import java.io.PrintWriter;
44 
45 /**
46  * Handle to a window used to display the augmented autofill UI.
47  *
48  * <p>The steps to create an augmented autofill UI are:
49  *
50  * <ol>
51  *   <li>Gets the {@link PresentationParams} from the {@link FillRequest}.
52  *   <li>Gets the {@link Area} to display the UI (for example, through
53  *   {@link PresentationParams#getSuggestionArea()}.
54  *   <li>Creates a {@link View} that must fit in the {@link Area#getBounds() area boundaries}.
55  *   <li>Set the proper listeners to the view (for example, a click listener that
56  *   triggers {@link FillController#autofill(java.util.List)}
57  *   <li>Call {@link #update(Area, View, long)} with these arguments.
58  *   <li>Create a {@link FillResponse} with the {@link FillWindow}.
59  *   <li>Pass such {@link FillResponse} to {@link FillCallback#onSuccess(FillResponse)}.
60  * </ol>
61  *
62  * @hide
63  */
64 @SystemApi
65 @TestApi
66 public final class FillWindow implements AutoCloseable {
67     private static final String TAG = FillWindow.class.getSimpleName();
68 
69     private final Object mLock = new Object();
70     private final CloseGuard mCloseGuard = CloseGuard.get();
71 
72     private final @NonNull Handler mUiThreadHandler = new Handler(Looper.getMainLooper());
73     private final @NonNull FillWindowPresenter mFillWindowPresenter = new FillWindowPresenter();
74 
75     @GuardedBy("mLock")
76     private WindowManager mWm;
77     @GuardedBy("mLock")
78     private View mFillView;
79     @GuardedBy("mLock")
80     private boolean mShowing;
81     @GuardedBy("mLock")
82     private Rect mBounds;
83 
84     @GuardedBy("mLock")
85     private boolean mUpdateCalled;
86     @GuardedBy("mLock")
87     private boolean mDestroyed;
88 
89     private AutofillProxy mProxy;
90 
91     /**
92      * Updates the content of the window.
93      *
94      * @param rootView new root view
95      * @param area coordinates to render the view.
96      * @param flags currently not used.
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      */
update(@onNull Area area, @NonNull View rootView, long flags)102     public boolean update(@NonNull Area area, @NonNull View rootView, long flags) {
103         if (sDebug) {
104             Log.d(TAG, "Updating " + area + " + with " + rootView);
105         }
106         // TODO(b/123100712): add test case for null
107         Preconditions.checkNotNull(area);
108         Preconditions.checkNotNull(area.proxy);
109         Preconditions.checkNotNull(rootView);
110         // TODO(b/123100712): must check the area is a valid object returned by
111         // SmartSuggestionParams, throw IAE if not
112 
113         final PresentationParams smartSuggestion = area.proxy.getSmartSuggestionParams();
114         if (smartSuggestion == null) {
115             Log.w(TAG, "No SmartSuggestionParams");
116             return false;
117         }
118 
119         final Rect rect = area.getBounds();
120         if (rect == null) {
121             Log.wtf(TAG, "No Rect on SmartSuggestionParams");
122             return false;
123         }
124 
125         synchronized (mLock) {
126             checkNotDestroyedLocked();
127 
128             mProxy = area.proxy;
129 
130             // TODO(b/123227534): once we have the SurfaceControl approach, we should update the
131             // window instead of destroying. In fact, it might be better to allocate a full window
132             // initially, which is transparent (and let touches get through) everywhere but in the
133             // rect boundaries.
134 
135             // TODO(b/123099468): make sure all touch events are handled, window is always closed,
136             // etc.
137 
138             mWm = rootView.getContext().getSystemService(WindowManager.class);
139             mFillView = rootView;
140             // Listen to the touch outside to destroy the window when typing is detected.
141             mFillView.setOnTouchListener(
142                     (view, motionEvent) -> {
143                         if (motionEvent.getAction() == MotionEvent.ACTION_OUTSIDE) {
144                             if (sVerbose) Log.v(TAG, "Outside touch detected, hiding the window");
145                             hide();
146                         }
147                         return false;
148                     }
149             );
150             mShowing = false;
151             mBounds = new Rect(area.getBounds());
152             if (sDebug) {
153                 Log.d(TAG, "Created FillWindow: params= " + smartSuggestion + " view=" + rootView);
154             }
155             mUpdateCalled = true;
156             mDestroyed = false;
157             mProxy.setFillWindow(this);
158             return true;
159         }
160     }
161 
162     /** @hide */
show()163     void show() {
164         // TODO(b/123100712): check if updated first / throw exception
165         if (sDebug) Log.d(TAG, "show()");
166         synchronized (mLock) {
167             checkNotDestroyedLocked();
168             if (mWm == null || mFillView == null) {
169                 throw new IllegalStateException("update() not called yet, or already destroyed()");
170             }
171             if (mProxy != null) {
172                 try {
173                     mProxy.requestShowFillUi(mBounds.right - mBounds.left,
174                             mBounds.bottom - mBounds.top,
175                             /*anchorBounds=*/ null, mFillWindowPresenter);
176                 } catch (RemoteException e) {
177                     Log.w(TAG, "Error requesting to show fill window", e);
178                 }
179                 mProxy.report(AutofillProxy.REPORT_EVENT_UI_SHOWN);
180             }
181         }
182     }
183 
184     /**
185      * Hides the window.
186      *
187      * <p>The window is not destroyed and can be shown again
188      */
hide()189     private void hide() {
190         if (sDebug) Log.d(TAG, "hide()");
191         synchronized (mLock) {
192             checkNotDestroyedLocked();
193             if (mWm == null || mFillView == null) {
194                 throw new IllegalStateException("update() not called yet, or already destroyed()");
195             }
196             if (mProxy != null && mShowing) {
197                 try {
198                     mProxy.requestHideFillUi();
199                 } catch (RemoteException e) {
200                     Log.w(TAG, "Error requesting to hide fill window", e);
201                 }
202             }
203         }
204     }
205 
handleShow(WindowManager.LayoutParams p)206     private void handleShow(WindowManager.LayoutParams p) {
207         if (sDebug) Log.d(TAG, "handleShow()");
208         synchronized (mLock) {
209             if (mWm != null && mFillView != null) {
210                 p.flags |= WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
211                 if (!mShowing) {
212                     mWm.addView(mFillView, p);
213                     mShowing = true;
214                 } else {
215                     mWm.updateViewLayout(mFillView, p);
216                 }
217             }
218         }
219     }
220 
handleHide()221     private void handleHide() {
222         if (sDebug) Log.d(TAG, "handleHide()");
223         synchronized (mLock) {
224             if (mWm != null && mFillView != null && mShowing) {
225                 mWm.removeView(mFillView);
226                 mShowing = false;
227             }
228         }
229     }
230 
231     /**
232      * Destroys the window.
233      *
234      * <p>Once destroyed, this window cannot be used anymore
235      */
destroy()236     public void destroy() {
237         if (sDebug) {
238             Log.d(TAG,
239                     "destroy(): mDestroyed=" + mDestroyed + " mShowing=" + mShowing + " mFillView="
240                             + mFillView);
241         }
242         synchronized (mLock) {
243             if (mDestroyed) return;
244             if (mUpdateCalled) {
245                 hide();
246                 mProxy.report(AutofillProxy.REPORT_EVENT_UI_DESTROYED);
247             }
248             mDestroyed = true;
249             mCloseGuard.close();
250         }
251     }
252 
253     @Override
finalize()254     protected void finalize() throws Throwable {
255         try {
256             if (mCloseGuard != null) {
257                 mCloseGuard.warnIfOpen();
258             }
259             destroy();
260         } finally {
261             super.finalize();
262         }
263     }
264 
checkNotDestroyedLocked()265     private void checkNotDestroyedLocked() {
266         if (mDestroyed) {
267             throw new IllegalStateException("already destroyed()");
268         }
269     }
270 
271     /** @hide */
dump(@onNull String prefix, @NonNull PrintWriter pw)272     public void dump(@NonNull String prefix, @NonNull PrintWriter pw) {
273         synchronized (this) {
274             pw.print(prefix); pw.print("destroyed: "); pw.println(mDestroyed);
275             pw.print(prefix); pw.print("updateCalled: "); pw.println(mUpdateCalled);
276             if (mFillView != null) {
277                 pw.print(prefix); pw.print("fill window: ");
278                 pw.println(mShowing ? "shown" : "hidden");
279                 pw.print(prefix); pw.print("fill view: ");
280                 pw.println(mFillView);
281                 pw.print(prefix); pw.print("mBounds: ");
282                 pw.println(mBounds);
283                 pw.print(prefix); pw.print("mWm: ");
284                 pw.println(mWm);
285             }
286         }
287     }
288 
289     /** @hide */
290     @Override
close()291     public void close() throws Exception {
292         destroy();
293     }
294 
295     private final class FillWindowPresenter extends IAutofillWindowPresenter.Stub {
296         @Override
show(WindowManager.LayoutParams p, Rect transitionEpicenter, boolean fitsSystemWindows, int layoutDirection)297         public void show(WindowManager.LayoutParams p, Rect transitionEpicenter,
298                 boolean fitsSystemWindows, int layoutDirection) {
299             if (sDebug) Log.d(TAG, "FillWindowPresenter.show()");
300             mUiThreadHandler.sendMessage(obtainMessage(FillWindow::handleShow, FillWindow.this, p));
301         }
302 
303         @Override
hide(Rect transitionEpicenter)304         public void hide(Rect transitionEpicenter) {
305             if (sDebug) Log.d(TAG, "FillWindowPresenter.hide()");
306             mUiThreadHandler.sendMessage(obtainMessage(FillWindow::handleHide, FillWindow.this));
307         }
308     }
309 }
310