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 ListItem(PackageManager pm, ResolveInfo resolveInfo, IconResizer resizer)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 ListItem()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 ActivityAdapter(IconResizer resizer)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 intentForPosition(int position)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 itemForPosition(int position)130 public ListItem itemForPosition(int position) { 131 if (mActivitiesList == null) { 132 return null; 133 } 134 135 return mActivitiesList.get(position); 136 } 137 getCount()138 public int getCount() { 139 return mActivitiesList != null ? mActivitiesList.size() : 0; 140 } 141 getItem(int position)142 public Object getItem(int position) { 143 return position; 144 } 145 getItemId(int position)146 public long getItemId(int position) { 147 return position; 148 } 149 getView(int position, View convertView, ViewGroup parent)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 bindView(View view, ListItem item)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.setCompoundDrawablesRelativeWithIntrinsicBounds(item.icon, null, null, null); 170 } 171 } 172 getFilter()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 performFiltering(CharSequence prefix)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 publishResults(CharSequence constraint, FilterResults results)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 IconResizer()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 */ createIconThumbnail(Drawable icon)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 onCreate(Bundle icicle)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 updateAlertTitle()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 updateButtonText()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 setTitle(CharSequence title)386 public void setTitle(CharSequence title) { 387 super.setTitle(title); 388 updateAlertTitle(); 389 } 390 391 @Override setTitle(int titleId)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 */ onSetContentView()401 protected void onSetContentView() { 402 setContentView(com.android.internal.R.layout.activity_list); 403 } 404 405 @Override onListItemClick(ListView l, View v, int position, long id)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 */ intentForPosition(int position)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 */ itemForPosition(int position)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 */ getTargetIntent()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 */ onQueryPackageManager(Intent queryIntent)443 protected List<ResolveInfo> onQueryPackageManager(Intent queryIntent) { 444 return mPackageManager.queryIntentActivities(queryIntent, /* no flags */ 0); 445 } 446 447 /** 448 * @hide 449 */ onSortResultList(List<ResolveInfo> results)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 */ makeListItems()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 */ onEvaluateShowIcons()477 protected boolean onEvaluateShowIcons() { 478 return true; 479 } 480 } 481