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 com.android.internal.app.AlertActivity; 20 import com.android.internal.app.AlertController; 21 22 import android.app.Activity; 23 import android.content.Context; 24 import android.content.DialogInterface; 25 import android.content.Intent; 26 import android.content.Intent.ShortcutIconResource; 27 import android.content.pm.PackageManager; 28 import android.content.pm.ResolveInfo; 29 import android.content.pm.PackageManager.NameNotFoundException; 30 import android.content.res.Resources; 31 import android.graphics.Bitmap; 32 import android.graphics.Canvas; 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.view.Gravity; 43 import android.view.LayoutInflater; 44 import android.view.View; 45 import android.view.ViewGroup; 46 import android.view.WindowManager; 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 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)); 163 } catch (NameNotFoundException e) { 164 } 165 166 items.add(new PickAdapter.Item(this, label, icon)); 167 } 168 } 169 170 // Add any intent items if base was given 171 if (mBaseIntent != null) { 172 putIntentItems(mBaseIntent, items); 173 } 174 175 return items; 176 } 177 178 /** 179 * Fill the given list with any activities matching the base {@link Intent}. 180 */ putIntentItems(Intent baseIntent, List<PickAdapter.Item> items)181 protected void putIntentItems(Intent baseIntent, List<PickAdapter.Item> items) { 182 PackageManager packageManager = getPackageManager(); 183 List<ResolveInfo> list = packageManager.queryIntentActivities(baseIntent, 184 0 /* no flags */); 185 Collections.sort(list, new ResolveInfo.DisplayNameComparator(packageManager)); 186 187 final int listSize = list.size(); 188 for (int i = 0; i < listSize; i++) { 189 ResolveInfo resolveInfo = list.get(i); 190 items.add(new PickAdapter.Item(this, packageManager, resolveInfo)); 191 } 192 } 193 194 /** 195 * Adapter which shows the set of activities that can be performed for a 196 * given {@link Intent}. 197 */ 198 protected static class PickAdapter extends BaseAdapter { 199 200 /** 201 * Item that appears in a {@link PickAdapter} list. 202 */ 203 public static class Item { 204 protected static IconResizer sResizer; 205 getResizer(Context context)206 protected IconResizer getResizer(Context context) { 207 if (sResizer == null) { 208 sResizer = new IconResizer(context); 209 } 210 return sResizer; 211 } 212 213 CharSequence label; 214 Drawable icon; 215 String packageName; 216 String className; 217 Bundle extras; 218 219 /** 220 * Create a list item from given label and icon. 221 */ Item(Context context, CharSequence label, Drawable icon)222 Item(Context context, CharSequence label, Drawable icon) { 223 this.label = label; 224 this.icon = getResizer(context).createIconThumbnail(icon); 225 } 226 227 /** 228 * Create a list item and fill it with details from the given 229 * {@link ResolveInfo} object. 230 */ Item(Context context, PackageManager pm, ResolveInfo resolveInfo)231 Item(Context context, PackageManager pm, ResolveInfo resolveInfo) { 232 label = resolveInfo.loadLabel(pm); 233 if (label == null && resolveInfo.activityInfo != null) { 234 label = resolveInfo.activityInfo.name; 235 } 236 237 icon = getResizer(context).createIconThumbnail(resolveInfo.loadIcon(pm)); 238 packageName = resolveInfo.activityInfo.applicationInfo.packageName; 239 className = resolveInfo.activityInfo.name; 240 } 241 242 /** 243 * Build the {@link Intent} described by this item. If this item 244 * can't create a valid {@link ComponentName}, it will return 245 * {@link Intent#ACTION_CREATE_SHORTCUT} filled with the item label. 246 */ getIntent(Intent baseIntent)247 Intent getIntent(Intent baseIntent) { 248 Intent intent = new Intent(baseIntent); 249 if (packageName != null && className != null) { 250 // Valid package and class, so fill details as normal intent 251 intent.setClassName(packageName, className); 252 if (extras != null) { 253 intent.putExtras(extras); 254 } 255 } else { 256 // No valid package or class, so treat as shortcut with label 257 intent.setAction(Intent.ACTION_CREATE_SHORTCUT); 258 intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, label); 259 } 260 return intent; 261 } 262 } 263 264 private final LayoutInflater mInflater; 265 266 private List<Item> mItems; 267 private int mLayoutRes = R.layout.pick_item; 268 269 /** 270 * Create an adapter for the given items. 271 */ PickAdapter(Context context, List<Item> items)272 public PickAdapter(Context context, List<Item> items) { 273 mInflater = (LayoutInflater) 274 context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 275 mItems = items; 276 } 277 278 /** 279 * {@inheritDoc} 280 */ getCount()281 public int getCount() { 282 return mItems.size(); 283 } 284 285 /** 286 * {@inheritDoc} 287 */ getItem(int position)288 public Object getItem(int position) { 289 return mItems.get(position); 290 } 291 292 /** 293 * {@inheritDoc} 294 */ getItemId(int position)295 public long getItemId(int position) { 296 return position; 297 } 298 299 /** 300 * {@inheritDoc} 301 */ getView(int position, View convertView, ViewGroup parent)302 public View getView(int position, View convertView, ViewGroup parent) { 303 if (convertView == null) { 304 convertView = mInflater.inflate(mLayoutRes, 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 int mIconWidth = -1; 322 private int mIconHeight = -1; 323 324 private final Rect mOldBounds = new Rect(); 325 private Canvas mCanvas = new Canvas(); 326 IconResizer(Context context)327 public IconResizer(Context context) { 328 mCanvas.setDrawFilter(new PaintFlagsDrawFilter(Paint.DITHER_FLAG, 329 Paint.FILTER_BITMAP_FLAG)); 330 331 final Resources resources = context.getResources(); 332 mIconWidth = mIconHeight = (int) resources.getDimension( 333 android.R.dimen.app_icon_size); 334 } 335 336 /** 337 * Returns a Drawable representing the thumbnail of the specified Drawable. 338 * The size of the thumbnail is defined by the dimension 339 * android.R.dimen.launcher_application_icon_size. 340 * 341 * This method is not thread-safe and should be invoked on the UI thread only. 342 * 343 * @param icon The icon to get a thumbnail of. 344 * 345 * @return A thumbnail for the specified icon or the icon itself if the 346 * thumbnail could not be created. 347 */ createIconThumbnail(Drawable icon)348 public Drawable createIconThumbnail(Drawable icon) { 349 int width = mIconWidth; 350 int height = mIconHeight; 351 352 if (icon == null) { 353 return null; 354 } 355 356 final int iconWidth = icon.getIntrinsicWidth(); 357 final int iconHeight = icon.getIntrinsicHeight(); 358 359 if (icon instanceof PaintDrawable) { 360 PaintDrawable painter = (PaintDrawable) icon; 361 painter.setIntrinsicWidth(width); 362 painter.setIntrinsicHeight(height); 363 } 364 365 if (width > 0 && height > 0) { 366 if (width < iconWidth || height < iconHeight) { 367 final float ratio = (float) iconWidth / iconHeight; 368 369 if (iconWidth > iconHeight) { 370 height = (int) (width / ratio); 371 } else if (iconHeight > iconWidth) { 372 width = (int) (height * ratio); 373 } 374 375 final Bitmap.Config c = icon.getOpacity() != PixelFormat.OPAQUE ? 376 Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565; 377 final Bitmap thumb = Bitmap.createBitmap(mIconWidth, mIconHeight, c); 378 final Canvas canvas = mCanvas; 379 canvas.setBitmap(thumb); 380 // Copy the old bounds to restore them later 381 // If we were to do oldBounds = icon.getBounds(), 382 // the call to setBounds() that follows would 383 // change the same instance and we would lose the 384 // old bounds 385 mOldBounds.set(icon.getBounds()); 386 final int x = (mIconWidth - width) / 2; 387 final int y = (mIconHeight - height) / 2; 388 icon.setBounds(x, y, x + width, y + height); 389 icon.draw(canvas); 390 icon.setBounds(mOldBounds); 391 icon = new BitmapDrawable(thumb); 392 } else if (iconWidth < width && iconHeight < height) { 393 final Bitmap.Config c = Bitmap.Config.ARGB_8888; 394 final Bitmap thumb = Bitmap.createBitmap(mIconWidth, mIconHeight, c); 395 final Canvas canvas = mCanvas; 396 canvas.setBitmap(thumb); 397 mOldBounds.set(icon.getBounds()); 398 final int x = (width - iconWidth) / 2; 399 final int y = (height - iconHeight) / 2; 400 icon.setBounds(x, y, x + iconWidth, y + iconHeight); 401 icon.draw(canvas); 402 icon.setBounds(mOldBounds); 403 icon = new BitmapDrawable(thumb); 404 } 405 } 406 407 return icon; 408 } 409 } 410 } 411