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