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