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 package android.view.contentcapture;
17 
18 import android.annotation.IntDef;
19 import android.annotation.NonNull;
20 import android.app.ActivityThread;
21 import android.content.LocusId;
22 import android.os.Parcel;
23 import android.os.Parcelable;
24 import android.util.IntArray;
25 
26 import com.android.internal.util.Preconditions;
27 
28 import java.lang.annotation.Retention;
29 import java.lang.annotation.RetentionPolicy;
30 import java.util.ArrayList;
31 import java.util.List;
32 import java.util.Objects;
33 
34 /**
35  * Class used by apps to request the content capture service to remove data associated with
36  * {@link LocusId LocusIds}.
37  *
38  * <p>An app which has tagged data with a LocusId can therefore delete them later. This is intended
39  * to let apps propagate deletions of user data into the operating system.
40  */
41 public final class DataRemovalRequest implements Parcelable {
42 
43     /**
44      * When set, service should use the {@link LocusId#getId()} as prefix for the data to be
45      * removed.
46      */
47     public static final int FLAG_IS_PREFIX = 0x1;
48 
49     /** @hide */
50     @IntDef(prefix = { "FLAG" }, flag = true, value = {
51             FLAG_IS_PREFIX
52     })
53     @Retention(RetentionPolicy.SOURCE)
54     @interface Flags {}
55 
56     private final String mPackageName;
57 
58     private final boolean mForEverything;
59     private ArrayList<LocusIdRequest> mLocusIdRequests;
60 
DataRemovalRequest(@onNull Builder builder)61     private DataRemovalRequest(@NonNull Builder builder) {
62         mPackageName = ActivityThread.currentActivityThread().getApplication().getPackageName();
63         mForEverything = builder.mForEverything;
64         if (builder.mLocusIds != null) {
65             final int size = builder.mLocusIds.size();
66             mLocusIdRequests = new ArrayList<>(size);
67             for (int i = 0; i < size; i++) {
68                 mLocusIdRequests.add(new LocusIdRequest(builder.mLocusIds.get(i),
69                         builder.mFlags.get(i)));
70             }
71         }
72     }
73 
DataRemovalRequest(@onNull Parcel parcel)74     private DataRemovalRequest(@NonNull Parcel parcel) {
75         mPackageName = parcel.readString();
76         mForEverything = parcel.readBoolean();
77         if (!mForEverything) {
78             final int size = parcel.readInt();
79             mLocusIdRequests = new ArrayList<>(size);
80             for (int i = 0; i < size; i++) {
81                 mLocusIdRequests.add(new LocusIdRequest((LocusId) parcel.readValue(null),
82                         parcel.readInt()));
83             }
84         }
85     }
86 
87     /**
88      * Gets the name of the app that's making the request.
89      */
90     @NonNull
getPackageName()91     public String getPackageName() {
92         return mPackageName;
93     }
94 
95     /**
96      * Checks if app is requesting to remove content capture data associated with its package.
97      */
isForEverything()98     public boolean isForEverything() {
99         return mForEverything;
100     }
101 
102     /**
103      * Gets the list of {@code LousId}s the apps is requesting to remove.
104      */
105     @NonNull
getLocusIdRequests()106     public List<LocusIdRequest> getLocusIdRequests() {
107         return mLocusIdRequests;
108     }
109 
110     /**
111      * Builder for {@link DataRemovalRequest} objects.
112      */
113     public static final class Builder {
114 
115         private boolean mForEverything;
116         private ArrayList<LocusId> mLocusIds;
117         private IntArray mFlags;
118 
119         private boolean mDestroyed;
120 
121         /**
122          * Requests servive to remove all content capture data associated with the app's package.
123          *
124          * @return this builder
125          */
126         @NonNull
forEverything()127         public Builder forEverything() {
128             throwIfDestroyed();
129             Preconditions.checkState(mLocusIds == null, "Already added LocusIds");
130 
131             mForEverything = true;
132             return this;
133         }
134 
135         /**
136          * Request service to remove data associated with a given {@link LocusId}.
137          *
138          * @param locusId the {@link LocusId} being requested to be removed.
139          * @param flags either {@link DataRemovalRequest#FLAG_IS_PREFIX} or {@code 0}
140          *
141          * @return this builder
142          */
143         @NonNull
addLocusId(@onNull LocusId locusId, @Flags int flags)144         public Builder addLocusId(@NonNull LocusId locusId, @Flags int flags) {
145             throwIfDestroyed();
146             Preconditions.checkState(!mForEverything, "Already is for everything");
147             Objects.requireNonNull(locusId);
148             // felipeal: check flags
149 
150             if (mLocusIds == null) {
151                 mLocusIds = new ArrayList<>();
152                 mFlags = new IntArray();
153             }
154 
155             mLocusIds.add(locusId);
156             mFlags.add(flags);
157             return this;
158         }
159 
160         /**
161          * Builds the {@link DataRemovalRequest}.
162          */
163         @NonNull
build()164         public DataRemovalRequest build() {
165             throwIfDestroyed();
166 
167             Preconditions.checkState(mForEverything || mLocusIds != null,
168                     "must call either #forEverything() or add one #addLocusId()");
169 
170             mDestroyed = true;
171             return new DataRemovalRequest(this);
172         }
173 
throwIfDestroyed()174         private void throwIfDestroyed() {
175             Preconditions.checkState(!mDestroyed, "Already destroyed!");
176         }
177     }
178 
179     @Override
describeContents()180     public int describeContents() {
181         return 0;
182     }
183 
184     @Override
writeToParcel(Parcel parcel, int flags)185     public void writeToParcel(Parcel parcel, int flags) {
186         parcel.writeString(mPackageName);
187         parcel.writeBoolean(mForEverything);
188         if (!mForEverything) {
189             final int size = mLocusIdRequests.size();
190             parcel.writeInt(size);
191             for (int i = 0; i < size; i++) {
192                 final LocusIdRequest request = mLocusIdRequests.get(i);
193                 parcel.writeValue(request.getLocusId());
194                 parcel.writeInt(request.getFlags());
195             }
196         }
197     }
198 
199     public static final @android.annotation.NonNull Parcelable.Creator<DataRemovalRequest> CREATOR =
200             new Parcelable.Creator<DataRemovalRequest>() {
201 
202         @Override
203         @NonNull
204         public DataRemovalRequest createFromParcel(Parcel parcel) {
205             return new DataRemovalRequest(parcel);
206         }
207 
208         @Override
209         @NonNull
210         public DataRemovalRequest[] newArray(int size) {
211             return new DataRemovalRequest[size];
212         }
213     };
214 
215     /**
216      * Representation of a request to remove data associated with a {@link LocusId}.
217      */
218     public final class LocusIdRequest {
219         private final @NonNull LocusId mLocusId;
220         private final @Flags int mFlags;
221 
LocusIdRequest(@onNull LocusId locusId, @Flags int flags)222         private LocusIdRequest(@NonNull LocusId locusId, @Flags int flags) {
223             this.mLocusId = locusId;
224             this.mFlags = flags;
225         }
226 
227         /**
228          * Gets the {@code LocusId} per se.
229          */
230         @NonNull
getLocusId()231         public LocusId getLocusId() {
232             return mLocusId;
233         }
234 
235         /**
236          * Gets the flags associates with request.
237          *
238          * @return either {@link DataRemovalRequest#FLAG_IS_PREFIX} or {@code 0}.
239          */
240         @NonNull
getFlags()241         public @Flags int getFlags() {
242             return mFlags;
243         }
244     }
245 }
246