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