Home | History | Annotate | Download | only in settings
      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.graphics.ColorFilter;
     20 import android.util.DisplayMetrics;
     21 import com.android.internal.app.AlertActivity;
     22 import com.android.internal.app.AlertController;
     23 
     24 import android.app.Activity;
     25 import android.content.Context;
     26 import android.content.DialogInterface;
     27 import android.content.Intent;
     28 import android.content.Intent.ShortcutIconResource;
     29 import android.content.pm.PackageManager;
     30 import android.content.pm.ResolveInfo;
     31 import android.content.pm.PackageManager.NameNotFoundException;
     32 import android.content.res.Resources;
     33 import android.graphics.Bitmap;
     34 import android.graphics.Canvas;
     35 import android.graphics.Paint;
     36 import android.graphics.PaintFlagsDrawFilter;
     37 import android.graphics.PixelFormat;
     38 import android.graphics.Rect;
     39 import android.graphics.drawable.BitmapDrawable;
     40 import android.graphics.drawable.Drawable;
     41 import android.graphics.drawable.PaintDrawable;
     42 import android.os.Bundle;
     43 import android.os.Parcelable;
     44 import android.view.LayoutInflater;
     45 import android.view.View;
     46 import android.view.ViewGroup;
     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
     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      */
    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      */
    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      */
    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      */
    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                     // Ignore
    165                 }
    166 
    167                 items.add(new PickAdapter.Item(this, label, icon));
    168             }
    169         }
    170 
    171         // Add any intent items if base was given
    172         if (mBaseIntent != null) {
    173             putIntentItems(mBaseIntent, items);
    174         }
    175 
    176         return items;
    177     }
    178 
    179     /**
    180      * Fill the given list with any activities matching the base {@link Intent}.
    181      */
    182     protected void putIntentItems(Intent baseIntent, List<PickAdapter.Item> items) {
    183         PackageManager packageManager = getPackageManager();
    184         List<ResolveInfo> list = packageManager.queryIntentActivities(baseIntent,
    185                 0 /* no flags */);
    186         Collections.sort(list, new ResolveInfo.DisplayNameComparator(packageManager));
    187 
    188         final int listSize = list.size();
    189         for (int i = 0; i < listSize; i++) {
    190             ResolveInfo resolveInfo = list.get(i);
    191             items.add(new PickAdapter.Item(this, packageManager, resolveInfo));
    192         }
    193     }
    194 
    195     /**
    196      * Adapter which shows the set of activities that can be performed for a
    197      * given {@link Intent}.
    198      */
    199     protected static class PickAdapter extends BaseAdapter {
    200 
    201         /**
    202          * Item that appears in a {@link PickAdapter} list.
    203          */
    204         public static class Item implements AppWidgetLoader.LabelledItem {
    205             protected static IconResizer sResizer;
    206 
    207             protected IconResizer getResizer(Context context) {
    208                 if (sResizer == null) {
    209                     final Resources resources = context.getResources();
    210                     int size = (int) resources.getDimension(android.R.dimen.app_icon_size);
    211                     sResizer = new IconResizer(size, size, resources.getDisplayMetrics());
    212                 }
    213                 return sResizer;
    214             }
    215 
    216             CharSequence label;
    217             Drawable icon;
    218             String packageName;
    219             String className;
    220             Bundle extras;
    221 
    222             /**
    223              * Create a list item from given label and icon.
    224              */
    225             Item(Context context, CharSequence label, Drawable icon) {
    226                 this.label = label;
    227                 this.icon = getResizer(context).createIconThumbnail(icon);
    228             }
    229 
    230             /**
    231              * Create a list item and fill it with details from the given
    232              * {@link ResolveInfo} object.
    233              */
    234             Item(Context context, PackageManager pm, ResolveInfo resolveInfo) {
    235                 label = resolveInfo.loadLabel(pm);
    236                 if (label == null && resolveInfo.activityInfo != null) {
    237                     label = resolveInfo.activityInfo.name;
    238                 }
    239 
    240                 icon = getResizer(context).createIconThumbnail(resolveInfo.loadIcon(pm));
    241                 packageName = resolveInfo.activityInfo.applicationInfo.packageName;
    242                 className = resolveInfo.activityInfo.name;
    243             }
    244 
    245             /**
    246              * Build the {@link Intent} described by this item. If this item
    247              * can't create a valid {@link android.content.ComponentName}, it will return
    248              * {@link Intent#ACTION_CREATE_SHORTCUT} filled with the item label.
    249              */
    250             Intent getIntent(Intent baseIntent) {
    251                 Intent intent = new Intent(baseIntent);
    252                 if (packageName != null && className != null) {
    253                     // Valid package and class, so fill details as normal intent
    254                     intent.setClassName(packageName, className);
    255                     if (extras != null) {
    256                         intent.putExtras(extras);
    257                     }
    258                 } else {
    259                     // No valid package or class, so treat as shortcut with label
    260                     intent.setAction(Intent.ACTION_CREATE_SHORTCUT);
    261                     intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, label);
    262                 }
    263                 return intent;
    264             }
    265 
    266             public CharSequence getLabel() {
    267                 return label;
    268             }
    269         }
    270 
    271         private final LayoutInflater mInflater;
    272         private final List<Item> mItems;
    273 
    274         /**
    275          * Create an adapter for the given items.
    276          */
    277         public PickAdapter(Context context, List<Item> items) {
    278             mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    279             mItems = items;
    280         }
    281 
    282         /**
    283          * {@inheritDoc}
    284          */
    285         public int getCount() {
    286             return mItems.size();
    287         }
    288 
    289         /**
    290          * {@inheritDoc}
    291          */
    292         public Object getItem(int position) {
    293             return mItems.get(position);
    294         }
    295 
    296         /**
    297          * {@inheritDoc}
    298          */
    299         public long getItemId(int position) {
    300             return position;
    301         }
    302 
    303         /**
    304          * {@inheritDoc}
    305          */
    306         public View getView(int position, View convertView, ViewGroup parent) {
    307             if (convertView == null) {
    308                 convertView = mInflater.inflate(R.layout.pick_item, parent, false);
    309             }
    310 
    311             Item item = (Item) getItem(position);
    312             TextView textView = (TextView) convertView;
    313             textView.setText(item.label);
    314             textView.setCompoundDrawablesWithIntrinsicBounds(item.icon, null, null, null);
    315 
    316             return convertView;
    317         }
    318     }
    319 
    320     /**
    321      * Utility class to resize icons to match default icon size. Code is mostly
    322      * borrowed from Launcher.
    323      */
    324     private static class IconResizer {
    325         private final int mIconWidth;
    326         private final int mIconHeight;
    327 
    328         private final DisplayMetrics mMetrics;
    329         private final Rect mOldBounds = new Rect();
    330         private final Canvas mCanvas = new Canvas();
    331 
    332         public IconResizer(int width, int height, DisplayMetrics metrics) {
    333             mCanvas.setDrawFilter(new PaintFlagsDrawFilter(Paint.DITHER_FLAG,
    334                     Paint.FILTER_BITMAP_FLAG));
    335 
    336             mMetrics = metrics;
    337             mIconWidth = width;
    338             mIconHeight = height;
    339         }
    340 
    341         /**
    342          * Returns a Drawable representing the thumbnail of the specified Drawable.
    343          * The size of the thumbnail is defined by the dimension
    344          * android.R.dimen.launcher_application_icon_size.
    345          *
    346          * This method is not thread-safe and should be invoked on the UI thread only.
    347          *
    348          * @param icon The icon to get a thumbnail of.
    349          *
    350          * @return A thumbnail for the specified icon or the icon itself if the
    351          *         thumbnail could not be created.
    352          */
    353         public Drawable createIconThumbnail(Drawable icon) {
    354             int width = mIconWidth;
    355             int height = mIconHeight;
    356 
    357             if (icon == null) {
    358                 return new EmptyDrawable(width, height);
    359             }
    360 
    361             try {
    362                 if (icon instanceof PaintDrawable) {
    363                     PaintDrawable painter = (PaintDrawable) icon;
    364                     painter.setIntrinsicWidth(width);
    365                     painter.setIntrinsicHeight(height);
    366                 } else if (icon instanceof BitmapDrawable) {
    367                     // Ensure the bitmap has a density.
    368                     BitmapDrawable bitmapDrawable = (BitmapDrawable) icon;
    369                     Bitmap bitmap = bitmapDrawable.getBitmap();
    370                     if (bitmap.getDensity() == Bitmap.DENSITY_NONE) {
    371                         bitmapDrawable.setTargetDensity(mMetrics);
    372                     }
    373                 }
    374                 int iconWidth = icon.getIntrinsicWidth();
    375                 int iconHeight = icon.getIntrinsicHeight();
    376 
    377                 if (iconWidth > 0 && iconHeight > 0) {
    378                     if (width < iconWidth || height < iconHeight) {
    379                         final float ratio = (float) iconWidth / iconHeight;
    380 
    381                         if (iconWidth > iconHeight) {
    382                             height = (int) (width / ratio);
    383                         } else if (iconHeight > iconWidth) {
    384                             width = (int) (height * ratio);
    385                         }
    386 
    387                         final Bitmap.Config c = icon.getOpacity() != PixelFormat.OPAQUE ?
    388                                     Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565;
    389                         final Bitmap thumb = Bitmap.createBitmap(mIconWidth, mIconHeight, c);
    390                         final Canvas canvas = mCanvas;
    391                         canvas.setBitmap(thumb);
    392                         // Copy the old bounds to restore them later
    393                         // If we were to do oldBounds = icon.getBounds(),
    394                         // the call to setBounds() that follows would
    395                         // change the same instance and we would lose the
    396                         // old bounds
    397                         mOldBounds.set(icon.getBounds());
    398                         final int x = (mIconWidth - width) / 2;
    399                         final int y = (mIconHeight - height) / 2;
    400                         icon.setBounds(x, y, x + width, y + height);
    401                         icon.draw(canvas);
    402                         icon.setBounds(mOldBounds);
    403                         //noinspection deprecation
    404                         icon = new BitmapDrawable(thumb);
    405                         ((BitmapDrawable) icon).setTargetDensity(mMetrics);
    406                         canvas.setBitmap(null);
    407                     } else if (iconWidth < width && iconHeight < height) {
    408                         final Bitmap.Config c = Bitmap.Config.ARGB_8888;
    409                         final Bitmap thumb = Bitmap.createBitmap(mIconWidth, mIconHeight, c);
    410                         final Canvas canvas = mCanvas;
    411                         canvas.setBitmap(thumb);
    412                         mOldBounds.set(icon.getBounds());
    413                         final int x = (width - iconWidth) / 2;
    414                         final int y = (height - iconHeight) / 2;
    415                         icon.setBounds(x, y, x + iconWidth, y + iconHeight);
    416                         icon.draw(canvas);
    417                         icon.setBounds(mOldBounds);
    418                         //noinspection deprecation
    419                         icon = new BitmapDrawable(thumb);
    420                         ((BitmapDrawable) icon).setTargetDensity(mMetrics);
    421                         canvas.setBitmap(null);
    422                     }
    423                 }
    424 
    425             } catch (Throwable t) {
    426                 icon = new EmptyDrawable(width, height);
    427             }
    428 
    429             return icon;
    430         }
    431     }
    432 
    433     private static class EmptyDrawable extends Drawable {
    434         private final int mWidth;
    435         private final int mHeight;
    436 
    437         EmptyDrawable(int width, int height) {
    438             mWidth = width;
    439             mHeight = height;
    440         }
    441 
    442         @Override
    443         public int getIntrinsicWidth() {
    444             return mWidth;
    445         }
    446 
    447         @Override
    448         public int getIntrinsicHeight() {
    449             return mHeight;
    450         }
    451 
    452         @Override
    453         public int getMinimumWidth() {
    454             return mWidth;
    455         }
    456 
    457         @Override
    458         public int getMinimumHeight() {
    459             return mHeight;
    460         }
    461 
    462         @Override
    463         public void draw(Canvas canvas) {
    464         }
    465 
    466         @Override
    467         public void setAlpha(int alpha) {
    468         }
    469 
    470         @Override
    471         public void setColorFilter(ColorFilter cf) {
    472         }
    473 
    474         @Override
    475         public int getOpacity() {
    476             return PixelFormat.TRANSLUCENT;
    477         }
    478     }
    479 }
    480