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