OSDN Git Service

Display icons in the correct density in the ActivityPicker.
[android-x86/packages-apps-Settings.git] / src / com / android / settings / ActivityPicker.java
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.util.DisplayMetrics;
20 import com.android.internal.app.AlertActivity;
21 import com.android.internal.app.AlertController;
22
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;
48
49 import java.util.ArrayList;
50 import java.util.Collections;
51 import java.util.List;
52
53 /**
54  * Displays a list of all activities matching the incoming
55  * {@link Intent#EXTRA_INTENT} query, along with any injected items.
56  */
57 public class ActivityPicker extends AlertActivity implements
58         DialogInterface.OnClickListener, DialogInterface.OnCancelListener {
59     
60     /**
61      * Adapter of items that are displayed in this dialog.
62      */
63     private PickAdapter mAdapter;
64     
65     /**
66      * Base {@link Intent} used when building list.
67      */
68     private Intent mBaseIntent;
69     
70     @Override
71     protected void onCreate(Bundle savedInstanceState) {
72         super.onCreate(savedInstanceState);
73         
74         final Intent intent = getIntent();
75         
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;
80         } else {
81             mBaseIntent = new Intent(Intent.ACTION_MAIN, null);
82             mBaseIntent.addCategory(Intent.CATEGORY_DEFAULT);
83         }
84
85         // Create dialog parameters
86         AlertController.AlertParams params = mAlertParams;
87         params.mOnClickListener = this;
88         params.mOnCancelListener = this;
89         
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);
93         } else {
94             params.mTitle = getTitle();
95         }
96         
97         // Build list adapter of pickable items
98         List<PickAdapter.Item> items = getItems();
99         mAdapter = new PickAdapter(this, items);
100         params.mAdapter = mAdapter;
101
102         setupAlert();
103     }
104     
105     /**
106      * Handle clicking of dialog item by passing back
107      * {@link #getIntentForPosition(int)} in {@link #setResult(int, Intent)}.
108      */
109     public void onClick(DialogInterface dialog, int which) {
110         Intent intent = getIntentForPosition(which);
111         setResult(Activity.RESULT_OK, intent);
112         finish();
113     }
114     
115     /**
116      * Handle canceled dialog by passing back {@link Activity#RESULT_CANCELED}.
117      */
118     public void onCancel(DialogInterface dialog) {
119         setResult(Activity.RESULT_CANCELED);
120         finish();
121     }
122
123     /**
124      * Build the specific {@link Intent} for a given list position. Convenience
125      * method that calls through to {@link PickAdapter.Item#getIntent(Intent)}.
126      */
127     protected Intent getIntentForPosition(int position) {
128         PickAdapter.Item item = (PickAdapter.Item) mAdapter.getItem(position);
129         return item.getIntent(mBaseIntent);
130     }
131
132     /**
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.
138      */
139     protected List<PickAdapter.Item> getItems() {
140         PackageManager packageManager = getPackageManager();
141         List<PickAdapter.Item> items = new ArrayList<PickAdapter.Item>();
142         
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);
149         
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;
154                 
155                 try {
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) {
163                     // Ignore
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      */
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             
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());
211                 }
212                 return sResizer;
213             }
214             
215             CharSequence label;
216             Drawable icon;
217             String packageName;
218             String className;
219             Bundle extras;
220             
221             /**
222              * Create a list item from given label and icon.
223              */
224             Item(Context context, CharSequence label, Drawable icon) {
225                 this.label = label;
226                 this.icon = getResizer(context).createIconThumbnail(icon);
227             }
228
229             /**
230              * Create a list item and fill it with details from the given
231              * {@link ResolveInfo} object.
232              */
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;
237                 }
238
239                 icon = getResizer(context).createIconThumbnail(resolveInfo.loadIcon(pm));
240                 packageName = resolveInfo.activityInfo.applicationInfo.packageName;
241                 className = resolveInfo.activityInfo.name;
242             }
243
244             /**
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.
248              */
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);
256                     }
257                 } else {
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);
261                 }
262                 return intent;
263             }
264         }
265         
266         private final LayoutInflater mInflater;
267         private final List<Item> mItems;
268         
269         /**
270          * Create an adapter for the given items.
271          */
272         public PickAdapter(Context context, List<Item> items) {
273             mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
274             mItems = items;
275         }
276
277         /**
278          * {@inheritDoc}
279          */
280         public int getCount() {
281             return mItems.size();
282         }
283
284         /**
285          * {@inheritDoc}
286          */
287         public Object getItem(int position) {
288             return mItems.get(position);
289         }
290
291         /**
292          * {@inheritDoc}
293          */
294         public long getItemId(int position) {
295             return position;
296         }
297
298         /**
299          * {@inheritDoc}
300          */
301         public View getView(int position, View convertView, ViewGroup parent) {
302             if (convertView == null) {
303                 convertView = mInflater.inflate(R.layout.pick_item, parent, false);
304             }
305             
306             Item item = (Item) getItem(position);
307             TextView textView = (TextView) convertView;
308             textView.setText(item.label);
309             textView.setCompoundDrawablesWithIntrinsicBounds(item.icon, null, null, null);
310             
311             return convertView;
312         }
313     }
314         
315     /**
316      * Utility class to resize icons to match default icon size. Code is mostly
317      * borrowed from Launcher.
318      */
319     private static class IconResizer {
320         private final int mIconWidth;
321         private final int mIconHeight;
322
323         private final DisplayMetrics mMetrics;
324         private final Rect mOldBounds = new Rect();
325         private final Canvas mCanvas = new Canvas();
326         
327         public IconResizer(int width, int height, DisplayMetrics metrics) {
328             mCanvas.setDrawFilter(new PaintFlagsDrawFilter(Paint.DITHER_FLAG,
329                     Paint.FILTER_BITMAP_FLAG));
330
331             mMetrics = metrics;
332             mIconWidth = width;
333             mIconHeight = height; 
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          */
348         public Drawable createIconThumbnail(Drawable icon) {
349             int width = mIconWidth;
350             int height = mIconHeight;
351
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);
362                 }
363             }
364             int iconWidth = icon.getIntrinsicWidth();
365             int iconHeight = icon.getIntrinsicHeight();
366
367             if (iconWidth > 0 && iconHeight > 0) {
368                 if (width < iconWidth || height < iconHeight) {
369                     final float ratio = (float) iconWidth / iconHeight;
370
371                     if (iconWidth > iconHeight) {
372                         height = (int) (width / ratio);
373                     } else if (iconHeight > iconWidth) {
374                         width = (int) (height * ratio);
375                     }
376
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
386                     // old bounds
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);
391                     icon.draw(canvas);
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);
405                     icon.draw(canvas);
406                     icon.setBounds(mOldBounds);
407                     //noinspection deprecation
408                     icon = new BitmapDrawable(thumb);
409                     ((BitmapDrawable) icon).setTargetDensity(mMetrics);
410                 }
411             }
412
413             return icon;
414         }
415     }
416 }