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