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.os.PatternMatcher; 27 import android.util.proto.ProtoOutputStream; 28 29 import org.xmlpull.v1.XmlPullParser; 30 import org.xmlpull.v1.XmlPullParserException; 31 import org.xmlpull.v1.XmlSerializer; 32 33 import java.io.IOException; 34 import java.lang.annotation.Retention; 35 import java.lang.annotation.RetentionPolicy; 36 37 /** 38 * A filter for matching Intent URI Data as part of a 39 * {@link UriRelativeFilterGroup}. A single filter can only be 40 * matched against either a URI path, query or fragment 41 */ 42 @FlaggedApi(Flags.FLAG_RELATIVE_REFERENCE_INTENT_FILTERS) 43 public final class UriRelativeFilter { 44 private static final String FILTER_STR = "filter"; 45 private static final String PART_STR = "part"; 46 private static final String PATTERN_STR = "pattern"; 47 static final String URI_RELATIVE_FILTER_STR = "uriRelativeFilter"; 48 49 /** 50 * Value to indicate that the filter is to be applied to a URI path. 51 */ 52 public static final int PATH = 0; 53 /** 54 * Value to indicate that the filter is to be applied to a URI query. 55 */ 56 public static final int QUERY = 1; 57 /** 58 * Value to indicate that the filter is to be applied to a URI fragment. 59 */ 60 public static final int FRAGMENT = 2; 61 62 /** @hide */ 63 @IntDef(value = { 64 PATH, 65 QUERY, 66 FRAGMENT 67 }) 68 @Retention(RetentionPolicy.SOURCE) 69 public @interface UriPart {} 70 71 private final @UriPart int mUriPart; 72 private final @PatternMatcher.PatternType int mPatternType; 73 private final String mFilter; 74 75 /** 76 * Creates a new UriRelativeFilter. 77 * 78 * @param uriPart The URI part this filter operates on. Can be either a 79 * {@link UriRelativeFilter#PATH}, {@link UriRelativeFilter#QUERY}, 80 * or {@link UriRelativeFilter#FRAGMENT}. 81 * @param patternType The pattern type of the filter. Can be either a 82 * {@link PatternMatcher#PATTERN_LITERAL}, 83 * {@link PatternMatcher#PATTERN_PREFIX}, 84 * {@link PatternMatcher#PATTERN_SUFFIX}, 85 * {@link PatternMatcher#PATTERN_SIMPLE_GLOB}, 86 * or {@link PatternMatcher#PATTERN_ADVANCED_GLOB}. 87 * @param filter A literal or pattern string depedning on patterType 88 * used to match a uriPart . 89 */ UriRelativeFilter( @riPart int uriPart, @PatternMatcher.PatternType int patternType, @NonNull String filter)90 public UriRelativeFilter( 91 @UriPart int uriPart, 92 @PatternMatcher.PatternType int patternType, 93 @NonNull String filter) { 94 mUriPart = uriPart; 95 com.android.internal.util.AnnotationValidations.validate( 96 UriPart.class, null, mUriPart); 97 mPatternType = patternType; 98 com.android.internal.util.AnnotationValidations.validate( 99 PatternMatcher.PatternType.class, null, mPatternType); 100 mFilter = filter; 101 com.android.internal.util.AnnotationValidations.validate( 102 NonNull.class, null, mFilter); 103 } 104 105 /** 106 * The URI part this filter operates on. 107 */ getUriPart()108 public @UriPart int getUriPart() { 109 return mUriPart; 110 } 111 112 /** 113 * The pattern type of the filter. 114 */ getPatternType()115 public @PatternMatcher.PatternType int getPatternType() { 116 return mPatternType; 117 } 118 119 /** 120 * The string used to filter the URI. 121 */ getFilter()122 public @NonNull String getFilter() { 123 return mFilter; 124 } 125 126 /** 127 * Match this URI filter against an Intent's data. QUERY filters can 128 * match against any key value pair in the query string. PATH and 129 * FRAGMENT filters must match the entire string. 130 * 131 * @param data The full data string to match against, as supplied in 132 * Intent.data. 133 * 134 * @return true if there is a match. 135 */ matchData(@onNull Uri data)136 public boolean matchData(@NonNull Uri data) { 137 PatternMatcher pe = new PatternMatcher(mFilter, mPatternType); 138 switch (getUriPart()) { 139 case PATH: 140 return pe.match(data.getPath()); 141 case QUERY: 142 return matchQuery(pe, data.getQuery()); 143 case FRAGMENT: 144 return pe.match(data.getFragment()); 145 default: 146 return false; 147 } 148 } 149 matchQuery(PatternMatcher pe, String query)150 private boolean matchQuery(PatternMatcher pe, String query) { 151 if (query != null) { 152 String[] params = query.split("&"); 153 if (params.length == 1) { 154 params = query.split(";"); 155 } 156 for (int i = 0; i < params.length; i++) { 157 if (pe.match(params[i])) return true; 158 } 159 } 160 return false; 161 } 162 163 /** @hide */ dumpDebug(ProtoOutputStream proto, long fieldId)164 public void dumpDebug(ProtoOutputStream proto, long fieldId) { 165 long token = proto.start(fieldId); 166 proto.write(UriRelativeFilterProto.URI_PART, mUriPart); 167 proto.write(UriRelativeFilterProto.PATTERN_TYPE, mPatternType); 168 proto.write(UriRelativeFilterProto.FILTER, mFilter); 169 proto.end(token); 170 } 171 172 /** @hide */ writeToXml(XmlSerializer serializer)173 public void writeToXml(XmlSerializer serializer) throws IOException { 174 serializer.startTag(null, URI_RELATIVE_FILTER_STR); 175 serializer.attribute(null, PATTERN_STR, Integer.toString(mPatternType)); 176 serializer.attribute(null, PART_STR, Integer.toString(mUriPart)); 177 serializer.attribute(null, FILTER_STR, mFilter); 178 serializer.endTag(null, URI_RELATIVE_FILTER_STR); 179 } 180 uriPartToString()181 private String uriPartToString() { 182 switch (mUriPart) { 183 case PATH: 184 return "PATH"; 185 case QUERY: 186 return "QUERY"; 187 case FRAGMENT: 188 return "FRAGMENT"; 189 default: 190 return "UNKNOWN"; 191 } 192 } 193 patternTypeToString()194 private String patternTypeToString() { 195 switch (mPatternType) { 196 case PatternMatcher.PATTERN_LITERAL: 197 return "LITERAL"; 198 case PatternMatcher.PATTERN_PREFIX: 199 return "PREFIX"; 200 case PatternMatcher.PATTERN_SIMPLE_GLOB: 201 return "GLOB"; 202 case PatternMatcher.PATTERN_ADVANCED_GLOB: 203 return "ADVANCED_GLOB"; 204 case PatternMatcher.PATTERN_SUFFIX: 205 return "SUFFIX"; 206 default: 207 return "UNKNOWN"; 208 } 209 } 210 211 @Override toString()212 public String toString() { 213 return "UriRelativeFilter { " 214 + "uriPart = " + uriPartToString() + ", " 215 + "patternType = " + patternTypeToString() + ", " 216 + "filter = " + mFilter 217 + " }"; 218 } 219 220 /** @hide */ toParcel()221 public UriRelativeFilterParcel toParcel() { 222 UriRelativeFilterParcel parcel = new UriRelativeFilterParcel(); 223 parcel.uriPart = mUriPart; 224 parcel.patternType = mPatternType; 225 parcel.filter = mFilter; 226 return parcel; 227 } 228 229 @Override equals(@ullable Object o)230 public boolean equals(@Nullable Object o) { 231 if (this == o) return true; 232 if (o == null || getClass() != o.getClass()) return false; 233 @SuppressWarnings("unchecked") 234 UriRelativeFilter that = (UriRelativeFilter) o; 235 return mUriPart == that.mUriPart 236 && mPatternType == that.mPatternType 237 && java.util.Objects.equals(mFilter, that.mFilter); 238 } 239 240 @Override hashCode()241 public int hashCode() { 242 int _hash = 1; 243 _hash = 31 * _hash + mUriPart; 244 _hash = 31 * _hash + mPatternType; 245 _hash = 31 * _hash + java.util.Objects.hashCode(mFilter); 246 return _hash; 247 } 248 249 /** @hide */ writeToParcel(@onNull Parcel dest, int flags)250 public void writeToParcel(@NonNull Parcel dest, int flags) { 251 dest.writeInt(mUriPart); 252 dest.writeInt(mPatternType); 253 dest.writeString(mFilter); 254 } 255 256 /** @hide */ UriRelativeFilter(@onNull android.os.Parcel in)257 UriRelativeFilter(@NonNull android.os.Parcel in) { 258 mUriPart = in.readInt(); 259 mPatternType = in.readInt(); 260 mFilter = in.readString(); 261 } 262 263 /** @hide */ UriRelativeFilter(XmlPullParser parser)264 public UriRelativeFilter(XmlPullParser parser) throws XmlPullParserException, IOException { 265 mUriPart = Integer.parseInt(parser.getAttributeValue(null, PART_STR)); 266 mPatternType = Integer.parseInt(parser.getAttributeValue(null, PATTERN_STR)); 267 mFilter = parser.getAttributeValue(null, FILTER_STR); 268 } 269 270 /** @hide */ UriRelativeFilter(UriRelativeFilterParcel parcel)271 public UriRelativeFilter(UriRelativeFilterParcel parcel) { 272 mUriPart = parcel.uriPart; 273 mPatternType = parcel.patternType; 274 mFilter = parcel.filter; 275 } 276 } 277