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 com.android.server.autofill;
18 
19 import static android.service.autofill.FillRequest.FLAG_MANUAL_REQUEST;
20 import static android.service.autofill.FillRequest.FLAG_VIEW_REQUESTS_CREDMAN_SERVICE;
21 
22 import static com.android.server.autofill.Helper.sDebug;
23 
24 import android.annotation.NonNull;
25 import android.annotation.Nullable;
26 import android.graphics.Rect;
27 import android.service.autofill.FillResponse;
28 import android.util.DebugUtils;
29 import android.util.Slog;
30 import android.view.autofill.AutofillId;
31 import android.view.autofill.AutofillValue;
32 
33 import java.io.PrintWriter;
34 
35 /**
36  * State for a given view with a AutofillId.
37  *
38  * <p>This class holds state about a view and calls its listener when the fill UI is ready to
39  * be displayed for the view.
40  */
41 final class ViewState {
42     interface Listener {
43         /**
44          * Called when the fill UI is ready to be shown for this view.
45          */
onFillReady(@onNull FillResponse fillResponse, @NonNull AutofillId focusedId, @Nullable AutofillValue value, int flags)46         void onFillReady(@NonNull FillResponse fillResponse, @NonNull AutofillId focusedId,
47                 @Nullable AutofillValue value, int flags);
48     }
49 
50     private static final String TAG = "ViewState";
51 
52     /** Initial state. */
53     public static final int STATE_INITIAL = 0x001;
54     /** View id is present in a dataset returned by the service. */
55     public static final int STATE_FILLABLE = 0x002;
56     /** View was autofilled after user selected a dataset. */
57     public static final int STATE_AUTOFILLED = 0x004;
58     /** View value was changed, but not by the service. */
59     public static final int STATE_CHANGED = 0x008;
60     /** Set only in the View that started a session. */
61     public static final int STATE_STARTED_SESSION = 0x010;
62     /** View that started a new partition when focused on. */
63     public static final int STATE_STARTED_PARTITION = 0x020;
64     /** User select a dataset in this view, but service must authenticate first. */
65     public static final int STATE_WAITING_DATASET_AUTH = 0x040;
66     /** Service does not care about this view. */
67     public static final int STATE_IGNORED = 0x080;
68     /** User manually request autofill in this view, after it was already autofilled. */
69     public static final int STATE_RESTARTED_SESSION = 0x100;
70     /** View is the URL bar of a package on compat mode. */
71     public  static final int STATE_URL_BAR = 0x200;
72     /** View was asked to autofill but failed to do so. */
73     public static final int STATE_AUTOFILL_FAILED = 0x400;
74     /** View has been autofilled at least once. */
75     public static final int STATE_AUTOFILLED_ONCE = 0x800;
76     /** View triggered the latest augmented autofill request. */
77     public static final int STATE_TRIGGERED_AUGMENTED_AUTOFILL = 0x1000;
78     /** Inline suggestions were shown for this View. */
79     public static final int STATE_INLINE_SHOWN = 0x2000;
80     /** A character was removed from the View value (not by the service). */
81     public static final int STATE_CHAR_REMOVED = 0x4000;
82     /** Showing inline suggestions is not allowed for this View. */
83     public static final int STATE_INLINE_DISABLED = 0x8000;
84     /** The View is waiting for an inline suggestions request from IME.*/
85     public static final int STATE_PENDING_CREATE_INLINE_REQUEST = 0x10000;
86     /** Fill dialog were shown for this View. */
87     public static final int STATE_FILL_DIALOG_SHOWN = 0x20000;
88 
89     public final AutofillId id;
90 
91     private final Listener mListener;
92 
93     private final boolean mIsPrimaryCredential;
94 
95     /**
96      * There are two sources of fill response. The fill response from the session's remote fill
97      * service and the fill response from the secondary provider handler. Primary Fill Response
98      * stores the fill response from the session's remote fill service.
99      */
100     private FillResponse mPrimaryFillResponse;
101 
102     /**
103      * Secondary fill response stores the fill response from the secondary provider handler. Based
104      * on whether the user focuses on a credential view or an autofill view, the relevant fill
105      * response will be used to show the autofill suggestions.
106      */
107     private FillResponse mSecondaryFillResponse;
108     private AutofillValue mCurrentValue;
109 
110     /**
111      * Some apps clear the form before navigating to another activity. The mCandidateSaveValue
112      * caches the value when a field with string longer than 2 characters are cleared.
113      *
114      * When showing save UI, if mCurrentValue of view state is empty, session would use
115      * mCandidateSaveValue to prompt save instead.
116      */
117     private AutofillValue mCandidateSaveValue;
118     private AutofillValue mAutofilledValue;
119     private AutofillValue mSanitizedValue;
120     private Rect mVirtualBounds;
121     private int mState;
122     private String mDatasetId;
123 
ViewState(AutofillId id, Listener listener, int state, boolean isPrimaryCredential)124     ViewState(AutofillId id, Listener listener, int state, boolean isPrimaryCredential) {
125         this.id = id;
126         mListener = listener;
127         mState = state;
128         mIsPrimaryCredential = isPrimaryCredential;
129     }
130 
131     /**
132      * Gets the boundaries of the virtual view, or {@code null} if the the view is not virtual.
133      */
134     @Nullable
getVirtualBounds()135     Rect getVirtualBounds() {
136         return mVirtualBounds;
137     }
138 
139     /**
140      * Gets the current value of the view.
141      */
142     @Nullable
getCurrentValue()143     AutofillValue getCurrentValue() {
144         return mCurrentValue;
145     }
146 
setCurrentValue(AutofillValue value)147     void setCurrentValue(AutofillValue value) {
148         mCurrentValue = value;
149     }
150 
151     /**
152      * Gets the candidate save value of the view.
153      */
154     @Nullable
getCandidateSaveValue()155     AutofillValue getCandidateSaveValue() {
156         return mCandidateSaveValue;
157     }
158 
setCandidateSaveValue(AutofillValue value)159     void setCandidateSaveValue(AutofillValue value) {
160         mCandidateSaveValue = value;
161     }
162 
163     @Nullable
getAutofilledValue()164     AutofillValue getAutofilledValue() {
165         return mAutofilledValue;
166     }
167 
setAutofilledValue(@ullable AutofillValue value)168     void setAutofilledValue(@Nullable AutofillValue value) {
169         mAutofilledValue = value;
170     }
171 
172     @Nullable
getSanitizedValue()173     AutofillValue getSanitizedValue() {
174         return mSanitizedValue;
175     }
176 
setSanitizedValue(@ullable AutofillValue value)177     void setSanitizedValue(@Nullable AutofillValue value) {
178         mSanitizedValue = value;
179     }
180 
181     @Nullable
getResponse()182     FillResponse getResponse() {
183         return mPrimaryFillResponse;
184     }
185 
186     @Nullable
getSecondaryResponse()187     FillResponse getSecondaryResponse() {
188         return mSecondaryFillResponse;
189     }
190 
setResponse(FillResponse response)191     void setResponse(FillResponse response) {
192         setResponse(response, /* isPrimary= */ true);
193     }
194 
setResponse(@ullable FillResponse response, boolean isPrimary)195     void setResponse(@Nullable FillResponse response, boolean isPrimary) {
196         if (isPrimary) {
197             mPrimaryFillResponse = response;
198         } else {
199             mSecondaryFillResponse = response;
200         }
201     }
202 
getState()203     int getState() {
204         return mState;
205     }
206 
getStateAsString()207     String getStateAsString() {
208         return getStateAsString(mState);
209     }
210 
getStateAsString(int state)211     static String getStateAsString(int state) {
212         return DebugUtils.flagsToString(ViewState.class, "STATE_", state);
213     }
214 
setState(int state)215     void setState(int state) {
216         if (mState == STATE_INITIAL) {
217             mState = state;
218         } else {
219             mState |= state;
220         }
221         if (state == STATE_AUTOFILLED) {
222             mState |= STATE_AUTOFILLED_ONCE;
223         }
224     }
225 
resetState(int state)226     void resetState(int state) {
227         mState &= ~state;
228     }
229 
230     @Nullable
getDatasetId()231     String getDatasetId() {
232         return mDatasetId;
233     }
234 
setDatasetId(String datasetId)235     void setDatasetId(String datasetId) {
236         mDatasetId = datasetId;
237     }
238 
239     // TODO: refactor / rename / document this method (and maybeCallOnFillReady) to make it clear
240     // that it can change the value and update the UI; similarly, should replace code that
241     // directly sets mAutofillValue to use encapsulation.
update(@ullable AutofillValue autofillValue, @Nullable Rect virtualBounds, int flags)242     void update(@Nullable AutofillValue autofillValue, @Nullable Rect virtualBounds, int flags) {
243         if (autofillValue != null) {
244             mCurrentValue = autofillValue;
245         }
246         if (virtualBounds != null) {
247             mVirtualBounds = virtualBounds;
248         }
249 
250         maybeCallOnFillReady(flags);
251     }
252 
253     /**
254      * Calls {@link
255      * Listener#onFillReady(FillResponse, AutofillId, AutofillValue, int)} if the
256      * fill UI is ready to be displayed (i.e. when response and bounds are set).
257      */
maybeCallOnFillReady(int flags)258     void maybeCallOnFillReady(int flags) {
259         if ((mState & STATE_AUTOFILLED) != 0 && (flags & FLAG_MANUAL_REQUEST) == 0) {
260             if (sDebug) Slog.d(TAG, "Ignoring UI for " + id + " on " + getStateAsString());
261             return;
262         }
263         // First try the current response associated with this View.
264         FillResponse requestedResponse = requestingPrimaryResponse(flags)
265                 ? mPrimaryFillResponse : mSecondaryFillResponse;
266         if (requestedResponse != null) {
267             if (requestedResponse.getDatasets() != null
268                     || requestedResponse.getAuthentication() != null) {
269                 mListener.onFillReady(requestedResponse, this.id, mCurrentValue, flags);
270             }
271         }
272     }
273 
requestingPrimaryResponse(int flags)274     private boolean requestingPrimaryResponse(int flags) {
275         if (mIsPrimaryCredential) {
276             return (flags & FLAG_VIEW_REQUESTS_CREDMAN_SERVICE) != 0;
277         } else {
278             return (flags & FLAG_VIEW_REQUESTS_CREDMAN_SERVICE) == 0;
279         }
280     }
281 
282     @Override
toString()283     public String toString() {
284         final StringBuilder builder = new StringBuilder("ViewState: [id=").append(id);
285         if (mDatasetId != null) {
286             builder.append(", datasetId:" ).append(mDatasetId);
287         }
288         builder.append(", state:").append(getStateAsString());
289         if (mCurrentValue != null) {
290             builder.append(", currentValue:" ).append(mCurrentValue);
291         }
292         if (mCandidateSaveValue != null) {
293             builder.append(", candidateSaveValue:").append(mCandidateSaveValue);
294         }
295         if (mAutofilledValue != null) {
296             builder.append(", autofilledValue:" ).append(mAutofilledValue);
297         }
298         if (mSanitizedValue != null) {
299             builder.append(", sanitizedValue:" ).append(mSanitizedValue);
300         }
301         if (mVirtualBounds != null) {
302             builder.append(", virtualBounds:" ).append(mVirtualBounds);
303         }
304         builder.append("]");
305         return builder.toString();
306     }
307 
dump(String prefix, PrintWriter pw)308     void dump(String prefix, PrintWriter pw) {
309         pw.print(prefix); pw.print("id:" ); pw.println(id);
310         if (mDatasetId != null) {
311             pw.print(prefix); pw.print("datasetId:" ); pw.println(mDatasetId);
312         }
313         pw.print(prefix); pw.print("state:" ); pw.println(getStateAsString());
314         pw.print(prefix); pw.print("is primary credential:"); pw.println(mIsPrimaryCredential);
315         if (mPrimaryFillResponse != null) {
316             pw.print(prefix); pw.print("primary response id:");
317             pw.println(mPrimaryFillResponse.getRequestId());
318         }
319         if (mSecondaryFillResponse != null) {
320             pw.print(prefix); pw.print("secondary response id:");
321             pw.println(mSecondaryFillResponse.getRequestId());
322         }
323         if (mCurrentValue != null) {
324             pw.print(prefix); pw.print("currentValue:" ); pw.println(mCurrentValue);
325         }
326         if (mAutofilledValue != null) {
327             pw.print(prefix); pw.print("autofilledValue:" ); pw.println(mAutofilledValue);
328         }
329         if (mCandidateSaveValue != null) {
330             pw.print(prefix); pw.print("candidateSaveValue:"); pw.println(mCandidateSaveValue);
331         }
332         if (mSanitizedValue != null) {
333             pw.print(prefix); pw.print("sanitizedValue:" ); pw.println(mSanitizedValue);
334         }
335         if (mVirtualBounds != null) {
336             pw.print(prefix); pw.print("virtualBounds:" ); pw.println(mVirtualBounds);
337         }
338     }
339 }