| /* |
| * Copyright (C) 2007 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package android.app; |
| |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.pm.ComponentInfo; |
| import android.content.pm.PackageManager; |
| import android.content.pm.ResolveInfo; |
| import android.content.res.Resources; |
| import android.graphics.Bitmap; |
| import android.graphics.Canvas; |
| import android.graphics.Paint; |
| import android.graphics.PaintFlagsDrawFilter; |
| import android.graphics.PixelFormat; |
| import android.graphics.Rect; |
| import android.graphics.drawable.BitmapDrawable; |
| import android.graphics.drawable.Drawable; |
| import android.graphics.drawable.PaintDrawable; |
| import android.os.Bundle; |
| import android.view.LayoutInflater; |
| import android.view.View; |
| import android.view.ViewGroup; |
| import android.view.Window; |
| import android.view.View.OnClickListener; |
| import android.widget.BaseAdapter; |
| import android.widget.Button; |
| import android.widget.Filter; |
| import android.widget.Filterable; |
| import android.widget.ListView; |
| import android.widget.TextView; |
| |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.List; |
| |
| |
| /** |
| * Displays a list of all activities which can be performed |
| * for a given intent. Launches when clicked. |
| * |
| */ |
| public abstract class LauncherActivity extends ListActivity { |
| Intent mIntent; |
| PackageManager mPackageManager; |
| IconResizer mIconResizer; |
| |
| /** |
| * An item in the list |
| */ |
| public static class ListItem { |
| public ResolveInfo resolveInfo; |
| public CharSequence label; |
| public Drawable icon; |
| public String packageName; |
| public String className; |
| public Bundle extras; |
| |
| ListItem(PackageManager pm, ResolveInfo resolveInfo, IconResizer resizer) { |
| this.resolveInfo = resolveInfo; |
| label = resolveInfo.loadLabel(pm); |
| ComponentInfo ci = resolveInfo.activityInfo; |
| if (ci == null) ci = resolveInfo.serviceInfo; |
| if (label == null && ci != null) { |
| label = resolveInfo.activityInfo.name; |
| } |
| |
| if (resizer != null) { |
| icon = resizer.createIconThumbnail(resolveInfo.loadIcon(pm)); |
| } |
| packageName = ci.applicationInfo.packageName; |
| className = ci.name; |
| } |
| |
| public ListItem() { |
| } |
| } |
| |
| /** |
| * Adapter which shows the set of activities that can be performed for a given intent. |
| */ |
| private class ActivityAdapter extends BaseAdapter implements Filterable { |
| private final Object lock = new Object(); |
| private ArrayList<ListItem> mOriginalValues; |
| |
| protected final IconResizer mIconResizer; |
| protected final LayoutInflater mInflater; |
| |
| protected List<ListItem> mActivitiesList; |
| |
| private Filter mFilter; |
| private final boolean mShowIcons; |
| |
| public ActivityAdapter(IconResizer resizer) { |
| mIconResizer = resizer; |
| mInflater = (LayoutInflater) LauncherActivity.this.getSystemService( |
| Context.LAYOUT_INFLATER_SERVICE); |
| mShowIcons = onEvaluateShowIcons(); |
| mActivitiesList = makeListItems(); |
| } |
| |
| public Intent intentForPosition(int position) { |
| if (mActivitiesList == null) { |
| return null; |
| } |
| |
| Intent intent = new Intent(mIntent); |
| ListItem item = mActivitiesList.get(position); |
| intent.setClassName(item.packageName, item.className); |
| if (item.extras != null) { |
| intent.putExtras(item.extras); |
| } |
| return intent; |
| } |
| |
| public ListItem itemForPosition(int position) { |
| if (mActivitiesList == null) { |
| return null; |
| } |
| |
| return mActivitiesList.get(position); |
| } |
| |
| public int getCount() { |
| return mActivitiesList != null ? mActivitiesList.size() : 0; |
| } |
| |
| public Object getItem(int position) { |
| return position; |
| } |
| |
| public long getItemId(int position) { |
| return position; |
| } |
| |
| public View getView(int position, View convertView, ViewGroup parent) { |
| View view; |
| if (convertView == null) { |
| view = mInflater.inflate( |
| com.android.internal.R.layout.activity_list_item_2, parent, false); |
| } else { |
| view = convertView; |
| } |
| bindView(view, mActivitiesList.get(position)); |
| return view; |
| } |
| |
| private void bindView(View view, ListItem item) { |
| TextView text = (TextView) view; |
| text.setText(item.label); |
| if (mShowIcons) { |
| if (item.icon == null) { |
| item.icon = mIconResizer.createIconThumbnail(item.resolveInfo.loadIcon(getPackageManager())); |
| } |
| text.setCompoundDrawablesWithIntrinsicBounds(item.icon, null, null, null); |
| } |
| } |
| |
| public Filter getFilter() { |
| if (mFilter == null) { |
| mFilter = new ArrayFilter(); |
| } |
| return mFilter; |
| } |
| |
| /** |
| * An array filters constrains the content of the array adapter with a prefix. Each |
| * item that does not start with the supplied prefix is removed from the list. |
| */ |
| private class ArrayFilter extends Filter { |
| @Override |
| protected FilterResults performFiltering(CharSequence prefix) { |
| FilterResults results = new FilterResults(); |
| |
| if (mOriginalValues == null) { |
| synchronized (lock) { |
| mOriginalValues = new ArrayList<ListItem>(mActivitiesList); |
| } |
| } |
| |
| if (prefix == null || prefix.length() == 0) { |
| synchronized (lock) { |
| ArrayList<ListItem> list = new ArrayList<ListItem>(mOriginalValues); |
| results.values = list; |
| results.count = list.size(); |
| } |
| } else { |
| final String prefixString = prefix.toString().toLowerCase(); |
| |
| ArrayList<ListItem> values = mOriginalValues; |
| int count = values.size(); |
| |
| ArrayList<ListItem> newValues = new ArrayList<ListItem>(count); |
| |
| for (int i = 0; i < count; i++) { |
| ListItem item = values.get(i); |
| |
| String[] words = item.label.toString().toLowerCase().split(" "); |
| int wordCount = words.length; |
| |
| for (int k = 0; k < wordCount; k++) { |
| final String word = words[k]; |
| |
| if (word.startsWith(prefixString)) { |
| newValues.add(item); |
| break; |
| } |
| } |
| } |
| |
| results.values = newValues; |
| results.count = newValues.size(); |
| } |
| |
| return results; |
| } |
| |
| @Override |
| protected void publishResults(CharSequence constraint, FilterResults results) { |
| //noinspection unchecked |
| mActivitiesList = (List<ListItem>) results.values; |
| if (results.count > 0) { |
| notifyDataSetChanged(); |
| } else { |
| notifyDataSetInvalidated(); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Utility class to resize icons to match default icon size. |
| */ |
| public class IconResizer { |
| // Code is borrowed from com.android.launcher.Utilities. |
| private int mIconWidth = -1; |
| private int mIconHeight = -1; |
| |
| private final Rect mOldBounds = new Rect(); |
| private Canvas mCanvas = new Canvas(); |
| |
| public IconResizer() { |
| mCanvas.setDrawFilter(new PaintFlagsDrawFilter(Paint.DITHER_FLAG, |
| Paint.FILTER_BITMAP_FLAG)); |
| |
| final Resources resources = LauncherActivity.this.getResources(); |
| mIconWidth = mIconHeight = (int) resources.getDimension( |
| android.R.dimen.app_icon_size); |
| } |
| |
| /** |
| * Returns a Drawable representing the thumbnail of the specified Drawable. |
| * The size of the thumbnail is defined by the dimension |
| * android.R.dimen.launcher_application_icon_size. |
| * |
| * This method is not thread-safe and should be invoked on the UI thread only. |
| * |
| * @param icon The icon to get a thumbnail of. |
| * |
| * @return A thumbnail for the specified icon or the icon itself if the |
| * thumbnail could not be created. |
| */ |
| public Drawable createIconThumbnail(Drawable icon) { |
| int width = mIconWidth; |
| int height = mIconHeight; |
| |
| final int iconWidth = icon.getIntrinsicWidth(); |
| final int iconHeight = icon.getIntrinsicHeight(); |
| |
| if (icon instanceof PaintDrawable) { |
| PaintDrawable painter = (PaintDrawable) icon; |
| painter.setIntrinsicWidth(width); |
| painter.setIntrinsicHeight(height); |
| } |
| |
| if (width > 0 && height > 0) { |
| if (width < iconWidth || height < iconHeight) { |
| final float ratio = (float) iconWidth / iconHeight; |
| |
| if (iconWidth > iconHeight) { |
| height = (int) (width / ratio); |
| } else if (iconHeight > iconWidth) { |
| width = (int) (height * ratio); |
| } |
| |
| final Bitmap.Config c = icon.getOpacity() != PixelFormat.OPAQUE ? |
| Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565; |
| final Bitmap thumb = Bitmap.createBitmap(mIconWidth, mIconHeight, c); |
| final Canvas canvas = mCanvas; |
| canvas.setBitmap(thumb); |
| // Copy the old bounds to restore them later |
| // If we were to do oldBounds = icon.getBounds(), |
| // the call to setBounds() that follows would |
| // change the same instance and we would lose the |
| // old bounds |
| mOldBounds.set(icon.getBounds()); |
| final int x = (mIconWidth - width) / 2; |
| final int y = (mIconHeight - height) / 2; |
| icon.setBounds(x, y, x + width, y + height); |
| icon.draw(canvas); |
| icon.setBounds(mOldBounds); |
| icon = new BitmapDrawable(getResources(), thumb); |
| canvas.setBitmap(null); |
| } else if (iconWidth < width && iconHeight < height) { |
| final Bitmap.Config c = Bitmap.Config.ARGB_8888; |
| final Bitmap thumb = Bitmap.createBitmap(mIconWidth, mIconHeight, c); |
| final Canvas canvas = mCanvas; |
| canvas.setBitmap(thumb); |
| mOldBounds.set(icon.getBounds()); |
| final int x = (width - iconWidth) / 2; |
| final int y = (height - iconHeight) / 2; |
| icon.setBounds(x, y, x + iconWidth, y + iconHeight); |
| icon.draw(canvas); |
| icon.setBounds(mOldBounds); |
| icon = new BitmapDrawable(getResources(), thumb); |
| canvas.setBitmap(null); |
| } |
| } |
| |
| return icon; |
| } |
| } |
| |
| @Override |
| protected void onCreate(Bundle icicle) { |
| super.onCreate(icicle); |
| |
| mPackageManager = getPackageManager(); |
| |
| if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_WATCH)) { |
| requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); |
| setProgressBarIndeterminateVisibility(true); |
| } |
| onSetContentView(); |
| |
| mIconResizer = new IconResizer(); |
| |
| mIntent = new Intent(getTargetIntent()); |
| mIntent.setComponent(null); |
| mAdapter = new ActivityAdapter(mIconResizer); |
| |
| setListAdapter(mAdapter); |
| getListView().setTextFilterEnabled(true); |
| |
| updateAlertTitle(); |
| updateButtonText(); |
| |
| if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_WATCH)) { |
| setProgressBarIndeterminateVisibility(false); |
| } |
| } |
| |
| private void updateAlertTitle() { |
| TextView alertTitle = (TextView) findViewById(com.android.internal.R.id.alertTitle); |
| if (alertTitle != null) { |
| alertTitle.setText(getTitle()); |
| } |
| } |
| |
| private void updateButtonText() { |
| Button cancelButton = (Button) findViewById(com.android.internal.R.id.button1); |
| if (cancelButton != null) { |
| cancelButton.setOnClickListener(new OnClickListener() { |
| public void onClick(View v) { |
| finish(); |
| } |
| }); |
| } |
| } |
| |
| @Override |
| public void setTitle(CharSequence title) { |
| super.setTitle(title); |
| updateAlertTitle(); |
| } |
| |
| @Override |
| public void setTitle(int titleId) { |
| super.setTitle(titleId); |
| updateAlertTitle(); |
| } |
| |
| /** |
| * Override to call setContentView() with your own content view to |
| * customize the list layout. |
| */ |
| protected void onSetContentView() { |
| setContentView(com.android.internal.R.layout.activity_list); |
| } |
| |
| @Override |
| protected void onListItemClick(ListView l, View v, int position, long id) { |
| Intent intent = intentForPosition(position); |
| startActivity(intent); |
| } |
| |
| /** |
| * Return the actual Intent for a specific position in our |
| * {@link android.widget.ListView}. |
| * @param position The item whose Intent to return |
| */ |
| protected Intent intentForPosition(int position) { |
| ActivityAdapter adapter = (ActivityAdapter) mAdapter; |
| return adapter.intentForPosition(position); |
| } |
| |
| /** |
| * Return the {@link ListItem} for a specific position in our |
| * {@link android.widget.ListView}. |
| * @param position The item to return |
| */ |
| protected ListItem itemForPosition(int position) { |
| ActivityAdapter adapter = (ActivityAdapter) mAdapter; |
| return adapter.itemForPosition(position); |
| } |
| |
| /** |
| * Get the base intent to use when running |
| * {@link PackageManager#queryIntentActivities(Intent, int)}. |
| */ |
| protected Intent getTargetIntent() { |
| return new Intent(); |
| } |
| |
| /** |
| * Perform query on package manager for list items. The default |
| * implementation queries for activities. |
| */ |
| protected List<ResolveInfo> onQueryPackageManager(Intent queryIntent) { |
| return mPackageManager.queryIntentActivities(queryIntent, /* no flags */ 0); |
| } |
| |
| /** |
| * @hide |
| */ |
| protected void onSortResultList(List<ResolveInfo> results) { |
| Collections.sort(results, new ResolveInfo.DisplayNameComparator(mPackageManager)); |
| } |
| |
| /** |
| * Perform the query to determine which results to show and return a list of them. |
| */ |
| public List<ListItem> makeListItems() { |
| // Load all matching activities and sort correctly |
| List<ResolveInfo> list = onQueryPackageManager(mIntent); |
| onSortResultList(list); |
| |
| ArrayList<ListItem> result = new ArrayList<ListItem>(list.size()); |
| int listSize = list.size(); |
| for (int i = 0; i < listSize; i++) { |
| ResolveInfo resolveInfo = list.get(i); |
| result.add(new ListItem(mPackageManager, resolveInfo, null)); |
| } |
| |
| return result; |
| } |
| |
| /** |
| * Whether or not to show icons in the list |
| * @hide keeping this private for now, since only Settings needs it |
| * @return true to show icons beside the activity names, false otherwise |
| */ |
| protected boolean onEvaluateShowIcons() { |
| return true; |
| } |
| } |