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         if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_WATCH)) {
    345             requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
    346             setProgressBarIndeterminateVisibility(true);
    347         }
    348         onSetContentView();
    349 
    350         mIconResizer = new IconResizer();
    351 
    352         mIntent = new Intent(getTargetIntent());
    353         mIntent.setComponent(null);
    354         mAdapter = new ActivityAdapter(mIconResizer);
    355 
    356         setListAdapter(mAdapter);
    357         getListView().setTextFilterEnabled(true);
    358 
    359         updateAlertTitle();
    360         updateButtonText();
    361 
    362         if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_WATCH)) {
    363             setProgressBarIndeterminateVisibility(false);
    364         }
    365     }
    366 
    367     private void updateAlertTitle() {
    368         TextView alertTitle = (TextView) findViewById(com.android.internal.R.id.alertTitle);
    369         if (alertTitle != null) {
    370             alertTitle.setText(getTitle());
    371         }
    372     }
    373 
    374     private void updateButtonText() {
    375         Button cancelButton = (Button) findViewById(com.android.internal.R.id.button1);
    376         if (cancelButton != null) {
    377             cancelButton.setOnClickListener(new OnClickListener() {
    378                 public void onClick(View v) {
    379                     finish();
    380                 }
    381             });
    382         }
    383     }
    384 
    385     @Override
    386     public void setTitle(CharSequence title) {
    387         super.setTitle(title);
    388         updateAlertTitle();
    389     }
    390 
    391     @Override
    392     public void setTitle(int titleId) {
    393         super.setTitle(titleId);
    394         updateAlertTitle();
    395     }
    396 
    397     /**
    398      * Override to call setContentView() with your own content view to
    399      * customize the list layout.
    400      */
    401     protected void onSetContentView() {
    402         setContentView(com.android.internal.R.layout.activity_list);
    403     }
    404 
    405     @Override
    406     protected void onListItemClick(ListView l, View v, int position, long id) {
    407         Intent intent = intentForPosition(position);
    408         startActivity(intent);
    409     }
    410 
    411     /**
    412      * Return the actual Intent for a specific position in our
    413      * {@link android.widget.ListView}.
    414      * @param position The item whose Intent to return
    415      */
    416     protected Intent intentForPosition(int position) {
    417         ActivityAdapter adapter = (ActivityAdapter) mAdapter;
    418         return adapter.intentForPosition(position);
    419     }
    420 
    421     /**
    422      * Return the {@link ListItem} for a specific position in our
    423      * {@link android.widget.ListView}.
    424      * @param position The item to return
    425      */
    426     protected ListItem itemForPosition(int position) {
    427         ActivityAdapter adapter = (ActivityAdapter) mAdapter;
    428         return adapter.itemForPosition(position);
    429     }
    430 
    431     /**
    432      * Get the base intent to use when running
    433      * {@link PackageManager#queryIntentActivities(Intent, int)}.
    434      */
    435     protected Intent getTargetIntent() {
    436         return new Intent();
    437     }
    438 
    439     /**
    440      * Perform query on package manager for list items.  The default
    441      * implementation queries for activities.
    442      */
    443     protected List<ResolveInfo> onQueryPackageManager(Intent queryIntent) {
    444         return mPackageManager.queryIntentActivities(queryIntent, /* no flags */ 0);
    445     }
    446 
    447     /**
    448      * @hide
    449      */
    450     protected void onSortResultList(List<ResolveInfo> results) {
    451         Collections.sort(results, new ResolveInfo.DisplayNameComparator(mPackageManager));
    452     }
    453 
    454     /**
    455      * Perform the query to determine which results to show and return a list of them.
    456      */
    457     public List<ListItem> makeListItems() {
    458         // Load all matching activities and sort correctly
    459         List<ResolveInfo> list = onQueryPackageManager(mIntent);
    460         onSortResultList(list);
    461 
    462         ArrayList<ListItem> result = new ArrayList<ListItem>(list.size());
    463         int listSize = list.size();
    464         for (int i = 0; i < listSize; i++) {
    465             ResolveInfo resolveInfo = list.get(i);
    466             result.add(new ListItem(mPackageManager, resolveInfo, null));
    467         }
    468 
    469         return result;
    470     }
    471 
    472     /**
    473      * Whether or not to show icons in the list
    474      * @hide keeping this private for now, since only Settings needs it
    475      * @return true to show icons beside the activity names, false otherwise
    476      */
    477     protected boolean onEvaluateShowIcons() {
    478         return true;
    479     }
    480 }
    481