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