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 addFilter(F f)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 removeFilter(F f)70 public void removeFilter(F f) { 71 removeFilterInternal(f); 72 mFilters.remove(f); 73 } 74 removeFilterInternal(F f)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 dumpMap(PrintWriter out, String titlePrefix, String title, String prefix, Map<String, ArrayList<F>> map, String packageName, boolean printFilter)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 dump(PrintWriter out, String title, String prefix, String packageName, boolean printFilter)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 IteratorWrapper(Iterator<F> it)168 IteratorWrapper(Iterator<F> it) { 169 mI = it; 170 } 171 hasNext()172 public boolean hasNext() { 173 return mI.hasNext(); 174 } 175 next()176 public F next() { 177 return (mCur = mI.next()); 178 } 179 remove()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 */ filterIterator()192 public Iterator<F> filterIterator() { 193 return new IteratorWrapper(mFilters.iterator()); 194 } 195 196 /** 197 * Returns a read-only set of the filters. 198 */ filterSet()199 public Set<F> filterSet() { 200 return Collections.unmodifiableSet(mFilters); 201 } 202 queryIntentFromList(Intent intent, String resolvedType, boolean defaultOnly, ArrayList<ArrayList<F>> listCut, int userId)203 public List<R> queryIntentFromList(Intent intent, String resolvedType, 204 boolean defaultOnly, ArrayList<ArrayList<F>> listCut, int userId) { 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, userId); 216 } 217 sortResults(resultList); 218 return resultList; 219 } 220 queryIntent(Intent intent, String resolvedType, boolean defaultOnly, int userId)221 public List<R> queryIntent(Intent intent, String resolvedType, boolean defaultOnly, 222 int userId) { 223 String scheme = intent.getScheme(); 224 225 ArrayList<R> finalList = new ArrayList<R>(); 226 227 final boolean debug = localLOGV || 228 ((intent.getFlags() & Intent.FLAG_DEBUG_LOG_RESOLUTION) != 0); 229 230 if (debug) Slog.v( 231 TAG, "Resolving type " + resolvedType + " scheme " + scheme 232 + " of intent " + intent); 233 234 ArrayList<F> firstTypeCut = null; 235 ArrayList<F> secondTypeCut = null; 236 ArrayList<F> thirdTypeCut = null; 237 ArrayList<F> schemeCut = null; 238 239 // If the intent includes a MIME type, then we want to collect all of 240 // the filters that match that MIME type. 241 if (resolvedType != null) { 242 int slashpos = resolvedType.indexOf('/'); 243 if (slashpos > 0) { 244 final String baseType = resolvedType.substring(0, slashpos); 245 if (!baseType.equals("*")) { 246 if (resolvedType.length() != slashpos+2 247 || resolvedType.charAt(slashpos+1) != '*') { 248 // Not a wild card, so we can just look for all filters that 249 // completely match or wildcards whose base type matches. 250 firstTypeCut = mTypeToFilter.get(resolvedType); 251 if (debug) Slog.v(TAG, "First type cut: " + firstTypeCut); 252 secondTypeCut = mWildTypeToFilter.get(baseType); 253 if (debug) Slog.v(TAG, "Second type cut: " + secondTypeCut); 254 } else { 255 // We can match anything with our base type. 256 firstTypeCut = mBaseTypeToFilter.get(baseType); 257 if (debug) Slog.v(TAG, "First type cut: " + firstTypeCut); 258 secondTypeCut = mWildTypeToFilter.get(baseType); 259 if (debug) Slog.v(TAG, "Second type cut: " + secondTypeCut); 260 } 261 // Any */* types always apply, but we only need to do this 262 // if the intent type was not already */*. 263 thirdTypeCut = mWildTypeToFilter.get("*"); 264 if (debug) Slog.v(TAG, "Third type cut: " + thirdTypeCut); 265 } else if (intent.getAction() != null) { 266 // The intent specified any type ({@literal *}/*). This 267 // can be a whole heck of a lot of things, so as a first 268 // cut let's use the action instead. 269 firstTypeCut = mTypedActionToFilter.get(intent.getAction()); 270 if (debug) Slog.v(TAG, "Typed Action list: " + firstTypeCut); 271 } 272 } 273 } 274 275 // If the intent includes a data URI, then we want to collect all of 276 // the filters that match its scheme (we will further refine matches 277 // on the authority and path by directly matching each resulting filter). 278 if (scheme != null) { 279 schemeCut = mSchemeToFilter.get(scheme); 280 if (debug) Slog.v(TAG, "Scheme list: " + schemeCut); 281 } 282 283 // If the intent does not specify any data -- either a MIME type or 284 // a URI -- then we will only be looking for matches against empty 285 // data. 286 if (resolvedType == null && scheme == null && intent.getAction() != null) { 287 firstTypeCut = mActionToFilter.get(intent.getAction()); 288 if (debug) Slog.v(TAG, "Action list: " + firstTypeCut); 289 } 290 291 FastImmutableArraySet<String> categories = getFastIntentCategories(intent); 292 if (firstTypeCut != null) { 293 buildResolveList(intent, categories, debug, defaultOnly, 294 resolvedType, scheme, firstTypeCut, finalList, userId); 295 } 296 if (secondTypeCut != null) { 297 buildResolveList(intent, categories, debug, defaultOnly, 298 resolvedType, scheme, secondTypeCut, finalList, userId); 299 } 300 if (thirdTypeCut != null) { 301 buildResolveList(intent, categories, debug, defaultOnly, 302 resolvedType, scheme, thirdTypeCut, finalList, userId); 303 } 304 if (schemeCut != null) { 305 buildResolveList(intent, categories, debug, defaultOnly, 306 resolvedType, scheme, schemeCut, finalList, userId); 307 } 308 sortResults(finalList); 309 310 if (debug) { 311 Slog.v(TAG, "Final result list:"); 312 for (R r : finalList) { 313 Slog.v(TAG, " " + r); 314 } 315 } 316 return finalList; 317 } 318 319 /** 320 * Control whether the given filter is allowed to go into the result 321 * list. Mainly intended to prevent adding multiple filters for the 322 * same target object. 323 */ allowFilterResult(F filter, List<R> dest)324 protected boolean allowFilterResult(F filter, List<R> dest) { 325 return true; 326 } 327 328 /** 329 * Returns whether the object associated with the given filter is 330 * "stopped," that is whether it should not be included in the result 331 * if the intent requests to excluded stopped objects. 332 */ isFilterStopped(F filter, int userId)333 protected boolean isFilterStopped(F filter, int userId) { 334 return false; 335 } 336 337 /** 338 * Return the package that owns this filter. This must be implemented to 339 * provide correct filtering of Intents that have specified a package name 340 * they are to be delivered to. 341 */ packageForFilter(F filter)342 protected abstract String packageForFilter(F filter); 343 344 @SuppressWarnings("unchecked") newResult(F filter, int match, int userId)345 protected R newResult(F filter, int match, int userId) { 346 return (R)filter; 347 } 348 349 @SuppressWarnings("unchecked") sortResults(List<R> results)350 protected void sortResults(List<R> results) { 351 Collections.sort(results, mResolvePrioritySorter); 352 } 353 dumpFilter(PrintWriter out, String prefix, F filter)354 protected void dumpFilter(PrintWriter out, String prefix, F filter) { 355 out.print(prefix); out.println(filter); 356 } 357 register_mime_types(F filter, String prefix)358 private final int register_mime_types(F filter, String prefix) { 359 final Iterator<String> i = filter.typesIterator(); 360 if (i == null) { 361 return 0; 362 } 363 364 int num = 0; 365 while (i.hasNext()) { 366 String name = i.next(); 367 num++; 368 if (localLOGV) Slog.v(TAG, prefix + name); 369 String baseName = name; 370 final int slashpos = name.indexOf('/'); 371 if (slashpos > 0) { 372 baseName = name.substring(0, slashpos).intern(); 373 } else { 374 name = name + "/*"; 375 } 376 377 ArrayList<F> array = mTypeToFilter.get(name); 378 if (array == null) { 379 //Slog.v(TAG, "Creating new array for " + name); 380 array = new ArrayList<F>(); 381 mTypeToFilter.put(name, array); 382 } 383 array.add(filter); 384 385 if (slashpos > 0) { 386 array = mBaseTypeToFilter.get(baseName); 387 if (array == null) { 388 //Slog.v(TAG, "Creating new array for " + name); 389 array = new ArrayList<F>(); 390 mBaseTypeToFilter.put(baseName, array); 391 } 392 array.add(filter); 393 } else { 394 array = mWildTypeToFilter.get(baseName); 395 if (array == null) { 396 //Slog.v(TAG, "Creating new array for " + name); 397 array = new ArrayList<F>(); 398 mWildTypeToFilter.put(baseName, array); 399 } 400 array.add(filter); 401 } 402 } 403 404 return num; 405 } 406 unregister_mime_types(F filter, String prefix)407 private final int unregister_mime_types(F filter, String prefix) { 408 final Iterator<String> i = filter.typesIterator(); 409 if (i == null) { 410 return 0; 411 } 412 413 int num = 0; 414 while (i.hasNext()) { 415 String name = i.next(); 416 num++; 417 if (localLOGV) Slog.v(TAG, prefix + name); 418 String baseName = name; 419 final int slashpos = name.indexOf('/'); 420 if (slashpos > 0) { 421 baseName = name.substring(0, slashpos).intern(); 422 } else { 423 name = name + "/*"; 424 } 425 426 if (!remove_all_objects(mTypeToFilter.get(name), filter)) { 427 mTypeToFilter.remove(name); 428 } 429 430 if (slashpos > 0) { 431 if (!remove_all_objects(mBaseTypeToFilter.get(baseName), filter)) { 432 mBaseTypeToFilter.remove(baseName); 433 } 434 } else { 435 if (!remove_all_objects(mWildTypeToFilter.get(baseName), filter)) { 436 mWildTypeToFilter.remove(baseName); 437 } 438 } 439 } 440 return num; 441 } 442 register_intent_filter(F filter, Iterator<String> i, HashMap<String, ArrayList<F>> dest, String prefix)443 private final int register_intent_filter(F filter, Iterator<String> i, 444 HashMap<String, ArrayList<F>> dest, String prefix) { 445 if (i == null) { 446 return 0; 447 } 448 449 int num = 0; 450 while (i.hasNext()) { 451 String name = i.next(); 452 num++; 453 if (localLOGV) Slog.v(TAG, prefix + name); 454 ArrayList<F> array = dest.get(name); 455 if (array == null) { 456 //Slog.v(TAG, "Creating new array for " + name); 457 array = new ArrayList<F>(); 458 dest.put(name, array); 459 } 460 array.add(filter); 461 } 462 return num; 463 } 464 unregister_intent_filter(F filter, Iterator<String> i, HashMap<String, ArrayList<F>> dest, String prefix)465 private final int unregister_intent_filter(F filter, Iterator<String> i, 466 HashMap<String, ArrayList<F>> dest, String prefix) { 467 if (i == null) { 468 return 0; 469 } 470 471 int num = 0; 472 while (i.hasNext()) { 473 String name = i.next(); 474 num++; 475 if (localLOGV) Slog.v(TAG, prefix + name); 476 if (!remove_all_objects(dest.get(name), filter)) { 477 dest.remove(name); 478 } 479 } 480 return num; 481 } 482 remove_all_objects(List<F> list, Object object)483 private final boolean remove_all_objects(List<F> list, Object object) { 484 if (list != null) { 485 int N = list.size(); 486 for (int idx=0; idx<N; idx++) { 487 if (list.get(idx) == object) { 488 list.remove(idx); 489 idx--; 490 N--; 491 } 492 } 493 return N > 0; 494 } 495 return false; 496 } 497 getFastIntentCategories(Intent intent)498 private static FastImmutableArraySet<String> getFastIntentCategories(Intent intent) { 499 final Set<String> categories = intent.getCategories(); 500 if (categories == null) { 501 return null; 502 } 503 return new FastImmutableArraySet<String>(categories.toArray(new String[categories.size()])); 504 } 505 buildResolveList(Intent intent, FastImmutableArraySet<String> categories, boolean debug, boolean defaultOnly, String resolvedType, String scheme, List<F> src, List<R> dest, int userId)506 private void buildResolveList(Intent intent, FastImmutableArraySet<String> categories, 507 boolean debug, boolean defaultOnly, 508 String resolvedType, String scheme, List<F> src, List<R> dest, int userId) { 509 final String action = intent.getAction(); 510 final Uri data = intent.getData(); 511 final String packageName = intent.getPackage(); 512 513 final boolean excludingStopped = intent.isExcludingStopped(); 514 515 final int N = src != null ? src.size() : 0; 516 boolean hasNonDefaults = false; 517 int i; 518 for (i=0; i<N; i++) { 519 F filter = src.get(i); 520 int match; 521 if (debug) Slog.v(TAG, "Matching against filter " + filter); 522 523 if (excludingStopped && isFilterStopped(filter, userId)) { 524 if (debug) { 525 Slog.v(TAG, " Filter's target is stopped; skipping"); 526 } 527 continue; 528 } 529 530 // Is delivery being limited to filters owned by a particular package? 531 if (packageName != null && !packageName.equals(packageForFilter(filter))) { 532 if (debug) { 533 Slog.v(TAG, " Filter is not from package " + packageName + "; skipping"); 534 } 535 continue; 536 } 537 538 // Do we already have this one? 539 if (!allowFilterResult(filter, dest)) { 540 if (debug) { 541 Slog.v(TAG, " Filter's target already added"); 542 } 543 continue; 544 } 545 546 match = filter.match(action, resolvedType, scheme, data, categories, TAG); 547 if (match >= 0) { 548 if (debug) Slog.v(TAG, " Filter matched! match=0x" + 549 Integer.toHexString(match)); 550 if (!defaultOnly || filter.hasCategory(Intent.CATEGORY_DEFAULT)) { 551 final R oneResult = newResult(filter, match, userId); 552 if (oneResult != null) { 553 dest.add(oneResult); 554 } 555 } else { 556 hasNonDefaults = true; 557 } 558 } else { 559 if (debug) { 560 String reason; 561 switch (match) { 562 case IntentFilter.NO_MATCH_ACTION: reason = "action"; break; 563 case IntentFilter.NO_MATCH_CATEGORY: reason = "category"; break; 564 case IntentFilter.NO_MATCH_DATA: reason = "data"; break; 565 case IntentFilter.NO_MATCH_TYPE: reason = "type"; break; 566 default: reason = "unknown reason"; break; 567 } 568 Slog.v(TAG, " Filter did not match: " + reason); 569 } 570 } 571 } 572 573 if (dest.size() == 0 && hasNonDefaults) { 574 Slog.w(TAG, "resolveIntent failed: found match, but none with Intent.CATEGORY_DEFAULT"); 575 } 576 } 577 578 // Sorts a List of IntentFilter objects into descending priority order. 579 @SuppressWarnings("rawtypes") 580 private static final Comparator mResolvePrioritySorter = new Comparator() { 581 public int compare(Object o1, Object o2) { 582 final int q1 = ((IntentFilter) o1).getPriority(); 583 final int q2 = ((IntentFilter) o2).getPriority(); 584 return (q1 > q2) ? -1 : ((q1 < q2) ? 1 : 0); 585 } 586 }; 587 588 /** 589 * All filters that have been registered. 590 */ 591 private final HashSet<F> mFilters = new HashSet<F>(); 592 593 /** 594 * All of the MIME types that have been registered, such as "image/jpeg", 595 * "image/*", or "{@literal *}/*". 596 */ 597 private final HashMap<String, ArrayList<F>> mTypeToFilter 598 = new HashMap<String, ArrayList<F>>(); 599 600 /** 601 * The base names of all of all fully qualified MIME types that have been 602 * registered, such as "image" or "*". Wild card MIME types such as 603 * "image/*" will not be here. 604 */ 605 private final HashMap<String, ArrayList<F>> mBaseTypeToFilter 606 = new HashMap<String, ArrayList<F>>(); 607 608 /** 609 * The base names of all of the MIME types with a sub-type wildcard that 610 * have been registered. For example, a filter with "image/*" will be 611 * included here as "image" but one with "image/jpeg" will not be 612 * included here. This also includes the "*" for the "{@literal *}/*" 613 * MIME type. 614 */ 615 private final HashMap<String, ArrayList<F>> mWildTypeToFilter 616 = new HashMap<String, ArrayList<F>>(); 617 618 /** 619 * All of the URI schemes (such as http) that have been registered. 620 */ 621 private final HashMap<String, ArrayList<F>> mSchemeToFilter 622 = new HashMap<String, ArrayList<F>>(); 623 624 /** 625 * All of the actions that have been registered, but only those that did 626 * not specify data. 627 */ 628 private final HashMap<String, ArrayList<F>> mActionToFilter 629 = new HashMap<String, ArrayList<F>>(); 630 631 /** 632 * All of the actions that have been registered and specified a MIME type. 633 */ 634 private final HashMap<String, ArrayList<F>> mTypedActionToFilter 635 = new HashMap<String, ArrayList<F>>(); 636 } 637 638