IntentResolver.java revision 54b6cfa9a9e5b861a9930af873580d6dc20f773c
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 17package com.android.server; 18 19import java.util.ArrayList; 20import java.util.Collections; 21import java.util.Comparator; 22import java.util.HashMap; 23import java.util.HashSet; 24import java.util.Iterator; 25import java.util.List; 26import java.util.Map; 27import java.util.Set; 28 29import android.util.Log; 30import android.util.LogPrinter; 31import android.util.Printer; 32 33import android.util.Config; 34import android.content.ContentResolver; 35import android.content.Intent; 36import android.content.IntentFilter; 37 38/** 39 * {@hide} 40 */ 41public class IntentResolver<F extends IntentFilter, R extends Object> { 42 final private static String TAG = "IntentResolver"; 43 final private static boolean DEBUG = false; 44 final private static boolean localLOGV = DEBUG || Config.LOGV; 45 46 public void addFilter(F f) { 47 if (localLOGV) { 48 Log.v(TAG, "Adding filter: " + f); 49 f.dump(new LogPrinter(Log.VERBOSE, TAG), " "); 50 Log.v(TAG, " Building Lookup Maps:"); 51 } 52 53 mFilters.add(f); 54 int numS = register_intent_filter(f, f.schemesIterator(), 55 mSchemeToFilter, " Scheme: "); 56 int numT = register_mime_types(f, " Type: "); 57 if (numS == 0 && numT == 0) { 58 register_intent_filter(f, f.actionsIterator(), 59 mActionToFilter, " Action: "); 60 } 61 if (numT != 0) { 62 register_intent_filter(f, f.actionsIterator(), 63 mTypedActionToFilter, " TypedAction: "); 64 } 65 } 66 67 public void removeFilter(F f) { 68 removeFilterInternal(f); 69 mFilters.remove(f); 70 } 71 72 void removeFilterInternal(F f) { 73 if (localLOGV) { 74 Log.v(TAG, "Removing filter: " + f); 75 f.dump(new LogPrinter(Log.VERBOSE, TAG), " "); 76 Log.v(TAG, " Cleaning Lookup Maps:"); 77 } 78 79 int numS = unregister_intent_filter(f, f.schemesIterator(), 80 mSchemeToFilter, " Scheme: "); 81 int numT = unregister_mime_types(f, " Type: "); 82 if (numS == 0 && numT == 0) { 83 unregister_intent_filter(f, f.actionsIterator(), 84 mActionToFilter, " Action: "); 85 } 86 if (numT != 0) { 87 unregister_intent_filter(f, f.actionsIterator(), 88 mTypedActionToFilter, " TypedAction: "); 89 } 90 } 91 92 void dumpMap(Printer out, String prefix, Map<String, ArrayList<F>> map) { 93 String eprefix = prefix + " "; 94 String fprefix = prefix + " "; 95 for (Map.Entry<String, ArrayList<F>> e : map.entrySet()) { 96 out.println(eprefix + e.getKey() + ":"); 97 ArrayList<F> a = e.getValue(); 98 final int N = a.size(); 99 for (int i=0; i<N; i++) { 100 dumpFilter(out, fprefix, a.get(i)); 101 } 102 } 103 } 104 105 public void dump(Printer out, String prefix) { 106 out.println(prefix + "Full MIME Types:"); 107 dumpMap(out, prefix+" ", mTypeToFilter); 108 out.println(prefix); 109 out.println(prefix + "Base MIME Types:"); 110 dumpMap(out, prefix+" ", mBaseTypeToFilter); 111 out.println(prefix); 112 out.println(prefix + "Wild MIME Types:"); 113 dumpMap(out, prefix+" ", mWildTypeToFilter); 114 out.println(prefix); 115 out.println(prefix + "Schemes:"); 116 dumpMap(out, prefix+" ", mSchemeToFilter); 117 out.println(prefix); 118 out.println(prefix + "Non-Data Actions:"); 119 dumpMap(out, prefix+" ", mActionToFilter); 120 out.println(prefix); 121 out.println(prefix + "MIME Typed Actions:"); 122 dumpMap(out, prefix+" ", mTypedActionToFilter); 123 } 124 125 private class IteratorWrapper implements Iterator<F> { 126 private final Iterator<F> mI; 127 private F mCur; 128 129 IteratorWrapper(Iterator<F> it) { 130 mI = it; 131 } 132 133 public boolean hasNext() { 134 return mI.hasNext(); 135 } 136 137 public F next() { 138 return (mCur = mI.next()); 139 } 140 141 public void remove() { 142 if (mCur != null) { 143 removeFilterInternal(mCur); 144 } 145 mI.remove(); 146 } 147 148 } 149 150 /** 151 * Returns an iterator allowing filters to be removed. 152 */ 153 public Iterator<F> filterIterator() { 154 return new IteratorWrapper(mFilters.iterator()); 155 } 156 157 /** 158 * Returns a read-only set of the filters. 159 */ 160 public Set<F> filterSet() { 161 return Collections.unmodifiableSet(mFilters); 162 } 163 164 public List<R> queryIntent(ContentResolver resolver, Intent intent, 165 String resolvedType, boolean defaultOnly) { 166 String scheme = intent.getScheme(); 167 168 ArrayList<R> finalList = new ArrayList<R>(); 169 170 final boolean debug = localLOGV || 171 ((intent.getFlags() & Intent.FLAG_DEBUG_LOG_RESOLUTION) != 0); 172 173 if (debug) Log.v( 174 TAG, "Resolving type " + resolvedType + " scheme " + scheme 175 + " of intent " + intent); 176 177 ArrayList<F> firstTypeCut = null; 178 ArrayList<F> secondTypeCut = null; 179 ArrayList<F> thirdTypeCut = null; 180 ArrayList<F> schemeCut = null; 181 182 // If the intent includes a MIME type, then we want to collect all of 183 // the filters that match that MIME type. 184 if (resolvedType != null) { 185 int slashpos = resolvedType.indexOf('/'); 186 if (slashpos > 0) { 187 final String baseType = resolvedType.substring(0, slashpos); 188 if (!baseType.equals("*")) { 189 if (resolvedType.length() != slashpos+2 190 || resolvedType.charAt(slashpos+1) != '*') { 191 // Not a wild card, so we can just look for all filters that 192 // completely match or wildcards whose base type matches. 193 firstTypeCut = mTypeToFilter.get(resolvedType); 194 if (debug) Log.v(TAG, "First type cut: " + firstTypeCut); 195 secondTypeCut = mWildTypeToFilter.get(baseType); 196 if (debug) Log.v(TAG, "Second type cut: " + secondTypeCut); 197 } else { 198 // We can match anything with our base type. 199 firstTypeCut = mBaseTypeToFilter.get(baseType); 200 if (debug) Log.v(TAG, "First type cut: " + firstTypeCut); 201 secondTypeCut = mWildTypeToFilter.get(baseType); 202 if (debug) Log.v(TAG, "Second type cut: " + secondTypeCut); 203 } 204 // Any */* types always apply, but we only need to do this 205 // if the intent type was not already */*. 206 thirdTypeCut = mWildTypeToFilter.get("*"); 207 if (debug) Log.v(TAG, "Third type cut: " + thirdTypeCut); 208 } else if (intent.getAction() != null) { 209 // The intent specified any type ({@literal *}/*). This 210 // can be a whole heck of a lot of things, so as a first 211 // cut let's use the action instead. 212 firstTypeCut = mTypedActionToFilter.get(intent.getAction()); 213 if (debug) Log.v(TAG, "Typed Action list: " + firstTypeCut); 214 } 215 } 216 } 217 218 // If the intent includes a data URI, then we want to collect all of 219 // the filters that match its scheme (we will further refine matches 220 // on the authority and path by directly matching each resulting filter). 221 if (scheme != null) { 222 schemeCut = mSchemeToFilter.get(scheme); 223 if (debug) Log.v(TAG, "Scheme list: " + schemeCut); 224 } 225 226 // If the intent does not specify any data -- either a MIME type or 227 // a URI -- then we will only be looking for matches against empty 228 // data. 229 if (resolvedType == null && scheme == null && intent.getAction() != null) { 230 firstTypeCut = mActionToFilter.get(intent.getAction()); 231 if (debug) Log.v(TAG, "Action list: " + firstTypeCut); 232 } 233 234 if (firstTypeCut != null) { 235 buildResolveList(intent, debug, defaultOnly, 236 resolvedType, scheme, firstTypeCut, finalList); 237 } 238 if (secondTypeCut != null) { 239 buildResolveList(intent, debug, defaultOnly, 240 resolvedType, scheme, secondTypeCut, finalList); 241 } 242 if (thirdTypeCut != null) { 243 buildResolveList(intent, debug, defaultOnly, 244 resolvedType, scheme, thirdTypeCut, finalList); 245 } 246 if (schemeCut != null) { 247 buildResolveList(intent, debug, defaultOnly, 248 resolvedType, scheme, schemeCut, finalList); 249 } 250 sortResults(finalList); 251 252 if (debug) { 253 Log.v(TAG, "Final result list:"); 254 for (R r : finalList) { 255 Log.v(TAG, " " + r); 256 } 257 } 258 return finalList; 259 } 260 261 /** 262 * Control whether the given filter is allowed to go into the result 263 * list. Mainly intended to prevent adding multiple filters for the 264 * same target object. 265 */ 266 protected boolean allowFilterResult(F filter, List<R> dest) { 267 return true; 268 } 269 270 protected R newResult(F filter, int match) { 271 return (R)filter; 272 } 273 274 protected void sortResults(List<R> results) { 275 Collections.sort(results, mResolvePrioritySorter); 276 } 277 278 protected void dumpFilter(Printer out, String prefix, F filter) { 279 out.println(prefix + filter); 280 } 281 282 private final int register_mime_types(F filter, String prefix) { 283 final Iterator<String> i = filter.typesIterator(); 284 if (i == null) { 285 return 0; 286 } 287 288 int num = 0; 289 while (i.hasNext()) { 290 String name = (String)i.next(); 291 num++; 292 if (localLOGV) Log.v(TAG, prefix + name); 293 String baseName = name; 294 final int slashpos = name.indexOf('/'); 295 if (slashpos > 0) { 296 baseName = name.substring(0, slashpos).intern(); 297 } else { 298 name = name + "/*"; 299 } 300 301 ArrayList<F> array = mTypeToFilter.get(name); 302 if (array == null) { 303 //Log.v(TAG, "Creating new array for " + name); 304 array = new ArrayList<F>(); 305 mTypeToFilter.put(name, array); 306 } 307 array.add(filter); 308 309 if (slashpos > 0) { 310 array = mBaseTypeToFilter.get(baseName); 311 if (array == null) { 312 //Log.v(TAG, "Creating new array for " + name); 313 array = new ArrayList<F>(); 314 mBaseTypeToFilter.put(baseName, array); 315 } 316 array.add(filter); 317 } else { 318 array = mWildTypeToFilter.get(baseName); 319 if (array == null) { 320 //Log.v(TAG, "Creating new array for " + name); 321 array = new ArrayList<F>(); 322 mWildTypeToFilter.put(baseName, array); 323 } 324 array.add(filter); 325 } 326 } 327 328 return num; 329 } 330 331 private final int unregister_mime_types(F filter, String prefix) { 332 final Iterator<String> i = filter.typesIterator(); 333 if (i == null) { 334 return 0; 335 } 336 337 int num = 0; 338 while (i.hasNext()) { 339 String name = (String)i.next(); 340 num++; 341 if (localLOGV) Log.v(TAG, prefix + name); 342 String baseName = name; 343 final int slashpos = name.indexOf('/'); 344 if (slashpos > 0) { 345 baseName = name.substring(0, slashpos).intern(); 346 } else { 347 name = name + "/*"; 348 } 349 350 if (!remove_all_objects(mTypeToFilter.get(name), filter)) { 351 mTypeToFilter.remove(name); 352 } 353 354 if (slashpos > 0) { 355 if (!remove_all_objects(mBaseTypeToFilter.get(baseName), filter)) { 356 mBaseTypeToFilter.remove(baseName); 357 } 358 } else { 359 if (!remove_all_objects(mWildTypeToFilter.get(baseName), filter)) { 360 mWildTypeToFilter.remove(baseName); 361 } 362 } 363 } 364 return num; 365 } 366 367 private final int register_intent_filter(F filter, Iterator<String> i, 368 HashMap<String, ArrayList<F>> dest, String prefix) { 369 if (i == null) { 370 return 0; 371 } 372 373 int num = 0; 374 while (i.hasNext()) { 375 String name = i.next(); 376 num++; 377 if (localLOGV) Log.v(TAG, prefix + name); 378 ArrayList<F> array = dest.get(name); 379 if (array == null) { 380 //Log.v(TAG, "Creating new array for " + name); 381 array = new ArrayList<F>(); 382 dest.put(name, array); 383 } 384 array.add(filter); 385 } 386 return num; 387 } 388 389 private final int unregister_intent_filter(F filter, Iterator<String> i, 390 HashMap<String, ArrayList<F>> dest, String prefix) { 391 if (i == null) { 392 return 0; 393 } 394 395 int num = 0; 396 while (i.hasNext()) { 397 String name = i.next(); 398 num++; 399 if (localLOGV) Log.v(TAG, prefix + name); 400 if (!remove_all_objects(dest.get(name), filter)) { 401 dest.remove(name); 402 } 403 } 404 return num; 405 } 406 407 private final boolean remove_all_objects(List<F> list, Object object) { 408 if (list != null) { 409 int N = list.size(); 410 for (int idx=0; idx<N; idx++) { 411 if (list.get(idx) == object) { 412 list.remove(idx); 413 idx--; 414 N--; 415 } 416 } 417 return N > 0; 418 } 419 return false; 420 } 421 422 private void buildResolveList(Intent intent, boolean debug, boolean defaultOnly, 423 String resolvedType, String scheme, List<F> src, List<R> dest) { 424 Set<String> categories = intent.getCategories(); 425 426 final int N = src != null ? src.size() : 0; 427 boolean hasNonDefaults = false; 428 int i; 429 for (i=0; i<N; i++) { 430 F filter = src.get(i); 431 int match; 432 if (debug) Log.v(TAG, "Matching against filter " + filter); 433 434 // Do we already have this one? 435 if (!allowFilterResult(filter, dest)) { 436 if (debug) { 437 Log.v(TAG, " Filter's target already added"); 438 } 439 continue; 440 } 441 442 match = filter.match( 443 intent.getAction(), resolvedType, scheme, intent.getData(), categories, TAG); 444 if (match >= 0) { 445 if (debug) Log.v(TAG, " Filter matched! match=0x" + 446 Integer.toHexString(match)); 447 if (!defaultOnly || filter.hasCategory(Intent.CATEGORY_DEFAULT)) { 448 final R oneResult = newResult(filter, match); 449 if (oneResult != null) { 450 dest.add(oneResult); 451 } 452 } else { 453 hasNonDefaults = true; 454 } 455 } else { 456 if (debug) { 457 String reason; 458 switch (match) { 459 case IntentFilter.NO_MATCH_ACTION: reason = "action"; break; 460 case IntentFilter.NO_MATCH_CATEGORY: reason = "category"; break; 461 case IntentFilter.NO_MATCH_DATA: reason = "data"; break; 462 case IntentFilter.NO_MATCH_TYPE: reason = "type"; break; 463 default: reason = "unknown reason"; break; 464 } 465 Log.v(TAG, " Filter did not match: " + reason); 466 } 467 } 468 } 469 470 if (dest.size() == 0 && hasNonDefaults) { 471 Log.w(TAG, "resolveIntent failed: found match, but none with Intent.CATEGORY_DEFAULT"); 472 } 473 } 474 475 // Sorts a List of IntentFilter objects into descending priority order. 476 private static final Comparator mResolvePrioritySorter = new Comparator() { 477 public int compare(Object o1, Object o2) { 478 float q1 = ((IntentFilter)o1).getPriority(); 479 float q2 = ((IntentFilter)o2).getPriority(); 480 return (q1 > q2) ? -1 : ((q1 < q2) ? 1 : 0); 481 } 482 }; 483 484 /** 485 * All filters that have been registered. 486 */ 487 private final HashSet<F> mFilters = new HashSet<F>(); 488 489 /** 490 * All of the MIME types that have been registered, such as "image/jpeg", 491 * "image/*", or "{@literal *}/*". 492 */ 493 private final HashMap<String, ArrayList<F>> mTypeToFilter 494 = new HashMap<String, ArrayList<F>>(); 495 496 /** 497 * The base names of all of all fully qualified MIME types that have been 498 * registered, such as "image" or "*". Wild card MIME types such as 499 * "image/*" will not be here. 500 */ 501 private final HashMap<String, ArrayList<F>> mBaseTypeToFilter 502 = new HashMap<String, ArrayList<F>>(); 503 504 /** 505 * The base names of all of the MIME types with a sub-type wildcard that 506 * have been registered. For example, a filter with "image/*" will be 507 * included here as "image" but one with "image/jpeg" will not be 508 * included here. This also includes the "*" for the "{@literal *}/*" 509 * MIME type. 510 */ 511 private final HashMap<String, ArrayList<F>> mWildTypeToFilter 512 = new HashMap<String, ArrayList<F>>(); 513 514 /** 515 * All of the URI schemes (such as http) that have been registered. 516 */ 517 private final HashMap<String, ArrayList<F>> mSchemeToFilter 518 = new HashMap<String, ArrayList<F>>(); 519 520 /** 521 * All of the actions that have been registered, but only those that did 522 * not specify data. 523 */ 524 private final HashMap<String, ArrayList<F>> mActionToFilter 525 = new HashMap<String, ArrayList<F>>(); 526 527 /** 528 * All of the actions that have been registered and specified a MIME type. 529 */ 530 private final HashMap<String, ArrayList<F>> mTypedActionToFilter 531 = new HashMap<String, ArrayList<F>>(); 532} 533 534