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.graphics.ColorFilter; 20 import android.util.DisplayMetrics; 21 import com.android.internal.app.AlertActivity; 22 import com.android.internal.app.AlertController; 23 24 import android.app.Activity; 25 import android.content.Context; 26 import android.content.DialogInterface; 27 import android.content.Intent; 28 import android.content.Intent.ShortcutIconResource; 29 import android.content.pm.PackageManager; 30 import android.content.pm.ResolveInfo; 31 import android.content.pm.PackageManager.NameNotFoundException; 32 import android.content.res.Resources; 33 import android.graphics.Bitmap; 34 import android.graphics.Canvas; 35 import android.graphics.Paint; 36 import android.graphics.PaintFlagsDrawFilter; 37 import android.graphics.PixelFormat; 38 import android.graphics.Rect; 39 import android.graphics.drawable.BitmapDrawable; 40 import android.graphics.drawable.Drawable; 41 import android.graphics.drawable.PaintDrawable; 42 import android.os.Bundle; 43 import android.os.Parcelable; 44 import android.view.LayoutInflater; 45 import android.view.View; 46 import android.view.ViewGroup; 47 import android.widget.BaseAdapter; 48 import android.widget.TextView; 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 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 */ 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 */ 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 */ 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 */ 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)); 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 */ 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 { 205 protected static IconResizer sResizer; 206 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 */ 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 */ 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 */ 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 } 266 267 private final LayoutInflater mInflater; 268 private final List<Item> mItems; 269 270 /** 271 * Create an adapter for the given items. 272 */ 273 public PickAdapter(Context context, List<Item> items) { 274 mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 275 mItems = items; 276 } 277 278 /** 279 * {@inheritDoc} 280 */ 281 public int getCount() { 282 return mItems.size(); 283 } 284 285 /** 286 * {@inheritDoc} 287 */ 288 public Object getItem(int position) { 289 return mItems.get(position); 290 } 291 292 /** 293 * {@inheritDoc} 294 */ 295 public long getItemId(int position) { 296 return position; 297 } 298 299 /** 300 * {@inheritDoc} 301 */ 302 public View getView(int position, View convertView, ViewGroup parent) { 303 if (convertView == null) { 304 convertView = mInflater.inflate(R.layout.pick_item, parent, false); 305 } 306 307 Item item = (Item) getItem(position); 308 TextView textView = (TextView) convertView; 309 textView.setText(item.label); 310 textView.setCompoundDrawablesWithIntrinsicBounds(item.icon, null, null, null); 311 312 return convertView; 313 } 314 } 315 316 /** 317 * Utility class to resize icons to match default icon size. Code is mostly 318 * borrowed from Launcher. 319 */ 320 private static class IconResizer { 321 private final int mIconWidth; 322 private final int mIconHeight; 323 324 private final DisplayMetrics mMetrics; 325 private final Rect mOldBounds = new Rect(); 326 private final Canvas mCanvas = new Canvas(); 327 328 public IconResizer(int width, int height, DisplayMetrics metrics) { 329 mCanvas.setDrawFilter(new PaintFlagsDrawFilter(Paint.DITHER_FLAG, 330 Paint.FILTER_BITMAP_FLAG)); 331 332 mMetrics = metrics; 333 mIconWidth = width; 334 mIconHeight = height; 335 } 336 337 /** 338 * Returns a Drawable representing the thumbnail of the specified Drawable. 339 * The size of the thumbnail is defined by the dimension 340 * android.R.dimen.launcher_application_icon_size. 341 * 342 * This method is not thread-safe and should be invoked on the UI thread only. 343 * 344 * @param icon The icon to get a thumbnail of. 345 * 346 * @return A thumbnail for the specified icon or the icon itself if the 347 * thumbnail could not be created. 348 */ 349 public Drawable createIconThumbnail(Drawable icon) { 350 int width = mIconWidth; 351 int height = mIconHeight; 352 353 if (icon == null) { 354 return new EmptyDrawable(width, height); 355 } 356 357 try { 358 if (icon instanceof PaintDrawable) { 359 PaintDrawable painter = (PaintDrawable) icon; 360 painter.setIntrinsicWidth(width); 361 painter.setIntrinsicHeight(height); 362 } else if (icon instanceof BitmapDrawable) { 363 // Ensure the bitmap has a density. 364 BitmapDrawable bitmapDrawable = (BitmapDrawable) icon; 365 Bitmap bitmap = bitmapDrawable.getBitmap(); 366 if (bitmap.getDensity() == Bitmap.DENSITY_NONE) { 367 bitmapDrawable.setTargetDensity(mMetrics); 368 } 369 } 370 int iconWidth = icon.getIntrinsicWidth(); 371 int iconHeight = icon.getIntrinsicHeight(); 372 373 if (iconWidth > 0 && iconHeight > 0) { 374 if (width < iconWidth || height < iconHeight) { 375 final float ratio = (float) iconWidth / iconHeight; 376 377 if (iconWidth > iconHeight) { 378 height = (int) (width / ratio); 379 } else if (iconHeight > iconWidth) { 380 width = (int) (height * ratio); 381 } 382 383 final Bitmap.Config c = icon.getOpacity() != PixelFormat.OPAQUE ? 384 Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565; 385 final Bitmap thumb = Bitmap.createBitmap(mIconWidth, mIconHeight, c); 386 final Canvas canvas = mCanvas; 387 canvas.setBitmap(thumb); 388 // Copy the old bounds to restore them later 389 // If we were to do oldBounds = icon.getBounds(), 390 // the call to setBounds() that follows would 391 // change the same instance and we would lose the 392 // old bounds 393 mOldBounds.set(icon.getBounds()); 394 final int x = (mIconWidth - width) / 2; 395 final int y = (mIconHeight - height) / 2; 396 icon.setBounds(x, y, x + width, y + height); 397 icon.draw(canvas); 398 icon.setBounds(mOldBounds); 399 //noinspection deprecation 400 icon = new BitmapDrawable(thumb); 401 ((BitmapDrawable) icon).setTargetDensity(mMetrics); 402 } else if (iconWidth < width && iconHeight < height) { 403 final Bitmap.Config c = Bitmap.Config.ARGB_8888; 404 final Bitmap thumb = Bitmap.createBitmap(mIconWidth, mIconHeight, c); 405 final Canvas canvas = mCanvas; 406 canvas.setBitmap(thumb); 407 mOldBounds.set(icon.getBounds()); 408 final int x = (width - iconWidth) / 2; 409 final int y = (height - iconHeight) / 2; 410 icon.setBounds(x, y, x + iconWidth, y + iconHeight); 411 icon.draw(canvas); 412 icon.setBounds(mOldBounds); 413 //noinspection deprecation 414 icon = new BitmapDrawable(thumb); 415 ((BitmapDrawable) icon).setTargetDensity(mMetrics); 416 } 417 } 418 419 } catch (Throwable t) { 420 icon = new EmptyDrawable(width, height); 421 } 422 423 return icon; 424 } 425 } 426 427 private static class EmptyDrawable extends Drawable { 428 private final int mWidth; 429 private final int mHeight; 430 431 EmptyDrawable(int width, int height) { 432 mWidth = width; 433 mHeight = height; 434 } 435 436 @Override 437 public int getIntrinsicWidth() { 438 return mWidth; 439 } 440 441 @Override 442 public int getIntrinsicHeight() { 443 return mHeight; 444 } 445 446 @Override 447 public int getMinimumWidth() { 448 return mWidth; 449 } 450 451 @Override 452 public int getMinimumHeight() { 453 return mHeight; 454 } 455 456 @Override 457 public void draw(Canvas canvas) { 458 } 459 460 @Override 461 public void setAlpha(int alpha) { 462 } 463 464 @Override 465 public void setColorFilter(ColorFilter cf) { 466 } 467 468 @Override 469 public int getOpacity() { 470 return PixelFormat.TRANSLUCENT; 471 } 472 } 473 } 474