1 /*
2  * Copyright (C) 2018 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 import static android.view.autofill.Helper.sVerbose;
21 
22 import android.annotation.IdRes;
23 import android.annotation.NonNull;
24 import android.os.Parcel;
25 import android.os.Parcelable;
26 import android.util.Slog;
27 import android.util.SparseIntArray;
28 import android.view.View;
29 import android.view.View.Visibility;
30 import android.view.ViewGroup;
31 import android.widget.RemoteViews;
32 
33 import com.android.internal.util.Preconditions;
34 
35 /**
36  * Action used to change the visibility of other child view in a {@link CustomDescription}
37  * {@link RemoteViews presentation template}.
38  *
39  * <p>See {@link CustomDescription.Builder#addOnClickAction(int, OnClickAction)} for more details.
40  */
41 public final class VisibilitySetterAction extends InternalOnClickAction implements
42         OnClickAction, Parcelable {
43     private static final String TAG = "VisibilitySetterAction";
44 
45     @NonNull private final SparseIntArray mVisibilities;
46 
VisibilitySetterAction(@onNull Builder builder)47     private VisibilitySetterAction(@NonNull Builder builder) {
48         mVisibilities = builder.mVisibilities;
49     }
50 
51     /** @hide */
52     @Override
onClick(@onNull ViewGroup rootView)53     public void onClick(@NonNull ViewGroup rootView) {
54         for (int i = 0; i < mVisibilities.size(); i++) {
55             final int id = mVisibilities.keyAt(i);
56             final View child = rootView.findViewById(id);
57             if (child == null) {
58                 Slog.w(TAG, "Skipping view id " + id + " because it's not found on " + rootView);
59                 continue;
60             }
61             final int visibility = mVisibilities.valueAt(i);
62             if (sVerbose) {
63                 Slog.v(TAG, "Changing visibility of view " + child + " from "
64                         + child.getVisibility() + " to  " + visibility);
65             }
66             child.setVisibility(visibility);
67         }
68     }
69 
70     /**
71      * Builder for {@link VisibilitySetterAction} objects.
72      */
73     public static final class Builder {
74         private final SparseIntArray mVisibilities = new SparseIntArray();
75         private boolean mDestroyed;
76 
77         /**
78          * Creates a new builder for an action that change the visibility of one child view.
79          *
80          * @param id view resource id of the children view.
81          * @param visibility one of {@link View#VISIBLE}, {@link View#INVISIBLE}, or
82          *            {@link View#GONE}.
83          * @throws IllegalArgumentException if visibility is not one of {@link View#VISIBLE},
84          * {@link View#INVISIBLE}, or {@link View#GONE}.
85          */
Builder(@dRes int id, @Visibility int visibility)86         public Builder(@IdRes int id, @Visibility int visibility) {
87             setVisibility(id, visibility);
88         }
89 
90         /**
91          * Sets the action to changes the visibility of a child view.
92          *
93          * @param id view resource id of the children view.
94          * @param visibility one of {@link View#VISIBLE}, {@link View#INVISIBLE}, or
95          *            {@link View#GONE}.
96          * @throws IllegalArgumentException if visibility is not one of {@link View#VISIBLE},
97          * {@link View#INVISIBLE}, or {@link View#GONE}.
98          */
99         @NonNull
setVisibility(@dRes int id, @Visibility int visibility)100         public Builder setVisibility(@IdRes int id, @Visibility int visibility) {
101             throwIfDestroyed();
102             switch (visibility) {
103                 case View.VISIBLE:
104                 case View.INVISIBLE:
105                 case View.GONE:
106                     mVisibilities.put(id, visibility);
107                     return this;
108             }
109             throw new IllegalArgumentException("Invalid visibility: " + visibility);
110         }
111 
112         /**
113          * Creates a new {@link VisibilitySetterAction} instance.
114          */
115         @NonNull
build()116         public VisibilitySetterAction build() {
117             throwIfDestroyed();
118             mDestroyed = true;
119             return new VisibilitySetterAction(this);
120         }
121 
throwIfDestroyed()122         private void throwIfDestroyed() {
123             Preconditions.checkState(!mDestroyed, "Already called build()");
124         }
125     }
126 
127     /////////////////////////////////////
128     // Object "contract" methods. //
129     /////////////////////////////////////
130     @Override
toString()131     public String toString() {
132         if (!sDebug) return super.toString();
133 
134         return "VisibilitySetterAction: [" + mVisibilities + "]";
135     }
136 
137     /////////////////////////////////////
138     // Parcelable "contract" methods. //
139     /////////////////////////////////////
140     @Override
describeContents()141     public int describeContents() {
142         return 0;
143     }
144 
145     @Override
writeToParcel(Parcel parcel, int flags)146     public void writeToParcel(Parcel parcel, int flags) {
147         parcel.writeSparseIntArray(mVisibilities);
148     }
149 
150     public static final @android.annotation.NonNull Parcelable.Creator<VisibilitySetterAction> CREATOR =
151             new Parcelable.Creator<VisibilitySetterAction>() {
152 
153         @NonNull
154         @Override
155         public VisibilitySetterAction createFromParcel(Parcel parcel) {
156             // Always go through the builder to ensure the data ingested by
157             // the system obeys the contract of the builder to avoid attacks
158             final SparseIntArray visibilities = parcel.readSparseIntArray();
159             Builder builder = null;
160             for (int i = 0; i < visibilities.size(); i++) {
161                 final int id = visibilities.keyAt(i);
162                 final int visibility = visibilities.valueAt(i);
163                 if (builder == null) {
164                     builder = new Builder(id, visibility);
165                 } else {
166                     builder.setVisibility(id, visibility);
167                 }
168             }
169             return builder == null ? null : builder.build();
170         }
171 
172         @NonNull
173         @Override
174         public VisibilitySetterAction[] newArray(int size) {
175             return new VisibilitySetterAction[size];
176         }
177     };
178 }
179