Home | History | Annotate | Download | only in autofill
      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 android.service.autofill;
     18 
     19 import static android.view.autofill.Helper.sDebug;
     20 
     21 import android.annotation.NonNull;
     22 import android.annotation.Nullable;
     23 import android.os.Parcel;
     24 import android.os.Parcelable;
     25 import android.util.Pair;
     26 import android.widget.RemoteViews;
     27 
     28 import com.android.internal.util.Preconditions;
     29 
     30 import java.util.ArrayList;
     31 
     32 /**
     33  * Defines actions to be applied to a {@link RemoteViews template presentation}.
     34  *
     35  *
     36  * <p>It supports 2 types of actions:
     37  *
     38  * <ol>
     39  *   <li>{@link RemoteViews Actions} to be applied to the template.
     40  *   <li>{@link Transformation Transformations} to be applied on child views.
     41  * </ol>
     42  *
     43  * <p>Typically used on {@link CustomDescription custom descriptions} to conditionally display
     44  * differents views based on user input - see
     45  * {@link CustomDescription.Builder#batchUpdate(Validator, BatchUpdates)} for more information.
     46  */
     47 public final class BatchUpdates implements Parcelable {
     48 
     49     private final ArrayList<Pair<Integer, InternalTransformation>> mTransformations;
     50     private final RemoteViews mUpdates;
     51 
     52     private BatchUpdates(Builder builder) {
     53         mTransformations = builder.mTransformations;
     54         mUpdates = builder.mUpdates;
     55     }
     56 
     57     /** @hide */
     58     @Nullable
     59     public ArrayList<Pair<Integer, InternalTransformation>> getTransformations() {
     60         return mTransformations;
     61     }
     62 
     63     /** @hide */
     64     @Nullable
     65     public RemoteViews getUpdates() {
     66         return mUpdates;
     67     }
     68 
     69     /**
     70      * Builder for {@link BatchUpdates} objects.
     71      */
     72     public static class Builder {
     73         private RemoteViews mUpdates;
     74 
     75         private boolean mDestroyed;
     76         private ArrayList<Pair<Integer, InternalTransformation>> mTransformations;
     77 
     78         /**
     79          * Applies the {@code updates} in the underlying presentation template.
     80          *
     81          * <p><b>Note:</b> The updates are applied before the
     82          * {@link #transformChild(int, Transformation) transformations} are applied to the children
     83          * views.
     84          *
     85          * <p>Theme does not work with RemoteViews layout. Avoid hardcoded text color
     86          * or background color: Autofill on different platforms may have different themes.
     87          *
     88          * @param updates a {@link RemoteViews} with the updated actions to be applied in the
     89          * underlying presentation template.
     90          *
     91          * @return this builder
     92          * @throws IllegalArgumentException if {@code condition} is not a class provided
     93          * by the Android System.
     94          */
     95         public Builder updateTemplate(@NonNull RemoteViews updates) {
     96             throwIfDestroyed();
     97             mUpdates = Preconditions.checkNotNull(updates);
     98             return this;
     99         }
    100 
    101         /**
    102          * Adds a transformation to replace the value of a child view with the fields in the
    103          * screen.
    104          *
    105          * <p>When multiple transformations are added for the same child view, they are applied
    106          * in the same order as added.
    107          *
    108          * <p><b>Note:</b> The transformations are applied after the
    109          * {@link #updateTemplate(RemoteViews) updates} are applied to the presentation template.
    110          *
    111          * @param id view id of the children view.
    112          * @param transformation an implementation provided by the Android System.
    113          * @return this builder.
    114          * @throws IllegalArgumentException if {@code transformation} is not a class provided
    115          * by the Android System.
    116          */
    117         public Builder transformChild(int id, @NonNull Transformation transformation) {
    118             throwIfDestroyed();
    119             Preconditions.checkArgument((transformation instanceof InternalTransformation),
    120                     "not provided by Android System: " + transformation);
    121             if (mTransformations == null) {
    122                 mTransformations = new ArrayList<>();
    123             }
    124             mTransformations.add(new Pair<>(id, (InternalTransformation) transformation));
    125             return this;
    126         }
    127 
    128         /**
    129          * Creates a new {@link BatchUpdates} instance.
    130          *
    131          * @throws IllegalStateException if {@link #build()} was already called before or no call
    132          * to {@link #updateTemplate(RemoteViews)} or {@link #transformChild(int, Transformation)}
    133          * has been made.
    134          */
    135         public BatchUpdates build() {
    136             throwIfDestroyed();
    137             Preconditions.checkState(mUpdates != null || mTransformations != null,
    138                     "must call either updateTemplate() or transformChild() at least once");
    139             mDestroyed = true;
    140             return new BatchUpdates(this);
    141         }
    142 
    143         private void throwIfDestroyed() {
    144             if (mDestroyed) {
    145                 throw new IllegalStateException("Already called #build()");
    146             }
    147         }
    148     }
    149 
    150     /////////////////////////////////////
    151     // Object "contract" methods. //
    152     /////////////////////////////////////
    153     @Override
    154     public String toString() {
    155         if (!sDebug) return super.toString();
    156 
    157         return new StringBuilder("BatchUpdates: [")
    158                 .append(", transformations=")
    159                     .append(mTransformations == null ? "N/A" : mTransformations.size())
    160                 .append(", updates=").append(mUpdates)
    161                 .append("]").toString();
    162     }
    163 
    164     /////////////////////////////////////
    165     // Parcelable "contract" methods. //
    166     /////////////////////////////////////
    167     @Override
    168     public int describeContents() {
    169         return 0;
    170     }
    171 
    172     @Override
    173     public void writeToParcel(Parcel dest, int flags) {
    174         if (mTransformations == null) {
    175             dest.writeIntArray(null);
    176         } else {
    177             final int size = mTransformations.size();
    178             final int[] ids = new int[size];
    179             final InternalTransformation[] values = new InternalTransformation[size];
    180             for (int i = 0; i < size; i++) {
    181                 final Pair<Integer, InternalTransformation> pair = mTransformations.get(i);
    182                 ids[i] = pair.first;
    183                 values[i] = pair.second;
    184             }
    185             dest.writeIntArray(ids);
    186             dest.writeParcelableArray(values, flags);
    187         }
    188         dest.writeParcelable(mUpdates, flags);
    189     }
    190     public static final Parcelable.Creator<BatchUpdates> CREATOR =
    191             new Parcelable.Creator<BatchUpdates>() {
    192         @Override
    193         public BatchUpdates createFromParcel(Parcel parcel) {
    194             // Always go through the builder to ensure the data ingested by
    195             // the system obeys the contract of the builder to avoid attacks
    196             // using specially crafted parcels.
    197             final Builder builder = new Builder();
    198             final int[] ids = parcel.createIntArray();
    199             if (ids != null) {
    200                 final InternalTransformation[] values =
    201                     parcel.readParcelableArray(null, InternalTransformation.class);
    202                 final int size = ids.length;
    203                 for (int i = 0; i < size; i++) {
    204                     builder.transformChild(ids[i], values[i]);
    205                 }
    206             }
    207             final RemoteViews updates = parcel.readParcelable(null);
    208             if (updates != null) {
    209                 builder.updateTemplate(updates);
    210             }
    211             return builder.build();
    212         }
    213 
    214         @Override
    215         public BatchUpdates[] newArray(int size) {
    216             return new BatchUpdates[size];
    217         }
    218     };
    219 }
    220