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