1 /* 2 * Copyright (C) 2006 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.compat.annotation.UnsupportedAppUsage; 20 import android.net.Uri; 21 22 import java.util.ArrayList; 23 import java.util.List; 24 25 /** 26 Utility class to aid in matching URIs in content providers. 27 28 <p>To use this class, build up a tree of <code>UriMatcher</code> objects. 29 For example: 30 <pre> 31 private static final int PEOPLE = 1; 32 private static final int PEOPLE_ID = 2; 33 private static final int PEOPLE_PHONES = 3; 34 private static final int PEOPLE_PHONES_ID = 4; 35 private static final int PEOPLE_CONTACTMETHODS = 7; 36 private static final int PEOPLE_CONTACTMETHODS_ID = 8; 37 38 private static final int DELETED_PEOPLE = 20; 39 40 private static final int PHONES = 9; 41 private static final int PHONES_ID = 10; 42 private static final int PHONES_FILTER = 14; 43 44 private static final int CONTACTMETHODS = 18; 45 private static final int CONTACTMETHODS_ID = 19; 46 47 private static final int CALLS = 11; 48 private static final int CALLS_ID = 12; 49 private static final int CALLS_FILTER = 15; 50 51 private static final UriMatcher sURIMatcher = new UriMatcher(UriMatcher.NO_MATCH); 52 53 static 54 { 55 sURIMatcher.addURI("contacts", "people", PEOPLE); 56 sURIMatcher.addURI("contacts", "people/#", PEOPLE_ID); 57 sURIMatcher.addURI("contacts", "people/#/phones", PEOPLE_PHONES); 58 sURIMatcher.addURI("contacts", "people/#/phones/#", PEOPLE_PHONES_ID); 59 sURIMatcher.addURI("contacts", "people/#/contact_methods", PEOPLE_CONTACTMETHODS); 60 sURIMatcher.addURI("contacts", "people/#/contact_methods/#", PEOPLE_CONTACTMETHODS_ID); 61 sURIMatcher.addURI("contacts", "deleted_people", DELETED_PEOPLE); 62 sURIMatcher.addURI("contacts", "phones", PHONES); 63 sURIMatcher.addURI("contacts", "phones/filter/*", PHONES_FILTER); 64 sURIMatcher.addURI("contacts", "phones/#", PHONES_ID); 65 sURIMatcher.addURI("contacts", "contact_methods", CONTACTMETHODS); 66 sURIMatcher.addURI("contacts", "contact_methods/#", CONTACTMETHODS_ID); 67 sURIMatcher.addURI("call_log", "calls", CALLS); 68 sURIMatcher.addURI("call_log", "calls/filter/*", CALLS_FILTER); 69 sURIMatcher.addURI("call_log", "calls/#", CALLS_ID); 70 } 71 </pre> 72 <p>Starting from API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2}, paths can start 73 with a leading slash. For example: 74 <pre> 75 sURIMatcher.addURI("contacts", "/people", PEOPLE); 76 </pre> 77 <p>Then when you need to match against a URI, call {@link #match}, providing 78 the URL that you have been given. You can use the result to build a query, 79 return a type, insert or delete a row, or whatever you need, without duplicating 80 all of the if-else logic that you would otherwise need. For example: 81 <pre> 82 public String getType(Uri url) 83 { 84 int match = sURIMatcher.match(url); 85 switch (match) 86 { 87 case PEOPLE: 88 return "vnd.android.cursor.dir/person"; 89 case PEOPLE_ID: 90 return "vnd.android.cursor.item/person"; 91 ... snip ... 92 return "vnd.android.cursor.dir/snail-mail"; 93 case PEOPLE_ADDRESS_ID: 94 return "vnd.android.cursor.item/snail-mail"; 95 default: 96 return null; 97 } 98 } 99 </pre> 100 instead of: 101 <pre> 102 public String getType(Uri url) 103 { 104 List<String> pathSegments = url.getPathSegments(); 105 if (pathSegments.size() >= 2) { 106 if ("people".equals(pathSegments.get(1))) { 107 if (pathSegments.size() == 2) { 108 return "vnd.android.cursor.dir/person"; 109 } else if (pathSegments.size() == 3) { 110 return "vnd.android.cursor.item/person"; 111 ... snip ... 112 return "vnd.android.cursor.dir/snail-mail"; 113 } else if (pathSegments.size() == 3) { 114 return "vnd.android.cursor.item/snail-mail"; 115 } 116 } 117 } 118 return null; 119 } 120 </pre> 121 */ 122 @android.ravenwood.annotation.RavenwoodKeepWholeClass 123 public class UriMatcher 124 { 125 public static final int NO_MATCH = -1; 126 /** 127 * Creates the root node of the URI tree. 128 * 129 * @param code the code to match for the root URI 130 */ UriMatcher(int code)131 public UriMatcher(int code) 132 { 133 mCode = code; 134 mWhich = -1; 135 mChildren = new ArrayList<UriMatcher>(); 136 mText = null; 137 } 138 UriMatcher(int which, String text)139 private UriMatcher(int which, String text) 140 { 141 mCode = NO_MATCH; 142 mWhich = which; 143 mChildren = new ArrayList<UriMatcher>(); 144 mText = text; 145 } 146 147 /** 148 * Add a URI to match, and the code to return when this URI is 149 * matched. URI nodes may be exact match string, the token "*" 150 * that matches any text, or the token "#" that matches only 151 * numbers. 152 * <p> 153 * Starting from API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2}, 154 * this method will accept a leading slash in the path. 155 * 156 * @param authority the authority to match 157 * @param path the path to match. * may be used as a wild card for 158 * any text, and # may be used as a wild card for numbers. 159 * @param code the code that is returned when a URI is matched 160 * against the given components. Must be positive. 161 */ addURI(String authority, String path, int code)162 public void addURI(String authority, String path, int code) 163 { 164 if (code < 0) { 165 throw new IllegalArgumentException("code " + code + " is invalid: it must be positive"); 166 } 167 168 String[] tokens = null; 169 if (path != null) { 170 String newPath = path; 171 // Strip leading slash if present. 172 if (path.length() > 1 && path.charAt(0) == '/') { 173 newPath = path.substring(1); 174 } 175 tokens = newPath.split("/"); 176 } 177 178 int numTokens = tokens != null ? tokens.length : 0; 179 UriMatcher node = this; 180 for (int i = -1; i < numTokens; i++) { 181 String token = i < 0 ? authority : tokens[i]; 182 ArrayList<UriMatcher> children = node.mChildren; 183 int numChildren = children.size(); 184 UriMatcher child; 185 int j; 186 for (j = 0; j < numChildren; j++) { 187 child = children.get(j); 188 if (token.equals(child.mText)) { 189 node = child; 190 break; 191 } 192 } 193 if (j == numChildren) { 194 // Child not found, create it 195 child = createChild(token); 196 node.mChildren.add(child); 197 node = child; 198 } 199 } 200 node.mCode = code; 201 } 202 203 private static UriMatcher createChild(String token) { 204 switch (token) { 205 case "#": 206 return new UriMatcher(NUMBER, "#"); 207 case "*": 208 return new UriMatcher(TEXT, "*"); 209 default: 210 return new UriMatcher(EXACT, token); 211 } 212 } 213 214 /** 215 * Try to match against the path in a url. 216 * 217 * @param uri The url whose path we will match against. 218 * 219 * @return The code for the matched node (added using addURI), 220 * or -1 if there is no matched node. 221 */ 222 public int match(Uri uri) 223 { 224 final List<String> pathSegments = uri.getPathSegments(); 225 final int li = pathSegments.size(); 226 227 UriMatcher node = this; 228 229 if (li == 0 && uri.getAuthority() == null) { 230 return this.mCode; 231 } 232 233 for (int i=-1; i<li; i++) { 234 String u = i < 0 ? uri.getAuthority() : pathSegments.get(i); 235 ArrayList<UriMatcher> list = node.mChildren; 236 if (list == null) { 237 break; 238 } 239 node = null; 240 int lj = list.size(); 241 for (int j=0; j<lj; j++) { 242 UriMatcher n = list.get(j); 243 which_switch: 244 switch (n.mWhich) { 245 case EXACT: 246 if (n.mText.equals(u)) { 247 node = n; 248 } 249 break; 250 case NUMBER: 251 int lk = u.length(); 252 for (int k=0; k<lk; k++) { 253 char c = u.charAt(k); 254 if (c < '0' || c > '9') { 255 break which_switch; 256 } 257 } 258 node = n; 259 break; 260 case TEXT: 261 node = n; 262 break; 263 } 264 if (node != null) { 265 break; 266 } 267 } 268 if (node == null) { 269 return NO_MATCH; 270 } 271 } 272 273 return node.mCode; 274 } 275 276 private static final int EXACT = 0; 277 private static final int NUMBER = 1; 278 private static final int TEXT = 2; 279 280 private int mCode; 281 private final int mWhich; 282 @UnsupportedAppUsage 283 private final String mText; 284 @UnsupportedAppUsage 285 private ArrayList<UriMatcher> mChildren; 286 } 287