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