• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2024 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.content;
18 
19 import android.annotation.FlaggedApi;
20 import android.annotation.IntDef;
21 import android.annotation.NonNull;
22 import android.annotation.Nullable;
23 import android.content.pm.Flags;
24 import android.net.Uri;
25 import android.os.Parcel;
26 import android.util.ArraySet;
27 import android.util.Log;
28 import android.util.proto.ProtoOutputStream;
29 
30 import com.android.internal.util.CollectionUtils;
31 import com.android.internal.util.XmlUtils;
32 
33 import org.xmlpull.v1.XmlPullParser;
34 import org.xmlpull.v1.XmlPullParserException;
35 import org.xmlpull.v1.XmlSerializer;
36 
37 import java.io.IOException;
38 import java.lang.annotation.Retention;
39 import java.lang.annotation.RetentionPolicy;
40 import java.util.ArrayList;
41 import java.util.Collection;
42 import java.util.Collections;
43 import java.util.Iterator;
44 import java.util.List;
45 import java.util.Objects;
46 
47 /**
48  * An intent data matching group based on a URI's relative reference which
49  * includes the path, query and fragment.  The group is only considered as
50  * matching if <em>all</em> UriRelativeFilters in the group match.  Each
51  * UriRelativeFilter defines a matching rule for a URI path, query or fragment.
52  * A group must contain one or more UriRelativeFilters to match but does not need to
53  * contain UriRelativeFilters for all existing parts of a URI to match.
54  *
55  * <p>For example, given a URI that contains path, query and fragment parts,
56  * a group containing only a path filter will match the URI if the path
57  * filter matches the URI path.  If the group contains a path and query
58  * filter, then the group will only match if both path and query filters
59  * match.  If a URI contains only a path with no query or fragment then a
60  * group can only match if it contains only a matching path filter. If the
61  * group also contained additional query or fragment filters then it will
62  * not match.</p>
63  */
64 @FlaggedApi(Flags.FLAG_RELATIVE_REFERENCE_INTENT_FILTERS)
65 public final class UriRelativeFilterGroup {
66     private static final String TAG = "UriRelativeFilterGroup";
67     private static final String ALLOW_STR = "allow";
68     private static final String URI_RELATIVE_FILTER_GROUP_STR = "uriRelativeFilterGroup";
69 
70     /**
71      * Value to indicate that the group match is allowed.
72      */
73     public static final int ACTION_ALLOW = 0;
74     /**
75      * Value to indicate that the group match is blocked.
76      */
77     public static final int ACTION_BLOCK = 1;
78 
79     /** @hide */
80     @IntDef(value = {
81             ACTION_ALLOW,
82             ACTION_BLOCK
83     })
84     @Retention(RetentionPolicy.SOURCE)
85     public @interface Action {}
86 
87     private final @Action int mAction;
88     private final ArraySet<UriRelativeFilter> mUriRelativeFilters = new ArraySet<>();
89 
90     /** @hide */
matchGroupsToUri(List<UriRelativeFilterGroup> groups, Uri uri)91     public static boolean matchGroupsToUri(List<UriRelativeFilterGroup> groups, Uri uri) {
92         for (int i = 0; i < groups.size(); i++) {
93             if (groups.get(i).matchData(uri)) {
94                 return groups.get(i).getAction() == UriRelativeFilterGroup.ACTION_ALLOW;
95             }
96         }
97         return false;
98     }
99 
100     /** @hide */
parcelsToGroups( @ullable List<UriRelativeFilterGroupParcel> parcels)101     public static List<UriRelativeFilterGroup> parcelsToGroups(
102             @Nullable List<UriRelativeFilterGroupParcel> parcels) {
103         List<UriRelativeFilterGroup> groups = new ArrayList<>();
104         if (parcels != null) {
105             for (int i = 0; i < parcels.size(); i++) {
106                 groups.add(new UriRelativeFilterGroup(parcels.get(i)));
107             }
108         }
109         return groups;
110     }
111 
112     /** @hide */
groupsToParcels( @ullable List<UriRelativeFilterGroup> groups)113     public static List<UriRelativeFilterGroupParcel> groupsToParcels(
114             @Nullable List<UriRelativeFilterGroup> groups) {
115         List<UriRelativeFilterGroupParcel> parcels = new ArrayList<>();
116         if (groups != null) {
117             for (int i = 0; i < groups.size(); i++) {
118                 parcels.add(groups.get(i).toParcel());
119             }
120         }
121         return parcels;
122     }
123 
124     /**
125      * New UriRelativeFilterGroup that matches a Intent data.
126      *
127      * @param action Whether this matching group should be allowed or disallowed.
128      */
UriRelativeFilterGroup(@ction int action)129     public UriRelativeFilterGroup(@Action int action) {
130         mAction = action;
131     }
132 
133     /** @hide */
UriRelativeFilterGroup(XmlPullParser parser)134     public UriRelativeFilterGroup(XmlPullParser parser) throws XmlPullParserException, IOException {
135         mAction = Integer.parseInt(parser.getAttributeValue(null, ALLOW_STR));
136 
137         int outerDepth = parser.getDepth();
138         int type;
139         while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
140                 && (type != XmlPullParser.END_TAG
141                 || parser.getDepth() > outerDepth)) {
142             if (type == XmlPullParser.END_TAG
143                     || type == XmlPullParser.TEXT) {
144                 continue;
145             }
146 
147             String tagName = parser.getName();
148             if (tagName.equals(UriRelativeFilter.URI_RELATIVE_FILTER_STR)) {
149                 addUriRelativeFilter(new UriRelativeFilter(parser));
150             } else {
151                 Log.w("IntentFilter", "Unknown tag parsing IntentFilter: " + tagName);
152             }
153             XmlUtils.skipCurrentTag(parser);
154         }
155     }
156 
157     /**
158      * Return {@link UriRelativeFilterGroup#ACTION_ALLOW} if a URI is allowed when matched
159      * and {@link UriRelativeFilterGroup#ACTION_BLOCK} if a URI is blacked when matched.
160      */
getAction()161     public @Action int getAction() {
162         return mAction;
163     }
164 
165     /**
166      * Add a filter to the group.
167      */
addUriRelativeFilter(@onNull UriRelativeFilter uriRelativeFilter)168     public void addUriRelativeFilter(@NonNull UriRelativeFilter uriRelativeFilter) {
169         Objects.requireNonNull(uriRelativeFilter);
170         if (!CollectionUtils.contains(mUriRelativeFilters, uriRelativeFilter)) {
171             mUriRelativeFilters.add(uriRelativeFilter);
172         }
173     }
174 
175     /**
176      * Returns a unmodifiable view of the UriRelativeFilters list in this group.
177      */
178     @NonNull
getUriRelativeFilters()179     public Collection<UriRelativeFilter> getUriRelativeFilters() {
180         return Collections.unmodifiableCollection(mUriRelativeFilters);
181     }
182 
183     /**
184      * Match all URI filter in this group against {@link Intent#getData()}.
185      *
186      * @param data The full data string to match against, as supplied in
187      *             Intent.data.
188      * @return true if all filters match.
189      */
matchData(@onNull Uri data)190     public boolean matchData(@NonNull Uri data) {
191         if (mUriRelativeFilters.size() == 0) {
192             return false;
193         }
194         for (UriRelativeFilter filter : mUriRelativeFilters) {
195             if (!filter.matchData(data)) {
196                 return false;
197             }
198         }
199         return true;
200     }
201 
202     /** @hide */
dumpDebug(ProtoOutputStream proto, long fieldId)203     public void dumpDebug(ProtoOutputStream proto, long fieldId) {
204         long token = proto.start(fieldId);
205         proto.write(UriRelativeFilterGroupProto.ACTION, mAction);
206         Iterator<UriRelativeFilter> it = mUriRelativeFilters.iterator();
207         while (it.hasNext()) {
208             it.next().dumpDebug(proto, UriRelativeFilterGroupProto.URI_RELATIVE_FILTERS);
209         }
210         proto.end(token);
211     }
212 
213     /** @hide */
writeToXml(XmlSerializer serializer)214     public void writeToXml(XmlSerializer serializer) throws IOException {
215         serializer.startTag(null, URI_RELATIVE_FILTER_GROUP_STR);
216         serializer.attribute(null, ALLOW_STR, Integer.toString(mAction));
217         Iterator<UriRelativeFilter> it = mUriRelativeFilters.iterator();
218         while (it.hasNext()) {
219             UriRelativeFilter filter = it.next();
220             filter.writeToXml(serializer);
221         }
222         serializer.endTag(null, URI_RELATIVE_FILTER_GROUP_STR);
223     }
224 
225     @Override
toString()226     public String toString() {
227         return "UriRelativeFilterGroup { allow = " + mAction
228                 + ", uri_filters = " + mUriRelativeFilters + ",  }";
229     }
230 
231     /** @hide */
writeToParcel(@onNull Parcel dest, int flags)232     public void writeToParcel(@NonNull Parcel dest, int flags) {
233         dest.writeInt(mAction);
234         final int n = mUriRelativeFilters.size();
235         if (n > 0) {
236             dest.writeInt(n);
237             int i = 0;
238             Iterator<UriRelativeFilter> it = mUriRelativeFilters.iterator();
239             while (it.hasNext()) {
240                 it.next().writeToParcel(dest, flags);
241                 i++;
242             }
243             if (i != n) {
244                 Log.e(TAG, "UriRelativeFilters was unexpectedly"
245                         + " modified while writing to parcel. Expected "
246                         + n + " but found " + i + " filters", new Exception());
247             }
248         } else {
249             dest.writeInt(0);
250         }
251     }
252 
253     @Override
equals(@ullable Object o)254     public boolean equals(@Nullable Object o) {
255         if (this == o) return true;
256         if (o == null || getClass() != o.getClass()) return false;
257         @SuppressWarnings("unchecked")
258         UriRelativeFilterGroup that = (UriRelativeFilterGroup) o;
259         if (mAction != that.mAction) return false;
260         return mUriRelativeFilters.equals(that.mUriRelativeFilters);
261     }
262 
263     @Override
hashCode()264     public int hashCode() {
265         int _hash = 0;
266         _hash = 31 * _hash + mAction;
267         _hash = 31 * _hash + java.util.Objects.hashCode(mUriRelativeFilters);
268         return _hash;
269     }
270 
271     /** @hide */
toParcel()272     public UriRelativeFilterGroupParcel toParcel() {
273         UriRelativeFilterGroupParcel parcel = new UriRelativeFilterGroupParcel();
274         parcel.action = mAction;
275         parcel.filters = new ArrayList<>();
276         for (UriRelativeFilter filter : mUriRelativeFilters) {
277             parcel.filters.add(filter.toParcel());
278         }
279         return parcel;
280     }
281 
282     /** @hide */
UriRelativeFilterGroup(@onNull Parcel src)283     UriRelativeFilterGroup(@NonNull Parcel src) {
284         mAction = src.readInt();
285         final int n = src.readInt();
286         for (int i = 0; i < n; i++) {
287             mUriRelativeFilters.add(new UriRelativeFilter(src));
288         }
289     }
290 
291     /** @hide */
UriRelativeFilterGroup(UriRelativeFilterGroupParcel parcel)292     public UriRelativeFilterGroup(UriRelativeFilterGroupParcel parcel) {
293         mAction = parcel.action;
294         for (int i = 0; i < parcel.filters.size(); i++) {
295             mUriRelativeFilters.add(new UriRelativeFilter(parcel.filters.get(i)));
296         }
297     }
298 }
299