ResolverActivity.java revision 589e6f960db7f3c208a218b7d035f01d0c8460ab
1/* 2 * Copyright (C) 2008 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.internal.app; 18 19import com.android.internal.R; 20import com.android.internal.content.PackageMonitor; 21 22import android.app.ActivityManager; 23import android.content.ComponentName; 24import android.content.Context; 25import android.content.Intent; 26import android.content.IntentFilter; 27import android.content.pm.ActivityInfo; 28import android.content.pm.LabeledIntent; 29import android.content.pm.PackageManager; 30import android.content.pm.PackageManager.NameNotFoundException; 31import android.content.pm.ResolveInfo; 32import android.content.res.Resources; 33import android.graphics.drawable.Drawable; 34import android.net.Uri; 35import android.os.Bundle; 36import android.os.PatternMatcher; 37import android.util.Log; 38import android.view.LayoutInflater; 39import android.view.View; 40import android.view.ViewGroup; 41import android.widget.AdapterView; 42import android.widget.BaseAdapter; 43import android.widget.Button; 44import android.widget.GridView; 45import android.widget.ImageView; 46import android.widget.ListView; 47import android.widget.TextView; 48 49import java.util.ArrayList; 50import java.util.Collections; 51import java.util.HashSet; 52import java.util.Iterator; 53import java.util.List; 54import java.util.Set; 55 56/** 57 * This activity is displayed when the system attempts to start an Intent for 58 * which there is more than one matching activity, allowing the user to decide 59 * which to go to. It is not normally used directly by application developers. 60 */ 61public class ResolverActivity extends AlertActivity implements AdapterView.OnItemClickListener { 62 private static final String TAG = "ResolverActivity"; 63 64 private ResolveListAdapter mAdapter; 65 private PackageManager mPm; 66 private boolean mAlwaysUseOption; 67 private boolean mShowExtended; 68 private GridView mGrid; 69 private Button mAlwaysButton; 70 private Button mOnceButton; 71 private int mIconDpi; 72 private int mIconSize; 73 private int mMaxColumns; 74 75 private boolean mRegistered; 76 private final PackageMonitor mPackageMonitor = new PackageMonitor() { 77 @Override public void onSomePackagesChanged() { 78 mAdapter.handlePackagesChanged(); 79 } 80 }; 81 82 private Intent makeMyIntent() { 83 Intent intent = new Intent(getIntent()); 84 // The resolver activity is set to be hidden from recent tasks. 85 // we don't want this attribute to be propagated to the next activity 86 // being launched. Note that if the original Intent also had this 87 // flag set, we are now losing it. That should be a very rare case 88 // and we can live with this. 89 intent.setFlags(intent.getFlags()&~Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); 90 return intent; 91 } 92 93 @Override 94 protected void onCreate(Bundle savedInstanceState) { 95 onCreate(savedInstanceState, makeMyIntent(), 96 getResources().getText(com.android.internal.R.string.whichApplication), 97 null, null, true); 98 } 99 100 protected void onCreate(Bundle savedInstanceState, Intent intent, 101 CharSequence title, Intent[] initialIntents, List<ResolveInfo> rList, 102 boolean alwaysUseOption) { 103 setTheme(R.style.Theme_DeviceDefault_Light_Dialog_Alert); 104 super.onCreate(savedInstanceState); 105 mPm = getPackageManager(); 106 mAlwaysUseOption = alwaysUseOption; 107 mMaxColumns = getResources().getInteger(R.integer.config_maxResolverActivityColumns); 108 intent.setComponent(null); 109 110 AlertController.AlertParams ap = mAlertParams; 111 112 ap.mTitle = title; 113 114 mPackageMonitor.register(this, getMainLooper(), false); 115 mRegistered = true; 116 117 final ActivityManager am = (ActivityManager) getSystemService(ACTIVITY_SERVICE); 118 mIconDpi = am.getLauncherLargeIconDensity(); 119 mIconSize = am.getLauncherLargeIconSize(); 120 121 mAdapter = new ResolveListAdapter(this, intent, initialIntents, rList); 122 int count = mAdapter.getCount(); 123 if (count > 1) { 124 ap.mView = getLayoutInflater().inflate(R.layout.resolver_grid, null); 125 mGrid = (GridView) ap.mView.findViewById(R.id.resolver_grid); 126 mGrid.setAdapter(mAdapter); 127 mGrid.setOnItemClickListener(this); 128 mGrid.setOnItemLongClickListener(new ItemLongClickListener()); 129 130 if (alwaysUseOption) { 131 mGrid.setChoiceMode(ListView.CHOICE_MODE_SINGLE); 132 } 133 134 resizeGrid(); 135 } else if (count == 1) { 136 startActivity(mAdapter.intentForPosition(0)); 137 mPackageMonitor.unregister(); 138 mRegistered = false; 139 finish(); 140 return; 141 } else { 142 ap.mMessage = getResources().getText(R.string.noApplications); 143 } 144 145 setupAlert(); 146 147 if (alwaysUseOption) { 148 final ViewGroup buttonLayout = (ViewGroup) findViewById(R.id.button_bar); 149 buttonLayout.setVisibility(View.VISIBLE); 150 mAlwaysButton = (Button) buttonLayout.findViewById(R.id.button_always); 151 mOnceButton = (Button) buttonLayout.findViewById(R.id.button_once); 152 } 153 } 154 155 void resizeGrid() { 156 final int itemCount = mAdapter.getCount(); 157 mGrid.setNumColumns(Math.min(itemCount, mMaxColumns)); 158 } 159 160 Drawable getIcon(Resources res, int resId) { 161 Drawable result; 162 try { 163 result = res.getDrawableForDensity(resId, mIconDpi); 164 } catch (Resources.NotFoundException e) { 165 result = null; 166 } 167 168 return result; 169 } 170 171 Drawable loadIconForResolveInfo(ResolveInfo ri) { 172 Drawable dr; 173 try { 174 if (ri.resolvePackageName != null && ri.icon != 0) { 175 dr = getIcon(mPm.getResourcesForApplication(ri.resolvePackageName), ri.icon); 176 if (dr != null) { 177 return dr; 178 } 179 } 180 final int iconRes = ri.getIconResource(); 181 if (iconRes != 0) { 182 dr = getIcon(mPm.getResourcesForApplication(ri.activityInfo.packageName), iconRes); 183 if (dr != null) { 184 return dr; 185 } 186 } 187 } catch (NameNotFoundException e) { 188 Log.e(TAG, "Couldn't find resources for package", e); 189 } 190 return ri.loadIcon(mPm); 191 } 192 193 @Override 194 protected void onRestart() { 195 super.onRestart(); 196 if (!mRegistered) { 197 mPackageMonitor.register(this, getMainLooper(), false); 198 mRegistered = true; 199 } 200 mAdapter.handlePackagesChanged(); 201 } 202 203 @Override 204 protected void onStop() { 205 super.onStop(); 206 if (mRegistered) { 207 mPackageMonitor.unregister(); 208 mRegistered = false; 209 } 210 } 211 212 @Override 213 public void onItemClick(AdapterView<?> parent, View view, int position, long id) { 214 if (mAlwaysUseOption) { 215 final int checkedPos = mGrid.getCheckedItemPosition(); 216 final boolean enabled = checkedPos != GridView.INVALID_POSITION; 217 mAlwaysButton.setEnabled(enabled); 218 mOnceButton.setEnabled(enabled); 219 } else { 220 startSelected(position, false); 221 } 222 } 223 224 public void onButtonClick(View v) { 225 final int id = v.getId(); 226 startSelected(mGrid.getCheckedItemPosition(), id == R.id.button_always); 227 dismiss(); 228 } 229 230 void startSelected(int which, boolean always) { 231 ResolveInfo ri = mAdapter.resolveInfoForPosition(which); 232 Intent intent = mAdapter.intentForPosition(which); 233 onIntentSelected(ri, intent, always); 234 finish(); 235 } 236 237 protected void onIntentSelected(ResolveInfo ri, Intent intent, boolean alwaysCheck) { 238 if (alwaysCheck) { 239 // Build a reasonable intent filter, based on what matched. 240 IntentFilter filter = new IntentFilter(); 241 242 if (intent.getAction() != null) { 243 filter.addAction(intent.getAction()); 244 } 245 Set<String> categories = intent.getCategories(); 246 if (categories != null) { 247 for (String cat : categories) { 248 filter.addCategory(cat); 249 } 250 } 251 filter.addCategory(Intent.CATEGORY_DEFAULT); 252 253 int cat = ri.match&IntentFilter.MATCH_CATEGORY_MASK; 254 Uri data = intent.getData(); 255 if (cat == IntentFilter.MATCH_CATEGORY_TYPE) { 256 String mimeType = intent.resolveType(this); 257 if (mimeType != null) { 258 try { 259 filter.addDataType(mimeType); 260 } catch (IntentFilter.MalformedMimeTypeException e) { 261 Log.w("ResolverActivity", e); 262 filter = null; 263 } 264 } 265 } 266 if (data != null && data.getScheme() != null) { 267 // We need the data specification if there was no type, 268 // OR if the scheme is not one of our magical "file:" 269 // or "content:" schemes (see IntentFilter for the reason). 270 if (cat != IntentFilter.MATCH_CATEGORY_TYPE 271 || (!"file".equals(data.getScheme()) 272 && !"content".equals(data.getScheme()))) { 273 filter.addDataScheme(data.getScheme()); 274 275 // Look through the resolved filter to determine which part 276 // of it matched the original Intent. 277 Iterator<IntentFilter.AuthorityEntry> aIt = ri.filter.authoritiesIterator(); 278 if (aIt != null) { 279 while (aIt.hasNext()) { 280 IntentFilter.AuthorityEntry a = aIt.next(); 281 if (a.match(data) >= 0) { 282 int port = a.getPort(); 283 filter.addDataAuthority(a.getHost(), 284 port >= 0 ? Integer.toString(port) : null); 285 break; 286 } 287 } 288 } 289 Iterator<PatternMatcher> pIt = ri.filter.pathsIterator(); 290 if (pIt != null) { 291 String path = data.getPath(); 292 while (path != null && pIt.hasNext()) { 293 PatternMatcher p = pIt.next(); 294 if (p.match(path)) { 295 filter.addDataPath(p.getPath(), p.getType()); 296 break; 297 } 298 } 299 } 300 } 301 } 302 303 if (filter != null) { 304 final int N = mAdapter.mList.size(); 305 ComponentName[] set = new ComponentName[N]; 306 int bestMatch = 0; 307 for (int i=0; i<N; i++) { 308 ResolveInfo r = mAdapter.mList.get(i).ri; 309 set[i] = new ComponentName(r.activityInfo.packageName, 310 r.activityInfo.name); 311 if (r.match > bestMatch) bestMatch = r.match; 312 } 313 getPackageManager().addPreferredActivity(filter, bestMatch, set, 314 intent.getComponent()); 315 } 316 } 317 318 if (intent != null) { 319 startActivity(intent); 320 } 321 } 322 323 void showAppDetails(ResolveInfo ri) { 324 Intent in = new Intent().setAction("android.settings.APPLICATION_DETAILS_SETTINGS") 325 .setData(Uri.fromParts("package", ri.activityInfo.packageName, null)); 326 startActivity(in); 327 } 328 329 private final class DisplayResolveInfo { 330 ResolveInfo ri; 331 CharSequence displayLabel; 332 Drawable displayIcon; 333 CharSequence extendedInfo; 334 Intent origIntent; 335 336 DisplayResolveInfo(ResolveInfo pri, CharSequence pLabel, 337 CharSequence pInfo, Intent pOrigIntent) { 338 ri = pri; 339 displayLabel = pLabel; 340 extendedInfo = pInfo; 341 origIntent = pOrigIntent; 342 } 343 } 344 345 private final class ResolveListAdapter extends BaseAdapter { 346 private final Intent[] mInitialIntents; 347 private final List<ResolveInfo> mBaseResolveList; 348 private final Intent mIntent; 349 private final LayoutInflater mInflater; 350 351 private List<ResolveInfo> mCurrentResolveList; 352 private List<DisplayResolveInfo> mList; 353 354 public ResolveListAdapter(Context context, Intent intent, 355 Intent[] initialIntents, List<ResolveInfo> rList) { 356 mIntent = new Intent(intent); 357 mIntent.setComponent(null); 358 mInitialIntents = initialIntents; 359 mBaseResolveList = rList; 360 mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 361 rebuildList(); 362 } 363 364 public void handlePackagesChanged() { 365 final int oldItemCount = getCount(); 366 rebuildList(); 367 notifyDataSetChanged(); 368 if (mList.size() <= 0) { 369 // We no longer have any items... just finish the activity. 370 finish(); 371 } 372 373 final int newItemCount = getCount(); 374 if (newItemCount != oldItemCount) { 375 resizeGrid(); 376 } 377 } 378 379 private void rebuildList() { 380 if (mBaseResolveList != null) { 381 mCurrentResolveList = mBaseResolveList; 382 } else { 383 mCurrentResolveList = mPm.queryIntentActivities( 384 mIntent, PackageManager.MATCH_DEFAULT_ONLY 385 | (mAlwaysUseOption ? PackageManager.GET_RESOLVED_FILTER : 0)); 386 } 387 int N; 388 if ((mCurrentResolveList != null) && ((N = mCurrentResolveList.size()) > 0)) { 389 // Only display the first matches that are either of equal 390 // priority or have asked to be default options. 391 ResolveInfo r0 = mCurrentResolveList.get(0); 392 for (int i=1; i<N; i++) { 393 ResolveInfo ri = mCurrentResolveList.get(i); 394 if (false) Log.v( 395 "ResolveListActivity", 396 r0.activityInfo.name + "=" + 397 r0.priority + "/" + r0.isDefault + " vs " + 398 ri.activityInfo.name + "=" + 399 ri.priority + "/" + ri.isDefault); 400 if (r0.priority != ri.priority || 401 r0.isDefault != ri.isDefault) { 402 while (i < N) { 403 mCurrentResolveList.remove(i); 404 N--; 405 } 406 } 407 } 408 if (N > 1) { 409 ResolveInfo.DisplayNameComparator rComparator = 410 new ResolveInfo.DisplayNameComparator(mPm); 411 Collections.sort(mCurrentResolveList, rComparator); 412 } 413 414 mList = new ArrayList<DisplayResolveInfo>(); 415 416 // First put the initial items at the top. 417 if (mInitialIntents != null) { 418 for (int i=0; i<mInitialIntents.length; i++) { 419 Intent ii = mInitialIntents[i]; 420 if (ii == null) { 421 continue; 422 } 423 ActivityInfo ai = ii.resolveActivityInfo( 424 getPackageManager(), 0); 425 if (ai == null) { 426 Log.w("ResolverActivity", "No activity found for " 427 + ii); 428 continue; 429 } 430 ResolveInfo ri = new ResolveInfo(); 431 ri.activityInfo = ai; 432 if (ii instanceof LabeledIntent) { 433 LabeledIntent li = (LabeledIntent)ii; 434 ri.resolvePackageName = li.getSourcePackage(); 435 ri.labelRes = li.getLabelResource(); 436 ri.nonLocalizedLabel = li.getNonLocalizedLabel(); 437 ri.icon = li.getIconResource(); 438 } 439 mList.add(new DisplayResolveInfo(ri, 440 ri.loadLabel(getPackageManager()), null, ii)); 441 } 442 } 443 444 // Check for applications with same name and use application name or 445 // package name if necessary 446 r0 = mCurrentResolveList.get(0); 447 int start = 0; 448 CharSequence r0Label = r0.loadLabel(mPm); 449 mShowExtended = false; 450 for (int i = 1; i < N; i++) { 451 if (r0Label == null) { 452 r0Label = r0.activityInfo.packageName; 453 } 454 ResolveInfo ri = mCurrentResolveList.get(i); 455 CharSequence riLabel = ri.loadLabel(mPm); 456 if (riLabel == null) { 457 riLabel = ri.activityInfo.packageName; 458 } 459 if (riLabel.equals(r0Label)) { 460 continue; 461 } 462 processGroup(mCurrentResolveList, start, (i-1), r0, r0Label); 463 r0 = ri; 464 r0Label = riLabel; 465 start = i; 466 } 467 // Process last group 468 processGroup(mCurrentResolveList, start, (N-1), r0, r0Label); 469 } 470 } 471 472 private void processGroup(List<ResolveInfo> rList, int start, int end, ResolveInfo ro, 473 CharSequence roLabel) { 474 // Process labels from start to i 475 int num = end - start+1; 476 if (num == 1) { 477 // No duplicate labels. Use label for entry at start 478 mList.add(new DisplayResolveInfo(ro, roLabel, null, null)); 479 } else { 480 mShowExtended = true; 481 boolean usePkg = false; 482 CharSequence startApp = ro.activityInfo.applicationInfo.loadLabel(mPm); 483 if (startApp == null) { 484 usePkg = true; 485 } 486 if (!usePkg) { 487 // Use HashSet to track duplicates 488 HashSet<CharSequence> duplicates = 489 new HashSet<CharSequence>(); 490 duplicates.add(startApp); 491 for (int j = start+1; j <= end ; j++) { 492 ResolveInfo jRi = rList.get(j); 493 CharSequence jApp = jRi.activityInfo.applicationInfo.loadLabel(mPm); 494 if ( (jApp == null) || (duplicates.contains(jApp))) { 495 usePkg = true; 496 break; 497 } else { 498 duplicates.add(jApp); 499 } 500 } 501 // Clear HashSet for later use 502 duplicates.clear(); 503 } 504 for (int k = start; k <= end; k++) { 505 ResolveInfo add = rList.get(k); 506 if (usePkg) { 507 // Use application name for all entries from start to end-1 508 mList.add(new DisplayResolveInfo(add, roLabel, 509 add.activityInfo.packageName, null)); 510 } else { 511 // Use package name for all entries from start to end-1 512 mList.add(new DisplayResolveInfo(add, roLabel, 513 add.activityInfo.applicationInfo.loadLabel(mPm), null)); 514 } 515 } 516 } 517 } 518 519 public ResolveInfo resolveInfoForPosition(int position) { 520 if (mList == null) { 521 return null; 522 } 523 524 return mList.get(position).ri; 525 } 526 527 public Intent intentForPosition(int position) { 528 if (mList == null) { 529 return null; 530 } 531 532 DisplayResolveInfo dri = mList.get(position); 533 534 Intent intent = new Intent(dri.origIntent != null 535 ? dri.origIntent : mIntent); 536 intent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT 537 |Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP); 538 ActivityInfo ai = dri.ri.activityInfo; 539 intent.setComponent(new ComponentName( 540 ai.applicationInfo.packageName, ai.name)); 541 return intent; 542 } 543 544 public int getCount() { 545 return mList != null ? mList.size() : 0; 546 } 547 548 public Object getItem(int position) { 549 return position; 550 } 551 552 public long getItemId(int position) { 553 return position; 554 } 555 556 public View getView(int position, View convertView, ViewGroup parent) { 557 View view; 558 if (convertView == null) { 559 view = mInflater.inflate( 560 com.android.internal.R.layout.resolve_list_item, parent, false); 561 562 // Fix the icon size even if we have different sized resources 563 ImageView icon = (ImageView)view.findViewById(R.id.icon); 564 ViewGroup.LayoutParams lp = (ViewGroup.LayoutParams) icon.getLayoutParams(); 565 lp.width = lp.height = mIconSize; 566 } else { 567 view = convertView; 568 } 569 bindView(view, mList.get(position)); 570 return view; 571 } 572 573 private final void bindView(View view, DisplayResolveInfo info) { 574 TextView text = (TextView)view.findViewById(com.android.internal.R.id.text1); 575 TextView text2 = (TextView)view.findViewById(com.android.internal.R.id.text2); 576 ImageView icon = (ImageView)view.findViewById(R.id.icon); 577 text.setText(info.displayLabel); 578 if (mShowExtended) { 579 text2.setVisibility(View.VISIBLE); 580 text2.setText(info.extendedInfo); 581 } else { 582 text2.setVisibility(View.GONE); 583 } 584 if (info.displayIcon == null) { 585 info.displayIcon = loadIconForResolveInfo(info.ri); 586 } 587 icon.setImageDrawable(info.displayIcon); 588 } 589 } 590 591 class ItemLongClickListener implements AdapterView.OnItemLongClickListener { 592 593 @Override 594 public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) { 595 ResolveInfo ri = mAdapter.resolveInfoForPosition(position); 596 showAppDetails(ri); 597 return true; 598 } 599 600 } 601} 602 603