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