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