FillResponse.java revision 67f9d5070a74a0bf34f0335899a96dedcac26c96
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
17package android.service.autofill;
18
19import static android.service.autofill.FillRequest.INVALID_REQUEST_ID;
20import static android.view.autofill.Helper.sDebug;
21
22import android.annotation.NonNull;
23import android.annotation.Nullable;
24import android.app.Activity;
25import android.content.IntentSender;
26import android.content.pm.ParceledListSlice;
27import android.os.Bundle;
28import android.os.Parcel;
29import android.os.Parcelable;
30import android.view.autofill.AutofillId;
31import android.widget.RemoteViews;
32
33import java.util.ArrayList;
34import java.util.Arrays;
35import 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 */
43public 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         * @throws 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}
377