1 /* 2 * Copyright (C) 2009 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.settings; 18 19 import android.app.Activity; 20 import android.content.Context; 21 import android.content.DialogInterface; 22 import android.content.Intent; 23 import android.content.Intent.ShortcutIconResource; 24 import android.content.pm.PackageManager; 25 import android.content.pm.PackageManager.NameNotFoundException; 26 import android.content.pm.ResolveInfo; 27 import android.content.res.Resources; 28 import android.graphics.Bitmap; 29 import android.graphics.Canvas; 30 import android.graphics.ColorFilter; 31 import android.graphics.Paint; 32 import android.graphics.PaintFlagsDrawFilter; 33 import android.graphics.PixelFormat; 34 import android.graphics.Rect; 35 import android.graphics.drawable.BitmapDrawable; 36 import android.graphics.drawable.Drawable; 37 import android.graphics.drawable.PaintDrawable; 38 import android.os.Bundle; 39 import android.os.Parcelable; 40 import android.util.DisplayMetrics; 41 import android.view.LayoutInflater; 42 import android.view.View; 43 import android.view.ViewGroup; 44 import android.widget.BaseAdapter; 45 import android.widget.TextView; 46 47 import com.android.internal.app.AlertActivity; 48 import com.android.internal.app.AlertController; 49 50 import java.util.ArrayList; 51 import java.util.Collections; 52 import java.util.List; 53 54 /** 55 * Displays a list of all activities matching the incoming 56 * {@link Intent#EXTRA_INTENT} query, along with any injected items. 57 */ 58 public class ActivityPicker extends AlertActivity implements 59 DialogInterface.OnClickListener, DialogInterface.OnCancelListener { 60 61 /** 62 * Adapter of items that are displayed in this dialog. 63 */ 64 private PickAdapter mAdapter; 65 66 /** 67 * Base {@link Intent} used when building list. 68 */ 69 private Intent mBaseIntent; 70 71 @Override onCreate(Bundle savedInstanceState)72 protected void onCreate(Bundle savedInstanceState) { 73 super.onCreate(savedInstanceState); 74 75 final Intent intent = getIntent(); 76 77 // Read base intent from extras, otherwise assume default 78 Parcelable parcel = intent.getParcelableExtra(Intent.EXTRA_INTENT); 79 if (parcel instanceof Intent) { 80 mBaseIntent = (Intent) parcel; 81 } else { 82 mBaseIntent = new Intent(Intent.ACTION_MAIN, null); 83 mBaseIntent.addCategory(Intent.CATEGORY_DEFAULT); 84 } 85 86 // Create dialog parameters 87 AlertController.AlertParams params = mAlertParams; 88 params.mOnClickListener = this; 89 params.mOnCancelListener = this; 90 91 // Use custom title if provided, otherwise default window title 92 if (intent.hasExtra(Intent.EXTRA_TITLE)) { 93 params.mTitle = intent.getStringExtra(Intent.EXTRA_TITLE); 94 } else { 95 params.mTitle = getTitle(); 96 } 97 98 // Build list adapter of pickable items 99 List<PickAdapter.Item> items = getItems(); 100 mAdapter = new PickAdapter(this, items); 101 params.mAdapter = mAdapter; 102 103 setupAlert(); 104 } 105 106 /** 107 * Handle clicking of dialog item by passing back 108 * {@link #getIntentForPosition(int)} in {@link #setResult(int, Intent)}. 109 */ onClick(DialogInterface dialog, int which)110 public void onClick(DialogInterface dialog, int which) { 111 Intent intent = getIntentForPosition(which); 112 setResult(Activity.RESULT_OK, intent); 113 finish(); 114 } 115 116 /** 117 * Handle canceled dialog by passing back {@link Activity#RESULT_CANCELED}. 118 */ onCancel(DialogInterface dialog)119 public void onCancel(DialogInterface dialog) { 120 setResult(Activity.RESULT_CANCELED); 121 finish(); 122 } 123 124 /** 125 * Build the specific {@link Intent} for a given list position. Convenience 126 * method that calls through to {@link PickAdapter.Item#getIntent(Intent)}. 127 */ getIntentForPosition(int position)128 protected Intent getIntentForPosition(int position) { 129 PickAdapter.Item item = (PickAdapter.Item) mAdapter.getItem(position); 130 return item.getIntent(mBaseIntent); 131 } 132 133 /** 134 * Build and return list of items to be shown in dialog. Default 135 * implementation mixes activities matching {@link #mBaseIntent} from 136 * {@link #putIntentItems(Intent, List)} with any injected items from 137 * {@link Intent#EXTRA_SHORTCUT_NAME}. Override this method in subclasses to 138 * change the items shown. 139 */ getItems()140 protected List<PickAdapter.Item> getItems() { 141 PackageManager packageManager = getPackageManager(); 142 List<PickAdapter.Item> items = new ArrayList<PickAdapter.Item>(); 143 144 // Add any injected pick items 145 final Intent intent = getIntent(); 146 ArrayList<String> labels = 147 intent.getStringArrayListExtra(Intent.EXTRA_SHORTCUT_NAME); 148 ArrayList<ShortcutIconResource> icons = 149 intent.getParcelableArrayListExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE); 150 151 if (labels != null && icons != null && labels.size() == icons.size()) { 152 for (int i = 0; i < labels.size(); i++) { 153 String label = labels.get(i); 154 Drawable icon = null; 155 156 try { 157 // Try loading icon from requested package 158 ShortcutIconResource iconResource = icons.get(i); 159 Resources res = packageManager.getResourcesForApplication( 160 iconResource.packageName); 161 icon = res.getDrawable(res.getIdentifier( 162 iconResource.resourceName, null, null), null); 163 } catch (NameNotFoundException e) { 164 // Ignore 165 } 166 167 items.add(new PickAdapter.Item(this, label, icon)); 168 } 169 } 170 171 // Add any intent items if base was given 172 if (mBaseIntent != null) { 173 putIntentItems(mBaseIntent, items); 174 } 175 176 return items; 177 } 178 179 /** 180 * Fill the given list with any activities matching the base {@link Intent}. 181 */ putIntentItems(Intent baseIntent, List<PickAdapter.Item> items)182 protected void putIntentItems(Intent baseIntent, List<PickAdapter.Item> items) { 183 PackageManager packageManager = getPackageManager(); 184 List<ResolveInfo> list = packageManager.queryIntentActivities(baseIntent, 185 0 /* no flags */); 186 Collections.sort(list, new ResolveInfo.DisplayNameComparator(packageManager)); 187 188 final int listSize = list.size(); 189 for (int i = 0; i < listSize; i++) { 190 ResolveInfo resolveInfo = list.get(i); 191 items.add(new PickAdapter.Item(this, packageManager, resolveInfo)); 192 } 193 } 194 195 /** 196 * Adapter which shows the set of activities that can be performed for a 197 * given {@link Intent}. 198 */ 199 protected static class PickAdapter extends BaseAdapter { 200 201 /** 202 * Item that appears in a {@link PickAdapter} list. 203 */ 204 public static class Item implements AppWidgetLoader.LabelledItem { 205 protected static IconResizer sResizer; 206 getResizer(Context context)207 protected IconResizer getResizer(Context context) { 208 if (sResizer == null) { 209 final Resources resources = context.getResources(); 210 int size = (int) resources.getDimension(android.R.dimen.app_icon_size); 211 sResizer = new IconResizer(size, size, resources.getDisplayMetrics()); 212 } 213 return sResizer; 214 } 215 216 CharSequence label; 217 Drawable icon; 218 String packageName; 219 String className; 220 Bundle extras; 221 222 /** 223 * Create a list item from given label and icon. 224 */ Item(Context context, CharSequence label, Drawable icon)225 Item(Context context, CharSequence label, Drawable icon) { 226 this.label = label; 227 this.icon = getResizer(context).createIconThumbnail(icon); 228 } 229 230 /** 231 * Create a list item and fill it with details from the given 232 * {@link ResolveInfo} object. 233 */ Item(Context context, PackageManager pm, ResolveInfo resolveInfo)234 Item(Context context, PackageManager pm, ResolveInfo resolveInfo) { 235 label = resolveInfo.loadLabel(pm); 236 if (label == null && resolveInfo.activityInfo != null) { 237 label = resolveInfo.activityInfo.name; 238 } 239 240 icon = getResizer(context).createIconThumbnail(resolveInfo.loadIcon(pm)); 241 packageName = resolveInfo.activityInfo.applicationInfo.packageName; 242 className = resolveInfo.activityInfo.name; 243 } 244 245 /** 246 * Build the {@link Intent} described by this item. If this item 247 * can't create a valid {@link android.content.ComponentName}, it will return 248 * {@link Intent#ACTION_CREATE_SHORTCUT} filled with the item label. 249 */ getIntent(Intent baseIntent)250 Intent getIntent(Intent baseIntent) { 251 Intent intent = new Intent(baseIntent); 252 if (packageName != null && className != null) { 253 // Valid package and class, so fill details as normal intent 254 intent.setClassName(packageName, className); 255 if (extras != null) { 256 intent.putExtras(extras); 257 } 258 } else { 259 // No valid package or class, so treat as shortcut with label 260 intent.setAction(Intent.ACTION_CREATE_SHORTCUT); 261 intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, label); 262 } 263 return intent; 264 } 265 getLabel()266 public CharSequence getLabel() { 267 return label; 268 } 269 } 270 271 private final LayoutInflater mInflater; 272 private final List<Item> mItems; 273 274 /** 275 * Create an adapter for the given items. 276 */ PickAdapter(Context context, List<Item> items)277 public PickAdapter(Context context, List<Item> items) { 278 mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 279 mItems = items; 280 } 281 282 /** 283 * {@inheritDoc} 284 */ getCount()285 public int getCount() { 286 return mItems.size(); 287 } 288 289 /** 290 * {@inheritDoc} 291 */ getItem(int position)292 public Object getItem(int position) { 293 return mItems.get(position); 294 } 295 296 /** 297 * {@inheritDoc} 298 */ getItemId(int position)299 public long getItemId(int position) { 300 return position; 301 } 302 303 /** 304 * {@inheritDoc} 305 */ getView(int position, View convertView, ViewGroup parent)306 public View getView(int position, View convertView, ViewGroup parent) { 307 if (convertView == null) { 308 convertView = mInflater.inflate(R.layout.pick_item, parent, false); 309 } 310 311 Item item = (Item) getItem(position); 312 TextView textView = (TextView) convertView; 313 textView.setText(item.label); 314 textView.setCompoundDrawablesWithIntrinsicBounds(item.icon, null, null, null); 315 316 return convertView; 317 } 318 } 319 320 /** 321 * Utility class to resize icons to match default icon size. Code is mostly 322 * borrowed from Launcher. 323 */ 324 private static class IconResizer { 325 private final int mIconWidth; 326 private final int mIconHeight; 327 328 private final DisplayMetrics mMetrics; 329 private final Rect mOldBounds = new Rect(); 330 private final Canvas mCanvas = new Canvas(); 331 IconResizer(int width, int height, DisplayMetrics metrics)332 public IconResizer(int width, int height, DisplayMetrics metrics) { 333 mCanvas.setDrawFilter(new PaintFlagsDrawFilter(Paint.DITHER_FLAG, 334 Paint.FILTER_BITMAP_FLAG)); 335 336 mMetrics = metrics; 337 mIconWidth = width; 338 mIconHeight = height; 339 } 340 341 /** 342 * Returns a Drawable representing the thumbnail of the specified Drawable. 343 * The size of the thumbnail is defined by the dimension 344 * android.R.dimen.launcher_application_icon_size. 345 * 346 * This method is not thread-safe and should be invoked on the UI thread only. 347 * 348 * @param icon The icon to get a thumbnail of. 349 * 350 * @return A thumbnail for the specified icon or the icon itself if the 351 * thumbnail could not be created. 352 */ createIconThumbnail(Drawable icon)353 public Drawable createIconThumbnail(Drawable icon) { 354 int width = mIconWidth; 355 int height = mIconHeight; 356 357 if (icon == null) { 358 return new EmptyDrawable(width, height); 359 } 360 361 try { 362 if (icon instanceof PaintDrawable) { 363 PaintDrawable painter = (PaintDrawable) icon; 364 painter.setIntrinsicWidth(width); 365 painter.setIntrinsicHeight(height); 366 } else if (icon instanceof BitmapDrawable) { 367 // Ensure the bitmap has a density. 368 BitmapDrawable bitmapDrawable = (BitmapDrawable) icon; 369 Bitmap bitmap = bitmapDrawable.getBitmap(); 370 if (bitmap.getDensity() == Bitmap.DENSITY_NONE) { 371 bitmapDrawable.setTargetDensity(mMetrics); 372 } 373 } 374 int iconWidth = icon.getIntrinsicWidth(); 375 int iconHeight = icon.getIntrinsicHeight(); 376 377 if (iconWidth > 0 && iconHeight > 0) { 378 if (width < iconWidth || height < iconHeight) { 379 final float ratio = (float) iconWidth / iconHeight; 380 381 if (iconWidth > iconHeight) { 382 height = (int) (width / ratio); 383 } else if (iconHeight > iconWidth) { 384 width = (int) (height * ratio); 385 } 386 387 final Bitmap.Config c = icon.getOpacity() != PixelFormat.OPAQUE ? 388 Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565; 389 final Bitmap thumb = Bitmap.createBitmap(mIconWidth, mIconHeight, c); 390 final Canvas canvas = mCanvas; 391 canvas.setBitmap(thumb); 392 // Copy the old bounds to restore them later 393 // If we were to do oldBounds = icon.getBounds(), 394 // the call to setBounds() that follows would 395 // change the same instance and we would lose the 396 // old bounds 397 mOldBounds.set(icon.getBounds()); 398 final int x = (mIconWidth - width) / 2; 399 final int y = (mIconHeight - height) / 2; 400 icon.setBounds(x, y, x + width, y + height); 401 icon.draw(canvas); 402 icon.setBounds(mOldBounds); 403 //noinspection deprecation 404 icon = new BitmapDrawable(thumb); 405 ((BitmapDrawable) icon).setTargetDensity(mMetrics); 406 canvas.setBitmap(null); 407 } else if (iconWidth < width && iconHeight < height) { 408 final Bitmap.Config c = Bitmap.Config.ARGB_8888; 409 final Bitmap thumb = Bitmap.createBitmap(mIconWidth, mIconHeight, c); 410 final Canvas canvas = mCanvas; 411 canvas.setBitmap(thumb); 412 mOldBounds.set(icon.getBounds()); 413 final int x = (width - iconWidth) / 2; 414 final int y = (height - iconHeight) / 2; 415 icon.setBounds(x, y, x + iconWidth, y + iconHeight); 416 icon.draw(canvas); 417 icon.setBounds(mOldBounds); 418 //noinspection deprecation 419 icon = new BitmapDrawable(thumb); 420 ((BitmapDrawable) icon).setTargetDensity(mMetrics); 421 canvas.setBitmap(null); 422 } 423 } 424 425 } catch (Throwable t) { 426 icon = new EmptyDrawable(width, height); 427 } 428 429 return icon; 430 } 431 } 432 433 private static class EmptyDrawable extends Drawable { 434 private final int mWidth; 435 private final int mHeight; 436 EmptyDrawable(int width, int height)437 EmptyDrawable(int width, int height) { 438 mWidth = width; 439 mHeight = height; 440 } 441 442 @Override getIntrinsicWidth()443 public int getIntrinsicWidth() { 444 return mWidth; 445 } 446 447 @Override getIntrinsicHeight()448 public int getIntrinsicHeight() { 449 return mHeight; 450 } 451 452 @Override getMinimumWidth()453 public int getMinimumWidth() { 454 return mWidth; 455 } 456 457 @Override getMinimumHeight()458 public int getMinimumHeight() { 459 return mHeight; 460 } 461 462 @Override draw(Canvas canvas)463 public void draw(Canvas canvas) { 464 } 465 466 @Override setAlpha(int alpha)467 public void setAlpha(int alpha) { 468 } 469 470 @Override setColorFilter(ColorFilter cf)471 public void setColorFilter(ColorFilter cf) { 472 } 473 474 @Override getOpacity()475 public int getOpacity() { 476 return PixelFormat.TRANSLUCENT; 477 } 478 } 479 } 480