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