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