OSDN Git Service

65b9cb2d2b50408168aecb515bf0e39991589c22
[android-x86/frameworks-base.git] / core / java / android / service / autofill / FillResponse.java
1 /*
2  * Copyright (C) 2016 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 android.service.autofill;
18
19 import static android.service.autofill.FillRequest.INVALID_REQUEST_ID;
20 import static android.view.autofill.Helper.sDebug;
21
22 import android.annotation.NonNull;
23 import android.annotation.Nullable;
24 import android.app.Activity;
25 import android.content.IntentSender;
26 import android.content.pm.ParceledListSlice;
27 import android.os.Bundle;
28 import android.os.Parcel;
29 import android.os.Parcelable;
30 import android.view.autofill.AutofillId;
31 import android.widget.RemoteViews;
32
33 import java.util.ArrayList;
34 import java.util.Arrays;
35 import java.util.List;
36
37 /**
38  * Response for a {@link
39  * AutofillService#onFillRequest(FillRequest, android.os.CancellationSignal, FillCallback)}.
40  *
41  * <p>See the main {@link AutofillService} documentation for more details and examples.
42  */
43 public final class FillResponse implements Parcelable {
44
45     private final @Nullable ParceledListSlice<Dataset> mDatasets;
46     private final @Nullable SaveInfo mSaveInfo;
47     private final @Nullable Bundle mClientState;
48     private final @Nullable RemoteViews mPresentation;
49     private final @Nullable IntentSender mAuthentication;
50     private final @Nullable AutofillId[] mAuthenticationIds;
51     private final @Nullable AutofillId[] mIgnoredIds;
52     private int mRequestId;
53
54     private FillResponse(@NonNull Builder builder) {
55         mDatasets = (builder.mDatasets != null) ? new ParceledListSlice<>(builder.mDatasets) : null;
56         mSaveInfo = builder.mSaveInfo;
57         mClientState = builder.mCLientState;
58         mPresentation = builder.mPresentation;
59         mAuthentication = builder.mAuthentication;
60         mAuthenticationIds = builder.mAuthenticationIds;
61         mIgnoredIds = builder.mIgnoredIds;
62         mRequestId = INVALID_REQUEST_ID;
63     }
64
65     /** @hide */
66     public @Nullable Bundle getClientState() {
67         return mClientState;
68     }
69
70     /** @hide */
71     public @Nullable List<Dataset> getDatasets() {
72         return (mDatasets != null) ? mDatasets.getList() : null;
73     }
74
75     /** @hide */
76     public @Nullable SaveInfo getSaveInfo() {
77         return mSaveInfo;
78     }
79
80     /** @hide */
81     public @Nullable RemoteViews getPresentation() {
82         return mPresentation;
83     }
84
85     /** @hide */
86     public @Nullable IntentSender getAuthentication() {
87         return mAuthentication;
88     }
89
90     /** @hide */
91     public @Nullable AutofillId[] getAuthenticationIds() {
92         return mAuthenticationIds;
93     }
94
95     /** @hide */
96     public @Nullable AutofillId[] getIgnoredIds() {
97         return mIgnoredIds;
98     }
99
100     /**
101      * Associates a {@link FillResponse} to a request.
102      *
103      * <p>Set inside of the {@link FillCallback} code, not the {@link AutofillService}.
104      *
105      * @param requestId The id of the request to associate the response to.
106      *
107      * @hide
108      */
109     public void setRequestId(int requestId) {
110         mRequestId = requestId;
111     }
112
113     /** @hide */
114     public int getRequestId() {
115         return mRequestId;
116     }
117
118     /**
119      * Builder for {@link FillResponse} objects. You must to provide at least
120      * one dataset or set an authentication intent with a presentation view.
121      */
122     public static final class Builder {
123         private ArrayList<Dataset> mDatasets;
124         private SaveInfo mSaveInfo;
125         private Bundle mCLientState;
126         private RemoteViews mPresentation;
127         private IntentSender mAuthentication;
128         private AutofillId[] mAuthenticationIds;
129         private AutofillId[] mIgnoredIds;
130         private boolean mDestroyed;
131
132         /**
133          * Requires a fill response authentication before autofilling the screen with
134          * any data set in this response.
135          *
136          * <p>This is typically useful when a user interaction is required to unlock their
137          * data vault if you encrypt the data set labels and data set data. It is recommended
138          * to encrypt only the sensitive data and not the data set labels which would allow
139          * auth on the data set level leading to a better user experience. Note that if you
140          * use sensitive data as a label, for example an email address, then it should also
141          * be encrypted. The provided {@link android.app.PendingIntent intent} must be an
142          * {@link Activity} which implements your authentication flow. Also if you provide an auth
143          * intent you also need to specify the presentation view to be shown in the fill UI
144          * for the user to trigger your authentication flow.
145          *
146          * <p>When a user triggers autofill, the system launches the provided intent
147          * whose extras will have the
148          * {@link android.view.autofill.AutofillManager#EXTRA_ASSIST_STRUCTURE screen
149          * content} and your {@link android.view.autofill.AutofillManager#EXTRA_CLIENT_STATE
150          * client state}. Once you complete your authentication flow you should set the
151          * {@link Activity} result to {@link android.app.Activity#RESULT_OK} and provide the fully
152          * populated {@link FillResponse response} by setting it to the
153          * {@link android.view.autofill.AutofillManager#EXTRA_AUTHENTICATION_RESULT} extra.
154          * For example, if you provided an empty {@link FillResponse resppnse} because the
155          * user's data was locked and marked that the response needs an authentication then
156          * in the response returned if authentication succeeds you need to provide all
157          * available data sets some of which may need to be further authenticated, for
158          * example a credit card whose CVV needs to be entered.
159          *
160          * <p>If you provide an authentication intent you must also provide a presentation
161          * which is used to visualize visualize the response for triggering the authentication
162          * flow.
163          *
164          * <p></><strong>Note:</strong> Do not make the provided pending intent
165          * immutable by using {@link android.app.PendingIntent#FLAG_IMMUTABLE} as the
166          * platform needs to fill in the authentication arguments.
167          *
168          * @param authentication Intent to an activity with your authentication flow.
169          * @param presentation The presentation to visualize the response.
170          * @param ids id of Views that when focused will display the authentication UI affordance.
171          *
172          * @return This builder.
173          * @throw {@link IllegalArgumentException} if {@code ids} is {@code null} or empty, or if
174          * neither {@code authentication} nor {@code presentation} is non-{@code null}.
175          *
176          * @see android.app.PendingIntent#getIntentSender()
177          */
178         public @NonNull Builder setAuthentication(@NonNull AutofillId[] ids,
179                 @Nullable IntentSender authentication, @Nullable RemoteViews presentation) {
180             throwIfDestroyed();
181             if (ids == null || ids.length == 0) {
182                 throw new IllegalArgumentException("ids cannot be null or empry");
183             }
184             if (authentication == null ^ presentation == null) {
185                 throw new IllegalArgumentException("authentication and presentation"
186                         + " must be both non-null or null");
187             }
188             mAuthentication = authentication;
189             mPresentation = presentation;
190             mAuthenticationIds = ids;
191             return this;
192         }
193
194         /**
195          * Specifies views that should not trigger new
196          * {@link AutofillService#onFillRequest(FillRequest, android.os.CancellationSignal,
197          * FillCallback)} requests.
198          *
199          * <p>This is typically used when the service cannot autofill the view; for example, a
200          * text field representing the result of a Captcha challenge.
201          */
202         public Builder setIgnoredIds(AutofillId...ids) {
203             mIgnoredIds = ids;
204             return this;
205         }
206
207         /**
208          * Adds a new {@link Dataset} to this response.
209          *
210          * <p><b>Note: </b> on Android {@link android.os.Build.VERSION_CODES#O}, the total number of
211          * datasets is limited by the Binder transaction size, so it's recommended to keep it
212          * small (in the range of 10-20 at most) and use pagination by adding a fake
213          * {@link Dataset.Builder#setAuthentication(IntentSender) authenticated dataset} at the end
214          * with a presentation string like "Next 10" that would return a new {@link FillResponse}
215          * with the next 10 datasets, and so on. This limitation was lifted on
216          * Android {@link android.os.Build.VERSION_CODES#O_MR1}, although the Binder transaction
217          * size can still be reached if each dataset itself is too big.
218          *
219          * @return This builder.
220          */
221         public @NonNull Builder addDataset(@Nullable Dataset dataset) {
222             throwIfDestroyed();
223             if (dataset == null) {
224                 return this;
225             }
226             if (mDatasets == null) {
227                 mDatasets = new ArrayList<>();
228             }
229             if (!mDatasets.add(dataset)) {
230                 return this;
231             }
232             return this;
233         }
234
235         /**
236          * Sets the {@link SaveInfo} associated with this response.
237          *
238          * @return This builder.
239          */
240         public @NonNull Builder setSaveInfo(@NonNull SaveInfo saveInfo) {
241             throwIfDestroyed();
242             mSaveInfo = saveInfo;
243             return this;
244         }
245
246         /**
247          * Sets a {@link Bundle state} that will be passed to subsequent APIs that
248          * manipulate this response. For example, they are passed to subsequent
249          * calls to {@link AutofillService#onFillRequest(FillRequest, android.os.CancellationSignal,
250          * FillCallback)} and {@link AutofillService#onSaveRequest(SaveRequest, SaveCallback)}.
251          * You can use this to store intermediate state that is persistent across multiple
252          * fill requests and the subsequent save request.
253          *
254          * <p>If this method is called on multiple {@link FillResponse} objects for the same
255          * screen, just the latest bundle is passed back to the service.
256          *
257          * <p>Once a {@link AutofillService#onSaveRequest(SaveRequest, SaveCallback)
258          * save request} is made the client state is cleared.
259          *
260          * @param clientState The custom client state.
261          * @return This builder.
262          */
263         public Builder setClientState(@Nullable Bundle clientState) {
264             throwIfDestroyed();
265             mCLientState = clientState;
266             return this;
267         }
268
269         /**
270          * Builds a new {@link FillResponse} instance.
271          *
272          * <p>You must provide at least one dataset or some savable ids or an authentication with a
273          * presentation view.
274          *
275          * @return A built response.
276          */
277         public FillResponse build() {
278             throwIfDestroyed();
279
280             if (mAuthentication == null && mDatasets == null && mSaveInfo == null) {
281                 throw new IllegalArgumentException("need to provide at least one DataSet or a "
282                         + "SaveInfo or an authentication with a presentation");
283             }
284             mDestroyed = true;
285             return new FillResponse(this);
286         }
287
288         private void throwIfDestroyed() {
289             if (mDestroyed) {
290                 throw new IllegalStateException("Already called #build()");
291             }
292         }
293     }
294
295     /////////////////////////////////////
296     // Object "contract" methods. //
297     /////////////////////////////////////
298     @Override
299     public String toString() {
300         if (!sDebug) return super.toString();
301
302         // TODO: create a dump() method instead
303         return new StringBuilder(
304                 "FillResponse : [mRequestId=" + mRequestId)
305                 .append(", datasets=").append(mDatasets)
306                 .append(", saveInfo=").append(mSaveInfo)
307                 .append(", clientState=").append(mClientState != null)
308                 .append(", hasPresentation=").append(mPresentation != null)
309                 .append(", hasAuthentication=").append(mAuthentication != null)
310                 .append(", authenticationIds=").append(Arrays.toString(mAuthenticationIds))
311                 .append(", ignoredIds=").append(Arrays.toString(mIgnoredIds))
312                 .append("]")
313                 .toString();
314     }
315
316     /////////////////////////////////////
317     // Parcelable "contract" methods. //
318     /////////////////////////////////////
319
320     @Override
321     public int describeContents() {
322         return 0;
323     }
324
325     @Override
326     public void writeToParcel(Parcel parcel, int flags) {
327         parcel.writeParcelable(mDatasets, flags);
328         parcel.writeParcelable(mSaveInfo, flags);
329         parcel.writeParcelable(mClientState, flags);
330         parcel.writeParcelableArray(mAuthenticationIds, flags);
331         parcel.writeParcelable(mAuthentication, flags);
332         parcel.writeParcelable(mPresentation, flags);
333         parcel.writeParcelableArray(mIgnoredIds, flags);
334         parcel.writeInt(mRequestId);
335     }
336
337     public static final Parcelable.Creator<FillResponse> CREATOR =
338             new Parcelable.Creator<FillResponse>() {
339         @Override
340         public FillResponse createFromParcel(Parcel parcel) {
341             // Always go through the builder to ensure the data ingested by
342             // the system obeys the contract of the builder to avoid attacks
343             // using specially crafted parcels.
344             final Builder builder = new Builder();
345             final ParceledListSlice<Dataset> datasetSlice = parcel.readParcelable(null);
346             final List<Dataset> datasets = (datasetSlice != null) ? datasetSlice.getList() : null;
347             final int datasetCount = (datasets != null) ? datasets.size() : 0;
348             for (int i = 0; i < datasetCount; i++) {
349                 builder.addDataset(datasets.get(i));
350             }
351             builder.setSaveInfo(parcel.readParcelable(null));
352             builder.setClientState(parcel.readParcelable(null));
353
354             // Sets authentication state.
355             final AutofillId[] authenticationIds = parcel.readParcelableArray(null,
356                     AutofillId.class);
357             final IntentSender authentication = parcel.readParcelable(null);
358             final RemoteViews presentation = parcel.readParcelable(null);
359             if (authenticationIds != null) {
360                 builder.setAuthentication(authenticationIds, authentication, presentation);
361             }
362
363             builder.setIgnoredIds(parcel.readParcelableArray(null, AutofillId.class));
364             final FillResponse response = builder.build();
365
366             response.setRequestId(parcel.readInt());
367
368             return response;
369         }
370
371         @Override
372         public FillResponse[] newArray(int size) {
373             return new FillResponse[size];
374         }
375     };
376 }