2 * Copyright (C) 2009 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.settings;
19 import android.util.DisplayMetrics;
20 import com.android.internal.app.AlertActivity;
21 import com.android.internal.app.AlertController;
23 import android.app.Activity;
24 import android.content.Context;
25 import android.content.DialogInterface;
26 import android.content.Intent;
27 import android.content.Intent.ShortcutIconResource;
28 import android.content.pm.PackageManager;
29 import android.content.pm.ResolveInfo;
30 import android.content.pm.PackageManager.NameNotFoundException;
31 import android.content.res.Resources;
32 import android.graphics.Bitmap;
33 import android.graphics.Canvas;
34 import android.graphics.Paint;
35 import android.graphics.PaintFlagsDrawFilter;
36 import android.graphics.PixelFormat;
37 import android.graphics.Rect;
38 import android.graphics.drawable.BitmapDrawable;
39 import android.graphics.drawable.Drawable;
40 import android.graphics.drawable.PaintDrawable;
41 import android.os.Bundle;
42 import android.os.Parcelable;
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;
49 import java.util.ArrayList;
50 import java.util.Collections;
51 import java.util.List;
54 * Displays a list of all activities matching the incoming
55 * {@link Intent#EXTRA_INTENT} query, along with any injected items.
57 public class ActivityPicker extends AlertActivity implements
58 DialogInterface.OnClickListener, DialogInterface.OnCancelListener {
61 * Adapter of items that are displayed in this dialog.
63 private PickAdapter mAdapter;
66 * Base {@link Intent} used when building list.
68 private Intent mBaseIntent;
71 protected void onCreate(Bundle savedInstanceState) {
72 super.onCreate(savedInstanceState);
74 final Intent intent = getIntent();
76 // Read base intent from extras, otherwise assume default
77 Parcelable parcel = intent.getParcelableExtra(Intent.EXTRA_INTENT);
78 if (parcel instanceof Intent) {
79 mBaseIntent = (Intent) parcel;
81 mBaseIntent = new Intent(Intent.ACTION_MAIN, null);
82 mBaseIntent.addCategory(Intent.CATEGORY_DEFAULT);
85 // Create dialog parameters
86 AlertController.AlertParams params = mAlertParams;
87 params.mOnClickListener = this;
88 params.mOnCancelListener = this;
90 // Use custom title if provided, otherwise default window title
91 if (intent.hasExtra(Intent.EXTRA_TITLE)) {
92 params.mTitle = intent.getStringExtra(Intent.EXTRA_TITLE);
94 params.mTitle = getTitle();
97 // Build list adapter of pickable items
98 List<PickAdapter.Item> items = getItems();
99 mAdapter = new PickAdapter(this, items);
100 params.mAdapter = mAdapter;
106 * Handle clicking of dialog item by passing back
107 * {@link #getIntentForPosition(int)} in {@link #setResult(int, Intent)}.
109 public void onClick(DialogInterface dialog, int which) {
110 Intent intent = getIntentForPosition(which);
111 setResult(Activity.RESULT_OK, intent);
116 * Handle canceled dialog by passing back {@link Activity#RESULT_CANCELED}.
118 public void onCancel(DialogInterface dialog) {
119 setResult(Activity.RESULT_CANCELED);
124 * Build the specific {@link Intent} for a given list position. Convenience
125 * method that calls through to {@link PickAdapter.Item#getIntent(Intent)}.
127 protected Intent getIntentForPosition(int position) {
128 PickAdapter.Item item = (PickAdapter.Item) mAdapter.getItem(position);
129 return item.getIntent(mBaseIntent);
133 * Build and return list of items to be shown in dialog. Default
134 * implementation mixes activities matching {@link #mBaseIntent} from
135 * {@link #putIntentItems(Intent, List)} with any injected items from
136 * {@link Intent#EXTRA_SHORTCUT_NAME}. Override this method in subclasses to
137 * change the items shown.
139 protected List<PickAdapter.Item> getItems() {
140 PackageManager packageManager = getPackageManager();
141 List<PickAdapter.Item> items = new ArrayList<PickAdapter.Item>();
143 // Add any injected pick items
144 final Intent intent = getIntent();
145 ArrayList<String> labels =
146 intent.getStringArrayListExtra(Intent.EXTRA_SHORTCUT_NAME);
147 ArrayList<ShortcutIconResource> icons =
148 intent.getParcelableArrayListExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE);
150 if (labels != null && icons != null && labels.size() == icons.size()) {
151 for (int i = 0; i < labels.size(); i++) {
152 String label = labels.get(i);
153 Drawable icon = null;
156 // Try loading icon from requested package
157 ShortcutIconResource iconResource = icons.get(i);
158 Resources res = packageManager.getResourcesForApplication(
159 iconResource.packageName);
160 icon = res.getDrawable(res.getIdentifier(
161 iconResource.resourceName, null, null));
162 } catch (NameNotFoundException e) {
166 items.add(new PickAdapter.Item(this, label, icon));
170 // Add any intent items if base was given
171 if (mBaseIntent != null) {
172 putIntentItems(mBaseIntent, items);
179 * Fill the given list with any activities matching the base {@link Intent}.
181 protected void putIntentItems(Intent baseIntent, List<PickAdapter.Item> items) {
182 PackageManager packageManager = getPackageManager();
183 List<ResolveInfo> list = packageManager.queryIntentActivities(baseIntent,
185 Collections.sort(list, new ResolveInfo.DisplayNameComparator(packageManager));
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));
195 * Adapter which shows the set of activities that can be performed for a
196 * given {@link Intent}.
198 protected static class PickAdapter extends BaseAdapter {
201 * Item that appears in a {@link PickAdapter} list.
203 public static class Item {
204 protected static IconResizer sResizer;
206 protected IconResizer getResizer(Context context) {
207 if (sResizer == null) {
208 final Resources resources = context.getResources();
209 int size = (int) resources.getDimension(android.R.dimen.app_icon_size);
210 sResizer = new IconResizer(size, size, resources.getDisplayMetrics());
222 * Create a list item from given label and icon.
224 Item(Context context, CharSequence label, Drawable icon) {
226 this.icon = getResizer(context).createIconThumbnail(icon);
230 * Create a list item and fill it with details from the given
231 * {@link ResolveInfo} object.
233 Item(Context context, PackageManager pm, ResolveInfo resolveInfo) {
234 label = resolveInfo.loadLabel(pm);
235 if (label == null && resolveInfo.activityInfo != null) {
236 label = resolveInfo.activityInfo.name;
239 icon = getResizer(context).createIconThumbnail(resolveInfo.loadIcon(pm));
240 packageName = resolveInfo.activityInfo.applicationInfo.packageName;
241 className = resolveInfo.activityInfo.name;
245 * Build the {@link Intent} described by this item. If this item
246 * can't create a valid {@link android.content.ComponentName}, it will return
247 * {@link Intent#ACTION_CREATE_SHORTCUT} filled with the item label.
249 Intent getIntent(Intent baseIntent) {
250 Intent intent = new Intent(baseIntent);
251 if (packageName != null && className != null) {
252 // Valid package and class, so fill details as normal intent
253 intent.setClassName(packageName, className);
254 if (extras != null) {
255 intent.putExtras(extras);
258 // No valid package or class, so treat as shortcut with label
259 intent.setAction(Intent.ACTION_CREATE_SHORTCUT);
260 intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, label);
266 private final LayoutInflater mInflater;
267 private final List<Item> mItems;
270 * Create an adapter for the given items.
272 public PickAdapter(Context context, List<Item> items) {
273 mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
280 public int getCount() {
281 return mItems.size();
287 public Object getItem(int position) {
288 return mItems.get(position);
294 public long getItemId(int position) {
301 public View getView(int position, View convertView, ViewGroup parent) {
302 if (convertView == null) {
303 convertView = mInflater.inflate(R.layout.pick_item, parent, false);
306 Item item = (Item) getItem(position);
307 TextView textView = (TextView) convertView;
308 textView.setText(item.label);
309 textView.setCompoundDrawablesWithIntrinsicBounds(item.icon, null, null, null);
316 * Utility class to resize icons to match default icon size. Code is mostly
317 * borrowed from Launcher.
319 private static class IconResizer {
320 private final int mIconWidth;
321 private final int mIconHeight;
323 private final DisplayMetrics mMetrics;
324 private final Rect mOldBounds = new Rect();
325 private final Canvas mCanvas = new Canvas();
327 public IconResizer(int width, int height, DisplayMetrics metrics) {
328 mCanvas.setDrawFilter(new PaintFlagsDrawFilter(Paint.DITHER_FLAG,
329 Paint.FILTER_BITMAP_FLAG));
333 mIconHeight = height;
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.
341 * This method is not thread-safe and should be invoked on the UI thread only.
343 * @param icon The icon to get a thumbnail of.
345 * @return A thumbnail for the specified icon or the icon itself if the
346 * thumbnail could not be created.
348 public Drawable createIconThumbnail(Drawable icon) {
349 int width = mIconWidth;
350 int height = mIconHeight;
352 if (icon instanceof PaintDrawable) {
353 PaintDrawable painter = (PaintDrawable) icon;
354 painter.setIntrinsicWidth(width);
355 painter.setIntrinsicHeight(height);
356 } else if (icon instanceof BitmapDrawable) {
357 // Ensure the bitmap has a density.
358 BitmapDrawable bitmapDrawable = (BitmapDrawable) icon;
359 Bitmap bitmap = bitmapDrawable.getBitmap();
360 if (bitmap.getDensity() == Bitmap.DENSITY_NONE) {
361 bitmapDrawable.setTargetDensity(mMetrics);
364 int iconWidth = icon.getIntrinsicWidth();
365 int iconHeight = icon.getIntrinsicHeight();
367 if (iconWidth > 0 && iconHeight > 0) {
368 if (width < iconWidth || height < iconHeight) {
369 final float ratio = (float) iconWidth / iconHeight;
371 if (iconWidth > iconHeight) {
372 height = (int) (width / ratio);
373 } else if (iconHeight > iconWidth) {
374 width = (int) (height * ratio);
377 final Bitmap.Config c = icon.getOpacity() != PixelFormat.OPAQUE ?
378 Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565;
379 final Bitmap thumb = Bitmap.createBitmap(mIconWidth, mIconHeight, c);
380 final Canvas canvas = mCanvas;
381 canvas.setBitmap(thumb);
382 // Copy the old bounds to restore them later
383 // If we were to do oldBounds = icon.getBounds(),
384 // the call to setBounds() that follows would
385 // change the same instance and we would lose the
387 mOldBounds.set(icon.getBounds());
388 final int x = (mIconWidth - width) / 2;
389 final int y = (mIconHeight - height) / 2;
390 icon.setBounds(x, y, x + width, y + height);
392 icon.setBounds(mOldBounds);
393 //noinspection deprecation
394 icon = new BitmapDrawable(thumb);
395 ((BitmapDrawable) icon).setTargetDensity(mMetrics);
396 } else if (iconWidth < width && iconHeight < height) {
397 final Bitmap.Config c = Bitmap.Config.ARGB_8888;
398 final Bitmap thumb = Bitmap.createBitmap(mIconWidth, mIconHeight, c);
399 final Canvas canvas = mCanvas;
400 canvas.setBitmap(thumb);
401 mOldBounds.set(icon.getBounds());
402 final int x = (width - iconWidth) / 2;
403 final int y = (height - iconHeight) / 2;
404 icon.setBounds(x, y, x + iconWidth, y + iconHeight);
406 icon.setBounds(mOldBounds);
407 //noinspection deprecation
408 icon = new BitmapDrawable(thumb);
409 ((BitmapDrawable) icon).setTargetDensity(mMetrics);