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