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 * @hide 445 */ 446 protected void onSortResultList(List<ResolveInfo> results) { 447 Collections.sort(results, new ResolveInfo.DisplayNameComparator(mPackageManager)); 448 } 449 450 /** 451 * Perform the query to determine which results to show and return a list of them. 452 */ 453 public List<ListItem> makeListItems() { 454 // Load all matching activities and sort correctly 455 List<ResolveInfo> list = onQueryPackageManager(mIntent); 456 onSortResultList(list); 457 458 ArrayList<ListItem> result = new ArrayList<ListItem>(list.size()); 459 int listSize = list.size(); 460 for (int i = 0; i < listSize; i++) { 461 ResolveInfo resolveInfo = list.get(i); 462 result.add(new ListItem(mPackageManager, resolveInfo, null)); 463 } 464 465 return result; 466 } 467 468 /** 469 * Whether or not to show icons in the list 470 * @hide keeping this private for now, since only Settings needs it 471 * @return true to show icons beside the activity names, false otherwise 472 */ 473 protected boolean onEvaluateShowIcons() { 474 return true; 475 } 476 } 477