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