Home | History | Annotate | Download | only in app
      1 /*
      2  * Copyright (C) 2007 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 android.app;
     18 
     19 import android.content.Context;
     20 import android.content.Intent;
     21 import android.content.pm.ComponentInfo;
     22 import android.content.pm.PackageManager;
     23 import android.content.pm.ResolveInfo;
     24 import android.content.res.Resources;
     25 import android.graphics.Bitmap;
     26 import android.graphics.Canvas;
     27 import android.graphics.Paint;
     28 import android.graphics.PaintFlagsDrawFilter;
     29 import android.graphics.PixelFormat;
     30 import android.graphics.Rect;
     31 import android.graphics.drawable.BitmapDrawable;
     32 import android.graphics.drawable.Drawable;
     33 import android.graphics.drawable.PaintDrawable;
     34 import android.os.Bundle;
     35 import android.view.LayoutInflater;
     36 import android.view.View;
     37 import android.view.ViewGroup;
     38 import android.view.Window;
     39 import android.view.View.OnClickListener;
     40 import android.widget.BaseAdapter;
     41 import android.widget.Button;
     42 import android.widget.Filter;
     43 import android.widget.Filterable;
     44 import android.widget.ListView;
     45 import android.widget.TextView;
     46 
     47 import java.util.ArrayList;
     48 import java.util.Collections;
     49 import java.util.List;
     50 
     51 
     52 /**
     53  * Displays a list of all activities which can be performed
     54  * for a given intent. Launches when clicked.
     55  *
     56  */
     57 public abstract class LauncherActivity extends ListActivity {
     58     Intent mIntent;
     59     PackageManager mPackageManager;
     60     IconResizer mIconResizer;
     61 
     62     /**
     63      * An item in the list
     64      */
     65     public static class ListItem {
     66         public ResolveInfo resolveInfo;
     67         public CharSequence label;
     68         public Drawable icon;
     69         public String packageName;
     70         public String className;
     71         public Bundle extras;
     72 
     73         ListItem(PackageManager pm, ResolveInfo resolveInfo, IconResizer resizer) {
     74             this.resolveInfo = resolveInfo;
     75             label = resolveInfo.loadLabel(pm);
     76             ComponentInfo ci = resolveInfo.activityInfo;
     77             if (ci == null) ci = resolveInfo.serviceInfo;
     78             if (label == null && ci != null) {
     79                 label = resolveInfo.activityInfo.name;
     80             }
     81 
     82             if (resizer != null) {
     83                 icon = resizer.createIconThumbnail(resolveInfo.loadIcon(pm));
     84             }
     85             packageName = ci.applicationInfo.packageName;
     86             className = ci.name;
     87         }
     88 
     89         public ListItem() {
     90         }
     91     }
     92 
     93     /**
     94      * Adapter which shows the set of activities that can be performed for a given intent.
     95      */
     96     private class ActivityAdapter extends BaseAdapter implements Filterable {
     97         private final Object lock = new Object();
     98         private ArrayList<ListItem> mOriginalValues;
     99 
    100         protected final IconResizer mIconResizer;
    101         protected final LayoutInflater mInflater;
    102 
    103         protected List<ListItem> mActivitiesList;
    104 
    105         private Filter mFilter;
    106         private final boolean mShowIcons;
    107 
    108         public ActivityAdapter(IconResizer resizer) {
    109             mIconResizer = resizer;
    110             mInflater = (LayoutInflater) LauncherActivity.this.getSystemService(
    111                     Context.LAYOUT_INFLATER_SERVICE);
    112             mShowIcons = onEvaluateShowIcons();
    113             mActivitiesList = makeListItems();
    114         }
    115 
    116         public Intent intentForPosition(int position) {
    117             if (mActivitiesList == null) {
    118                 return null;
    119             }
    120 
    121             Intent intent = new Intent(mIntent);
    122             ListItem item = mActivitiesList.get(position);
    123             intent.setClassName(item.packageName, item.className);
    124             if (item.extras != null) {
    125                 intent.putExtras(item.extras);
    126             }
    127             return intent;
    128         }
    129 
    130         public ListItem itemForPosition(int position) {
    131             if (mActivitiesList == null) {
    132                 return null;
    133             }
    134 
    135             return mActivitiesList.get(position);
    136         }
    137 
    138         public int getCount() {
    139             return mActivitiesList != null ? mActivitiesList.size() : 0;
    140         }
    141 
    142         public Object getItem(int position) {
    143             return position;
    144         }
    145 
    146         public long getItemId(int position) {
    147             return position;
    148         }
    149 
    150         public View getView(int position, View convertView, ViewGroup parent) {
    151             View view;
    152             if (convertView == null) {
    153                 view = mInflater.inflate(
    154                         com.android.internal.R.layout.activity_list_item_2, parent, false);
    155             } else {
    156                 view = convertView;
    157             }
    158             bindView(view, mActivitiesList.get(position));
    159             return view;
    160         }
    161 
    162         private void bindView(View view, ListItem item) {
    163             TextView text = (TextView) view;
    164             text.setText(item.label);
    165             if (mShowIcons) {
    166                 if (item.icon == null) {
    167                     item.icon = mIconResizer.createIconThumbnail(item.resolveInfo.loadIcon(getPackageManager()));
    168                 }
    169                 text.setCompoundDrawablesWithIntrinsicBounds(item.icon, null, null, null);
    170             }
    171         }
    172 
    173         public Filter getFilter() {
    174             if (mFilter == null) {
    175                 mFilter = new ArrayFilter();
    176             }
    177             return mFilter;
    178         }
    179 
    180         /**
    181          * An array filters constrains the content of the array adapter with a prefix. Each
    182          * item that does not start with the supplied prefix is removed from the list.
    183          */
    184         private class ArrayFilter extends Filter {
    185             @Override
    186             protected FilterResults performFiltering(CharSequence prefix) {
    187                 FilterResults results = new FilterResults();
    188 
    189                 if (mOriginalValues == null) {
    190                     synchronized (lock) {
    191                         mOriginalValues = new ArrayList<ListItem>(mActivitiesList);
    192                     }
    193                 }
    194 
    195                 if (prefix == null || prefix.length() == 0) {
    196                     synchronized (lock) {
    197                         ArrayList<ListItem> list = new ArrayList<ListItem>(mOriginalValues);
    198                         results.values = list;
    199                         results.count = list.size();
    200                     }
    201                 } else {
    202                     final String prefixString = prefix.toString().toLowerCase();
    203 
    204                     ArrayList<ListItem> values = mOriginalValues;
    205                     int count = values.size();
    206 
    207                     ArrayList<ListItem> newValues = new ArrayList<ListItem>(count);
    208 
    209                     for (int i = 0; i < count; i++) {
    210                         ListItem item = values.get(i);
    211 
    212                         String[] words = item.label.toString().toLowerCase().split(" ");
    213                         int wordCount = words.length;
    214 
    215                         for (int k = 0; k < wordCount; k++) {
    216                             final String word = words[k];
    217 
    218                             if (word.startsWith(prefixString)) {
    219                                 newValues.add(item);
    220                                 break;
    221                             }
    222                         }
    223                     }
    224 
    225                     results.values = newValues;
    226                     results.count = newValues.size();
    227                 }
    228 
    229                 return results;
    230             }
    231 
    232             @Override
    233             protected void publishResults(CharSequence constraint, FilterResults results) {
    234                 //noinspection unchecked
    235                 mActivitiesList = (List<ListItem>) results.values;
    236                 if (results.count > 0) {
    237                     notifyDataSetChanged();
    238                 } else {
    239                     notifyDataSetInvalidated();
    240                 }
    241             }
    242         }
    243     }
    244 
    245     /**
    246      * Utility class to resize icons to match default icon size.
    247      */
    248     public class IconResizer {
    249         // Code is borrowed from com.android.launcher.Utilities.
    250         private int mIconWidth = -1;
    251         private int mIconHeight = -1;
    252 
    253         private final Rect mOldBounds = new Rect();
    254         private Canvas mCanvas = new Canvas();
    255 
    256         public IconResizer() {
    257             mCanvas.setDrawFilter(new PaintFlagsDrawFilter(Paint.DITHER_FLAG,
    258                     Paint.FILTER_BITMAP_FLAG));
    259 
    260             final Resources resources = LauncherActivity.this.getResources();
    261             mIconWidth = mIconHeight = (int) resources.getDimension(
    262                     android.R.dimen.app_icon_size);
    263         }
    264 
    265         /**
    266          * Returns a Drawable representing the thumbnail of the specified Drawable.
    267          * The size of the thumbnail is defined by the dimension
    268          * android.R.dimen.launcher_application_icon_size.
    269          *
    270          * This method is not thread-safe and should be invoked on the UI thread only.
    271          *
    272          * @param icon The icon to get a thumbnail of.
    273          *
    274          * @return A thumbnail for the specified icon or the icon itself if the
    275          *         thumbnail could not be created.
    276          */
    277         public Drawable createIconThumbnail(Drawable icon) {
    278             int width = mIconWidth;
    279             int height = mIconHeight;
    280 
    281             final int iconWidth = icon.getIntrinsicWidth();
    282             final int iconHeight = icon.getIntrinsicHeight();
    283 
    284             if (icon instanceof PaintDrawable) {
    285                 PaintDrawable painter = (PaintDrawable) icon;
    286                 painter.setIntrinsicWidth(width);
    287                 painter.setIntrinsicHeight(height);
    288             }
    289 
    290             if (width > 0 && height > 0) {
    291                 if (width < iconWidth || height < iconHeight) {
    292                     final float ratio = (float) iconWidth / iconHeight;
    293 
    294                     if (iconWidth > iconHeight) {
    295                         height = (int) (width / ratio);
    296                     } else if (iconHeight > iconWidth) {
    297                         width = (int) (height * ratio);
    298                     }
    299 
    300                     final Bitmap.Config c = icon.getOpacity() != PixelFormat.OPAQUE ?
    301                                 Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565;
    302                     final Bitmap thumb = Bitmap.createBitmap(mIconWidth, mIconHeight, c);
    303                     final Canvas canvas = mCanvas;
    304                     canvas.setBitmap(thumb);
    305                     // Copy the old bounds to restore them later
    306                     // If we were to do oldBounds = icon.getBounds(),
    307                     // the call to setBounds() that follows would
    308                     // change the same instance and we would lose the
    309                     // old bounds
    310                     mOldBounds.set(icon.getBounds());
    311                     final int x = (mIconWidth - width) / 2;
    312                     final int y = (mIconHeight - height) / 2;
    313                     icon.setBounds(x, y, x + width, y + height);
    314                     icon.draw(canvas);
    315                     icon.setBounds(mOldBounds);
    316                     icon = new BitmapDrawable(getResources(), thumb);
    317                     canvas.setBitmap(null);
    318                 } else if (iconWidth < width && iconHeight < height) {
    319                     final Bitmap.Config c = Bitmap.Config.ARGB_8888;
    320                     final Bitmap thumb = Bitmap.createBitmap(mIconWidth, mIconHeight, c);
    321                     final Canvas canvas = mCanvas;
    322                     canvas.setBitmap(thumb);
    323                     mOldBounds.set(icon.getBounds());
    324                     final int x = (width - iconWidth) / 2;
    325                     final int y = (height - iconHeight) / 2;
    326                     icon.setBounds(x, y, x + iconWidth, y + iconHeight);
    327                     icon.draw(canvas);
    328                     icon.setBounds(mOldBounds);
    329                     icon = new BitmapDrawable(getResources(), thumb);
    330                     canvas.setBitmap(null);
    331                 }
    332             }
    333 
    334             return icon;
    335         }
    336     }
    337 
    338     @Override
    339     protected void onCreate(Bundle icicle) {
    340         super.onCreate(icicle);
    341 
    342         mPackageManager = getPackageManager();
    343 
    344         requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
    345         setProgressBarIndeterminateVisibility(true);
    346         onSetContentView();
    347 
    348         mIconResizer = new IconResizer();
    349 
    350         mIntent = new Intent(getTargetIntent());
    351         mIntent.setComponent(null);
    352         mAdapter = new ActivityAdapter(mIconResizer);
    353 
    354         setListAdapter(mAdapter);
    355         getListView().setTextFilterEnabled(true);
    356 
    357         updateAlertTitle();
    358         updateButtonText();
    359 
    360         setProgressBarIndeterminateVisibility(false);
    361     }
    362 
    363     private void updateAlertTitle() {
    364         TextView alertTitle = (TextView) findViewById(com.android.internal.R.id.alertTitle);
    365         if (alertTitle != null) {
    366             alertTitle.setText(getTitle());
    367         }
    368     }
    369 
    370     private void updateButtonText() {
    371         Button cancelButton = (Button) findViewById(com.android.internal.R.id.button1);
    372         if (cancelButton != null) {
    373             cancelButton.setOnClickListener(new OnClickListener() {
    374                 public void onClick(View v) {
    375                     finish();
    376                 }
    377             });
    378         }
    379     }
    380 
    381     @Override
    382     public void setTitle(CharSequence title) {
    383         super.setTitle(title);
    384         updateAlertTitle();
    385     }
    386 
    387     @Override
    388     public void setTitle(int titleId) {
    389         super.setTitle(titleId);
    390         updateAlertTitle();
    391     }
    392 
    393     /**
    394      * Override to call setContentView() with your own content view to
    395      * customize the list layout.
    396      */
    397     protected void onSetContentView() {
    398         setContentView(com.android.internal.R.layout.activity_list);
    399     }
    400 
    401     @Override
    402     protected void onListItemClick(ListView l, View v, int position, long id) {
    403         Intent intent = intentForPosition(position);
    404         startActivity(intent);
    405     }
    406 
    407     /**
    408      * Return the actual Intent for a specific position in our
    409      * {@link android.widget.ListView}.
    410      * @param position The item whose Intent to return
    411      */
    412     protected Intent intentForPosition(int position) {
    413         ActivityAdapter adapter = (ActivityAdapter) mAdapter;
    414         return adapter.intentForPosition(position);
    415     }
    416 
    417     /**
    418      * Return the {@link ListItem} for a specific position in our
    419      * {@link android.widget.ListView}.
    420      * @param position The item to return
    421      */
    422     protected ListItem itemForPosition(int position) {
    423         ActivityAdapter adapter = (ActivityAdapter) mAdapter;
    424         return adapter.itemForPosition(position);
    425     }
    426 
    427     /**
    428      * Get the base intent to use when running
    429      * {@link PackageManager#queryIntentActivities(Intent, int)}.
    430      */
    431     protected Intent getTargetIntent() {
    432         return new Intent();
    433     }
    434 
    435     /**
    436      * Perform query on package manager for list items.  The default
    437      * implementation queries for activities.
    438      */
    439     protected List<ResolveInfo> onQueryPackageManager(Intent queryIntent) {
    440         return mPackageManager.queryIntentActivities(queryIntent, /* no flags */ 0);
    441     }
    442 
    443     /**
    444      * Perform the query to determine which results to show and return a list of them.
    445      */
    446     public List<ListItem> makeListItems() {
    447         // Load all matching activities and sort correctly
    448         List<ResolveInfo> list = onQueryPackageManager(mIntent);
    449         Collections.sort(list, new ResolveInfo.DisplayNameComparator(mPackageManager));
    450 
    451         ArrayList<ListItem> result = new ArrayList<ListItem>(list.size());
    452         int listSize = list.size();
    453         for (int i = 0; i < listSize; i++) {
    454             ResolveInfo resolveInfo = list.get(i);
    455             result.add(new ListItem(mPackageManager, resolveInfo, null));
    456         }
    457 
    458         return result;
    459     }
    460 
    461     /**
    462      * Whether or not to show icons in the list
    463      * @hide keeping this private for now, since only Settings needs it
    464      * @return true to show icons beside the activity names, false otherwise
    465      */
    466     protected boolean onEvaluateShowIcons() {
    467         return true;
    468     }
    469 }
    470