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 ALLOW_STR = "allow"; 67 private static final String URI_RELATIVE_FILTER_GROUP_STR = "uriRelativeFilterGroup"; 68 69 /** 70 * Value to indicate that the group match is allowed. 71 */ 72 public static final int ACTION_ALLOW = 0; 73 /** 74 * Value to indicate that the group match is blocked. 75 */ 76 public static final int ACTION_BLOCK = 1; 77 78 /** @hide */ 79 @IntDef(value = { 80 ACTION_ALLOW, 81 ACTION_BLOCK 82 }) 83 @Retention(RetentionPolicy.SOURCE) 84 public @interface Action {} 85 86 private final @Action int mAction; 87 private final ArraySet<UriRelativeFilter> mUriRelativeFilters = new ArraySet<>(); 88 89 /** @hide */ matchGroupsToUri(List<UriRelativeFilterGroup> groups, Uri uri)90 public static boolean matchGroupsToUri(List<UriRelativeFilterGroup> groups, Uri uri) { 91 for (int i = 0; i < groups.size(); i++) { 92 if (groups.get(i).matchData(uri)) { 93 return groups.get(i).getAction() == UriRelativeFilterGroup.ACTION_ALLOW; 94 } 95 } 96 return false; 97 } 98 99 /** @hide */ parcelsToGroups( @ullable List<UriRelativeFilterGroupParcel> parcels)100 public static List<UriRelativeFilterGroup> parcelsToGroups( 101 @Nullable List<UriRelativeFilterGroupParcel> parcels) { 102 List<UriRelativeFilterGroup> groups = new ArrayList<>(); 103 if (parcels != null) { 104 for (int i = 0; i < parcels.size(); i++) { 105 groups.add(new UriRelativeFilterGroup(parcels.get(i))); 106 } 107 } 108 return groups; 109 } 110 111 /** @hide */ groupsToParcels( @ullable List<UriRelativeFilterGroup> groups)112 public static List<UriRelativeFilterGroupParcel> groupsToParcels( 113 @Nullable List<UriRelativeFilterGroup> groups) { 114 List<UriRelativeFilterGroupParcel> parcels = new ArrayList<>(); 115 if (groups != null) { 116 for (int i = 0; i < groups.size(); i++) { 117 parcels.add(groups.get(i).toParcel()); 118 } 119 } 120 return parcels; 121 } 122 123 /** 124 * New UriRelativeFilterGroup that matches a Intent data. 125 * 126 * @param action Whether this matching group should be allowed or disallowed. 127 */ UriRelativeFilterGroup(@ction int action)128 public UriRelativeFilterGroup(@Action int action) { 129 mAction = action; 130 } 131 132 /** @hide */ UriRelativeFilterGroup(XmlPullParser parser)133 public UriRelativeFilterGroup(XmlPullParser parser) throws XmlPullParserException, IOException { 134 mAction = Integer.parseInt(parser.getAttributeValue(null, ALLOW_STR)); 135 136 int outerDepth = parser.getDepth(); 137 int type; 138 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT 139 && (type != XmlPullParser.END_TAG 140 || parser.getDepth() > outerDepth)) { 141 if (type == XmlPullParser.END_TAG 142 || type == XmlPullParser.TEXT) { 143 continue; 144 } 145 146 String tagName = parser.getName(); 147 if (tagName.equals(UriRelativeFilter.URI_RELATIVE_FILTER_STR)) { 148 addUriRelativeFilter(new UriRelativeFilter(parser)); 149 } else { 150 Log.w("IntentFilter", "Unknown tag parsing IntentFilter: " + tagName); 151 } 152 XmlUtils.skipCurrentTag(parser); 153 } 154 } 155 156 /** 157 * Return {@link UriRelativeFilterGroup#ACTION_ALLOW} if a URI is allowed when matched 158 * and {@link UriRelativeFilterGroup#ACTION_BLOCK} if a URI is blacked when matched. 159 */ getAction()160 public @Action int getAction() { 161 return mAction; 162 } 163 164 /** 165 * Add a filter to the group. 166 */ addUriRelativeFilter(@onNull UriRelativeFilter uriRelativeFilter)167 public void addUriRelativeFilter(@NonNull UriRelativeFilter uriRelativeFilter) { 168 Objects.requireNonNull(uriRelativeFilter); 169 if (!CollectionUtils.contains(mUriRelativeFilters, uriRelativeFilter)) { 170 mUriRelativeFilters.add(uriRelativeFilter); 171 } 172 } 173 174 /** 175 * Returns a unmodifiable view of the UriRelativeFilters list in this group. 176 */ 177 @NonNull getUriRelativeFilters()178 public Collection<UriRelativeFilter> getUriRelativeFilters() { 179 return Collections.unmodifiableCollection(mUriRelativeFilters); 180 } 181 182 /** 183 * Match all URI filter in this group against {@link Intent#getData()}. 184 * 185 * @param data The full data string to match against, as supplied in 186 * Intent.data. 187 * @return true if all filters match. 188 */ matchData(@onNull Uri data)189 public boolean matchData(@NonNull Uri data) { 190 if (mUriRelativeFilters.size() == 0) { 191 return false; 192 } 193 for (UriRelativeFilter filter : mUriRelativeFilters) { 194 if (!filter.matchData(data)) { 195 return false; 196 } 197 } 198 return true; 199 } 200 201 /** @hide */ dumpDebug(ProtoOutputStream proto, long fieldId)202 public void dumpDebug(ProtoOutputStream proto, long fieldId) { 203 long token = proto.start(fieldId); 204 proto.write(UriRelativeFilterGroupProto.ACTION, mAction); 205 Iterator<UriRelativeFilter> it = mUriRelativeFilters.iterator(); 206 while (it.hasNext()) { 207 it.next().dumpDebug(proto, UriRelativeFilterGroupProto.URI_RELATIVE_FILTERS); 208 } 209 proto.end(token); 210 } 211 212 /** @hide */ writeToXml(XmlSerializer serializer)213 public void writeToXml(XmlSerializer serializer) throws IOException { 214 serializer.startTag(null, URI_RELATIVE_FILTER_GROUP_STR); 215 serializer.attribute(null, ALLOW_STR, Integer.toString(mAction)); 216 Iterator<UriRelativeFilter> it = mUriRelativeFilters.iterator(); 217 while (it.hasNext()) { 218 UriRelativeFilter filter = it.next(); 219 filter.writeToXml(serializer); 220 } 221 serializer.endTag(null, URI_RELATIVE_FILTER_GROUP_STR); 222 } 223 224 @Override toString()225 public String toString() { 226 return "UriRelativeFilterGroup { allow = " + mAction 227 + ", uri_filters = " + mUriRelativeFilters + ", }"; 228 } 229 230 /** @hide */ writeToParcel(@onNull Parcel dest, int flags)231 public void writeToParcel(@NonNull Parcel dest, int flags) { 232 dest.writeInt(mAction); 233 final int n = mUriRelativeFilters.size(); 234 if (n > 0) { 235 dest.writeInt(n); 236 Iterator<UriRelativeFilter> it = mUriRelativeFilters.iterator(); 237 while (it.hasNext()) { 238 it.next().writeToParcel(dest, flags); 239 } 240 } else { 241 dest.writeInt(0); 242 } 243 } 244 245 @Override equals(@ullable Object o)246 public boolean equals(@Nullable Object o) { 247 if (this == o) return true; 248 if (o == null || getClass() != o.getClass()) return false; 249 @SuppressWarnings("unchecked") 250 UriRelativeFilterGroup that = (UriRelativeFilterGroup) o; 251 if (mAction != that.mAction) return false; 252 return mUriRelativeFilters.equals(that.mUriRelativeFilters); 253 } 254 255 @Override hashCode()256 public int hashCode() { 257 int _hash = 0; 258 _hash = 31 * _hash + mAction; 259 _hash = 31 * _hash + java.util.Objects.hashCode(mUriRelativeFilters); 260 return _hash; 261 } 262 263 /** @hide */ toParcel()264 public UriRelativeFilterGroupParcel toParcel() { 265 UriRelativeFilterGroupParcel parcel = new UriRelativeFilterGroupParcel(); 266 parcel.action = mAction; 267 parcel.filters = new ArrayList<>(); 268 for (UriRelativeFilter filter : mUriRelativeFilters) { 269 parcel.filters.add(filter.toParcel()); 270 } 271 return parcel; 272 } 273 274 /** @hide */ UriRelativeFilterGroup(@onNull Parcel src)275 UriRelativeFilterGroup(@NonNull Parcel src) { 276 mAction = src.readInt(); 277 final int n = src.readInt(); 278 for (int i = 0; i < n; i++) { 279 mUriRelativeFilters.add(new UriRelativeFilter(src)); 280 } 281 } 282 283 /** @hide */ UriRelativeFilterGroup(UriRelativeFilterGroupParcel parcel)284 public UriRelativeFilterGroup(UriRelativeFilterGroupParcel parcel) { 285 mAction = parcel.action; 286 for (int i = 0; i < parcel.filters.size(); i++) { 287 mUriRelativeFilters.add(new UriRelativeFilter(parcel.filters.get(i))); 288 } 289 } 290 } 291