• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2019 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 com.android.internal.app;
18 
19 import static android.content.Context.ACTIVITY_SERVICE;
20 
21 import android.annotation.NonNull;
22 import android.annotation.Nullable;
23 import android.app.ActivityManager;
24 import android.content.ComponentName;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.content.PermissionChecker;
28 import android.content.pm.ActivityInfo;
29 import android.content.pm.ApplicationInfo;
30 import android.content.pm.LabeledIntent;
31 import android.content.pm.PackageManager;
32 import android.content.pm.ResolveInfo;
33 import android.content.res.Resources;
34 import android.graphics.Bitmap;
35 import android.graphics.ColorMatrix;
36 import android.graphics.ColorMatrixColorFilter;
37 import android.graphics.drawable.BitmapDrawable;
38 import android.graphics.drawable.Drawable;
39 import android.os.AsyncTask;
40 import android.os.RemoteException;
41 import android.os.UserHandle;
42 import android.os.UserManager;
43 import android.text.TextUtils;
44 import android.util.Log;
45 import android.view.LayoutInflater;
46 import android.view.View;
47 import android.view.ViewGroup;
48 import android.widget.AbsListView;
49 import android.widget.BaseAdapter;
50 import android.widget.ImageView;
51 import android.widget.TextView;
52 
53 import com.android.internal.R;
54 import com.android.internal.annotations.VisibleForTesting;
55 import com.android.internal.app.ResolverActivity.ResolvedComponentInfo;
56 import com.android.internal.app.chooser.DisplayResolveInfo;
57 import com.android.internal.app.chooser.TargetInfo;
58 
59 import java.util.ArrayList;
60 import java.util.List;
61 
62 public class ResolverListAdapter extends BaseAdapter {
63     private static final String TAG = "ResolverListAdapter";
64 
65     private final List<Intent> mIntents;
66     private final Intent[] mInitialIntents;
67     private final List<ResolveInfo> mBaseResolveList;
68     private final PackageManager mPm;
69     protected final Context mContext;
70     private static ColorMatrixColorFilter sSuspendedMatrixColorFilter;
71     private final int mIconDpi;
72     protected ResolveInfo mLastChosen;
73     private DisplayResolveInfo mOtherProfile;
74     ResolverListController mResolverListController;
75     private int mPlaceholderCount;
76 
77     protected final LayoutInflater mInflater;
78 
79     // This one is the list that the Adapter will actually present.
80     List<DisplayResolveInfo> mDisplayList;
81     private List<ResolvedComponentInfo> mUnfilteredResolveList;
82 
83     private int mLastChosenPosition = -1;
84     private boolean mFilterLastUsed;
85     final ResolverListCommunicator mResolverListCommunicator;
86     private Runnable mPostListReadyRunnable;
87     private final boolean mIsAudioCaptureDevice;
88     private boolean mIsTabLoaded;
89 
ResolverListAdapter(Context context, List<Intent> payloadIntents, Intent[] initialIntents, List<ResolveInfo> rList, boolean filterLastUsed, ResolverListController resolverListController, ResolverListCommunicator resolverListCommunicator, boolean isAudioCaptureDevice)90     public ResolverListAdapter(Context context, List<Intent> payloadIntents,
91             Intent[] initialIntents, List<ResolveInfo> rList,
92             boolean filterLastUsed,
93             ResolverListController resolverListController,
94             ResolverListCommunicator resolverListCommunicator,
95             boolean isAudioCaptureDevice) {
96         mContext = context;
97         mIntents = payloadIntents;
98         mInitialIntents = initialIntents;
99         mBaseResolveList = rList;
100         mInflater = LayoutInflater.from(context);
101         mPm = context.getPackageManager();
102         mDisplayList = new ArrayList<>();
103         mFilterLastUsed = filterLastUsed;
104         mResolverListController = resolverListController;
105         mResolverListCommunicator = resolverListCommunicator;
106         mIsAudioCaptureDevice = isAudioCaptureDevice;
107         final ActivityManager am = (ActivityManager) mContext.getSystemService(ACTIVITY_SERVICE);
108         mIconDpi = am.getLauncherLargeIconDensity();
109     }
110 
handlePackagesChanged()111     public void handlePackagesChanged() {
112         mResolverListCommunicator.onHandlePackagesChanged(this);
113     }
114 
setPlaceholderCount(int count)115     public void setPlaceholderCount(int count) {
116         mPlaceholderCount = count;
117     }
118 
getPlaceholderCount()119     public int getPlaceholderCount() {
120         return mPlaceholderCount;
121     }
122 
123     @Nullable
getFilteredItem()124     public DisplayResolveInfo getFilteredItem() {
125         if (mFilterLastUsed && mLastChosenPosition >= 0) {
126             // Not using getItem since it offsets to dodge this position for the list
127             return mDisplayList.get(mLastChosenPosition);
128         }
129         return null;
130     }
131 
getOtherProfile()132     public DisplayResolveInfo getOtherProfile() {
133         return mOtherProfile;
134     }
135 
getFilteredPosition()136     public int getFilteredPosition() {
137         if (mFilterLastUsed && mLastChosenPosition >= 0) {
138             return mLastChosenPosition;
139         }
140         return AbsListView.INVALID_POSITION;
141     }
142 
hasFilteredItem()143     public boolean hasFilteredItem() {
144         return mFilterLastUsed && mLastChosen != null;
145     }
146 
getScore(DisplayResolveInfo target)147     public float getScore(DisplayResolveInfo target) {
148         return mResolverListController.getScore(target);
149     }
150 
151     /**
152      * Returns the app share score of the given {@code componentName}.
153      */
getScore(ComponentName componentName)154     public float getScore(ComponentName componentName) {
155         return mResolverListController.getScore(componentName);
156     }
157 
158     /**
159      * Returns the list of top K component names which have highest
160      * {@link #getScore(DisplayResolveInfo)}
161      */
getTopComponentNames(int topK)162     public List<ComponentName> getTopComponentNames(int topK) {
163         return mResolverListController.getTopComponentNames(topK);
164     }
165 
updateModel(ComponentName componentName)166     public void updateModel(ComponentName componentName) {
167         mResolverListController.updateModel(componentName);
168     }
169 
updateChooserCounts(String packageName, String action)170     public void updateChooserCounts(String packageName, String action) {
171         mResolverListController.updateChooserCounts(
172                 packageName, getUserHandle().getIdentifier(), action);
173     }
174 
getUnfilteredResolveList()175     List<ResolvedComponentInfo> getUnfilteredResolveList() {
176         return mUnfilteredResolveList;
177     }
178 
179     /**
180      * Rebuild the list of resolvers. In some cases some parts will need some asynchronous work
181      * to complete.
182      *
183      * The {@code doPostProcessing } parameter is used to specify whether to update the UI and
184      * load additional targets (e.g. direct share) after the list has been rebuilt. This is used
185      * in the case where we want to load the inactive profile's resolved apps to know the
186      * number of targets.
187      *
188      * @return Whether or not the list building is completed.
189      */
rebuildList(boolean doPostProcessing)190     protected boolean rebuildList(boolean doPostProcessing) {
191         List<ResolvedComponentInfo> currentResolveList = null;
192         // Clear the value of mOtherProfile from previous call.
193         mOtherProfile = null;
194         mLastChosen = null;
195         mLastChosenPosition = -1;
196         mDisplayList.clear();
197         mIsTabLoaded = false;
198 
199         if (mBaseResolveList != null) {
200             currentResolveList = mUnfilteredResolveList = new ArrayList<>();
201             mResolverListController.addResolveListDedupe(currentResolveList,
202                     mResolverListCommunicator.getTargetIntent(),
203                     mBaseResolveList);
204         } else {
205             currentResolveList = mUnfilteredResolveList =
206                     mResolverListController.getResolversForIntent(
207                             /* shouldGetResolvedFilter= */ true,
208                             mResolverListCommunicator.shouldGetActivityMetadata(),
209                             mIntents);
210             if (currentResolveList == null) {
211                 processSortedList(currentResolveList, doPostProcessing);
212                 return true;
213             }
214             List<ResolvedComponentInfo> originalList =
215                     mResolverListController.filterIneligibleActivities(currentResolveList,
216                             true);
217             if (originalList != null) {
218                 mUnfilteredResolveList = originalList;
219             }
220         }
221 
222         // So far we only support a single other profile at a time.
223         // The first one we see gets special treatment.
224         for (ResolvedComponentInfo info : currentResolveList) {
225             ResolveInfo resolveInfo = info.getResolveInfoAt(0);
226             if (resolveInfo.targetUserId != UserHandle.USER_CURRENT) {
227                 Intent pOrigIntent = mResolverListCommunicator.getReplacementIntent(
228                         resolveInfo.activityInfo,
229                         info.getIntentAt(0));
230                 Intent replacementIntent = mResolverListCommunicator.getReplacementIntent(
231                         resolveInfo.activityInfo,
232                         mResolverListCommunicator.getTargetIntent());
233                 mOtherProfile = new DisplayResolveInfo(info.getIntentAt(0),
234                         resolveInfo,
235                         resolveInfo.loadLabel(mPm),
236                         resolveInfo.loadLabel(mPm),
237                         pOrigIntent != null ? pOrigIntent : replacementIntent,
238                         makePresentationGetter(resolveInfo));
239                 currentResolveList.remove(info);
240                 break;
241             }
242         }
243 
244         if (mOtherProfile == null) {
245             try {
246                 mLastChosen = mResolverListController.getLastChosen();
247             } catch (RemoteException re) {
248                 Log.d(TAG, "Error calling getLastChosenActivity\n" + re);
249             }
250         }
251 
252         setPlaceholderCount(0);
253         int n;
254         if ((currentResolveList != null) && ((n = currentResolveList.size()) > 0)) {
255             // We only care about fixing the unfilteredList if the current resolve list and
256             // current resolve list are currently the same.
257             List<ResolvedComponentInfo> originalList =
258                     mResolverListController.filterLowPriority(currentResolveList,
259                             mUnfilteredResolveList == currentResolveList);
260             if (originalList != null) {
261                 mUnfilteredResolveList = originalList;
262             }
263 
264             if (currentResolveList.size() > 1) {
265                 int placeholderCount = currentResolveList.size();
266                 if (mResolverListCommunicator.useLayoutWithDefault()) {
267                     --placeholderCount;
268                 }
269                 setPlaceholderCount(placeholderCount);
270                 createSortingTask(doPostProcessing).execute(currentResolveList);
271                 postListReadyRunnable(doPostProcessing, /* rebuildCompleted */ false);
272                 return false;
273             } else {
274                 processSortedList(currentResolveList, doPostProcessing);
275                 return true;
276             }
277         } else {
278             processSortedList(currentResolveList, doPostProcessing);
279             return true;
280         }
281     }
282 
283     AsyncTask<List<ResolvedComponentInfo>,
284             Void,
createSortingTask(boolean doPostProcessing)285             List<ResolvedComponentInfo>> createSortingTask(boolean doPostProcessing) {
286         return new AsyncTask<List<ResolvedComponentInfo>,
287                 Void,
288                 List<ResolvedComponentInfo>>() {
289             @Override
290             protected List<ResolvedComponentInfo> doInBackground(
291                     List<ResolvedComponentInfo>... params) {
292                 mResolverListController.sort(params[0]);
293                 return params[0];
294             }
295             @Override
296             protected void onPostExecute(List<ResolvedComponentInfo> sortedComponents) {
297                 processSortedList(sortedComponents, doPostProcessing);
298                 notifyDataSetChanged();
299                 if (doPostProcessing) {
300                     mResolverListCommunicator.updateProfileViewButton();
301                 }
302             }
303         };
304     }
305 
306     protected void processSortedList(List<ResolvedComponentInfo> sortedComponents,
307             boolean doPostProcessing) {
308         int n;
309         if (sortedComponents != null && (n = sortedComponents.size()) != 0) {
310             // First put the initial items at the top.
311             if (mInitialIntents != null) {
312                 for (int i = 0; i < mInitialIntents.length; i++) {
313                     Intent ii = mInitialIntents[i];
314                     if (ii == null) {
315                         continue;
316                     }
317                     ActivityInfo ai = ii.resolveActivityInfo(
318                             mPm, 0);
319                     if (ai == null) {
320                         Log.w(TAG, "No activity found for " + ii);
321                         continue;
322                     }
323                     ResolveInfo ri = new ResolveInfo();
324                     ri.activityInfo = ai;
325                     UserManager userManager =
326                             (UserManager) mContext.getSystemService(Context.USER_SERVICE);
327                     if (ii instanceof LabeledIntent) {
328                         LabeledIntent li = (LabeledIntent) ii;
329                         ri.resolvePackageName = li.getSourcePackage();
330                         ri.labelRes = li.getLabelResource();
331                         ri.nonLocalizedLabel = li.getNonLocalizedLabel();
332                         ri.icon = li.getIconResource();
333                         ri.iconResourceId = ri.icon;
334                     }
335                     if (userManager.isManagedProfile()) {
336                         ri.noResourceId = true;
337                         ri.icon = 0;
338                     }
339 
340                     addResolveInfo(new DisplayResolveInfo(ii, ri,
341                             ri.loadLabel(mPm), null, ii, makePresentationGetter(ri)));
342                 }
343             }
344 
345 
346             for (ResolvedComponentInfo rci : sortedComponents) {
347                 final ResolveInfo ri = rci.getResolveInfoAt(0);
348                 if (ri != null) {
349                     addResolveInfoWithAlternates(rci);
350                 }
351             }
352         }
353 
354         mResolverListCommunicator.sendVoiceChoicesIfNeeded();
355         postListReadyRunnable(doPostProcessing, /* rebuildCompleted */ true);
356         mIsTabLoaded = true;
357     }
358 
359     /**
360      * Some necessary methods for creating the list are initiated in onCreate and will also
361      * determine the layout known. We therefore can't update the UI inline and post to the
362      * handler thread to update after the current task is finished.
363      * @param doPostProcessing Whether to update the UI and load additional direct share targets
364      *                         after the list has been rebuilt
365      * @param rebuildCompleted Whether the list has been completely rebuilt
366      */
367     void postListReadyRunnable(boolean doPostProcessing, boolean rebuildCompleted) {
368         if (mPostListReadyRunnable == null) {
369             mPostListReadyRunnable = new Runnable() {
370                 @Override
371                 public void run() {
372                     mResolverListCommunicator.onPostListReady(ResolverListAdapter.this,
373                             doPostProcessing, rebuildCompleted);
374                     mPostListReadyRunnable = null;
375                 }
376             };
377             mContext.getMainThreadHandler().post(mPostListReadyRunnable);
378         }
379     }
380 
381     private void addResolveInfoWithAlternates(ResolvedComponentInfo rci) {
382         final int count = rci.getCount();
383         final Intent intent = rci.getIntentAt(0);
384         final ResolveInfo add = rci.getResolveInfoAt(0);
385         final Intent replaceIntent =
386                 mResolverListCommunicator.getReplacementIntent(add.activityInfo, intent);
387         final Intent defaultIntent = mResolverListCommunicator.getReplacementIntent(
388                 add.activityInfo, mResolverListCommunicator.getTargetIntent());
389         final DisplayResolveInfo
390                 dri = new DisplayResolveInfo(intent, add,
391                 replaceIntent != null ? replaceIntent : defaultIntent, makePresentationGetter(add));
392         dri.setPinned(rci.isPinned());
393         if (rci.isPinned()) {
394             Log.i(TAG, "Pinned item: " + rci.name);
395         }
396         addResolveInfo(dri);
397         if (replaceIntent == intent) {
398             // Only add alternates if we didn't get a specific replacement from
399             // the caller. If we have one it trumps potential alternates.
400             for (int i = 1, n = count; i < n; i++) {
401                 final Intent altIntent = rci.getIntentAt(i);
402                 dri.addAlternateSourceIntent(altIntent);
403             }
404         }
405         updateLastChosenPosition(add);
406     }
407 
408     private void updateLastChosenPosition(ResolveInfo info) {
409         // If another profile is present, ignore the last chosen entry.
410         if (mOtherProfile != null) {
411             mLastChosenPosition = -1;
412             return;
413         }
414         if (mLastChosen != null
415                 && mLastChosen.activityInfo.packageName.equals(info.activityInfo.packageName)
416                 && mLastChosen.activityInfo.name.equals(info.activityInfo.name)) {
417             mLastChosenPosition = mDisplayList.size() - 1;
418         }
419     }
420 
421     // We assume that at this point we've already filtered out the only intent for a different
422     // targetUserId which we're going to use.
423     private void addResolveInfo(DisplayResolveInfo dri) {
424         if (dri != null && dri.getResolveInfo() != null
425                 && dri.getResolveInfo().targetUserId == UserHandle.USER_CURRENT) {
426             if (shouldAddResolveInfo(dri)) {
427                 mDisplayList.add(dri);
428                 Log.i(TAG, "Add DisplayResolveInfo component: " + dri.getResolvedComponentName()
429                         + ", intent component: " + dri.getResolvedIntent().getComponent());
430             }
431         }
432     }
433 
434     // Check whether {@code dri} should be added into mDisplayList.
435     protected boolean shouldAddResolveInfo(DisplayResolveInfo dri) {
436         // Checks if this info is already listed in display.
437         for (DisplayResolveInfo existingInfo : mDisplayList) {
438             if (mResolverListCommunicator
439                     .resolveInfoMatch(dri.getResolveInfo(), existingInfo.getResolveInfo())) {
440                 return false;
441             }
442         }
443         return true;
444     }
445 
446     @Nullable
447     public ResolveInfo resolveInfoForPosition(int position, boolean filtered) {
448         TargetInfo target = targetInfoForPosition(position, filtered);
449         if (target != null) {
450             return target.getResolveInfo();
451         }
452         return null;
453     }
454 
455     @Nullable
456     public TargetInfo targetInfoForPosition(int position, boolean filtered) {
457         if (filtered) {
458             return getItem(position);
459         }
460         if (mDisplayList.size() > position) {
461             return mDisplayList.get(position);
462         }
463         return null;
464     }
465 
466     public int getCount() {
467         int totalSize = mDisplayList == null || mDisplayList.isEmpty() ? mPlaceholderCount :
468                 mDisplayList.size();
469         if (mFilterLastUsed && mLastChosenPosition >= 0) {
470             totalSize--;
471         }
472         return totalSize;
473     }
474 
475     public int getUnfilteredCount() {
476         return mDisplayList.size();
477     }
478 
479     @Nullable
480     public TargetInfo getItem(int position) {
481         if (mFilterLastUsed && mLastChosenPosition >= 0 && position >= mLastChosenPosition) {
482             position++;
483         }
484         if (mDisplayList.size() > position) {
485             return mDisplayList.get(position);
486         } else {
487             return null;
488         }
489     }
490 
491     public long getItemId(int position) {
492         return position;
493     }
494 
495     public int getDisplayResolveInfoCount() {
496         return mDisplayList.size();
497     }
498 
499     public DisplayResolveInfo getDisplayResolveInfo(int index) {
500         // Used to query services. We only query services for primary targets, not alternates.
501         return mDisplayList.get(index);
502     }
503 
504     public final View getView(int position, View convertView, ViewGroup parent) {
505         View view = convertView;
506         if (view == null) {
507             view = createView(parent);
508         }
509         onBindView(view, getItem(position), position);
510         return view;
511     }
512 
513     public final View createView(ViewGroup parent) {
514         final View view = onCreateView(parent);
515         final ViewHolder holder = new ViewHolder(view);
516         view.setTag(holder);
517         return view;
518     }
519 
520     View onCreateView(ViewGroup parent) {
521         return mInflater.inflate(
522                 com.android.internal.R.layout.resolve_list_item, parent, false);
523     }
524 
525     public final void bindView(int position, View view) {
526         onBindView(view, getItem(position), position);
527     }
528 
529     protected void onBindView(View view, TargetInfo info, int position) {
530         final ViewHolder holder = (ViewHolder) view.getTag();
531         if (info == null) {
532             holder.icon.setImageDrawable(
533                     mContext.getDrawable(R.drawable.resolver_icon_placeholder));
534             return;
535         }
536 
537         if (info instanceof DisplayResolveInfo
538                 && !((DisplayResolveInfo) info).hasDisplayLabel()) {
539             getLoadLabelTask((DisplayResolveInfo) info, holder).execute();
540         } else {
541             holder.bindLabel(info.getDisplayLabel(), info.getExtendedInfo(), alwaysShowSubLabel());
542         }
543 
544         if (info instanceof DisplayResolveInfo
545                 && !((DisplayResolveInfo) info).hasDisplayIcon()) {
546             new LoadIconTask((DisplayResolveInfo) info, holder).execute();
547         } else {
548             holder.bindIcon(info);
549         }
550     }
551 
552     protected LoadLabelTask getLoadLabelTask(DisplayResolveInfo info, ViewHolder holder) {
553         return new LoadLabelTask(info, holder);
554     }
555 
556     public void onDestroy() {
557         if (mPostListReadyRunnable != null) {
558             mContext.getMainThreadHandler().removeCallbacks(mPostListReadyRunnable);
559             mPostListReadyRunnable = null;
560         }
561         if (mResolverListController != null) {
562             mResolverListController.destroy();
563         }
564     }
565 
566     private static ColorMatrixColorFilter getSuspendedColorMatrix() {
567         if (sSuspendedMatrixColorFilter == null) {
568 
569             int grayValue = 127;
570             float scale = 0.5f; // half bright
571 
572             ColorMatrix tempBrightnessMatrix = new ColorMatrix();
573             float[] mat = tempBrightnessMatrix.getArray();
574             mat[0] = scale;
575             mat[6] = scale;
576             mat[12] = scale;
577             mat[4] = grayValue;
578             mat[9] = grayValue;
579             mat[14] = grayValue;
580 
581             ColorMatrix matrix = new ColorMatrix();
582             matrix.setSaturation(0.0f);
583             matrix.preConcat(tempBrightnessMatrix);
584             sSuspendedMatrixColorFilter = new ColorMatrixColorFilter(matrix);
585         }
586         return sSuspendedMatrixColorFilter;
587     }
588 
589     ActivityInfoPresentationGetter makePresentationGetter(ActivityInfo ai) {
590         return new ActivityInfoPresentationGetter(mContext, mIconDpi, ai);
591     }
592 
593     ResolveInfoPresentationGetter makePresentationGetter(ResolveInfo ri) {
594         return new ResolveInfoPresentationGetter(mContext, mIconDpi, ri);
595     }
596 
597     Drawable loadIconForResolveInfo(ResolveInfo ri) {
598         // Load icons based on the current process. If in work profile icons should be badged.
599         return makePresentationGetter(ri).getIcon(getUserHandle());
600     }
601 
602     void loadFilteredItemIconTaskAsync(@NonNull ImageView iconView) {
603         final DisplayResolveInfo iconInfo = getFilteredItem();
604         if (iconView != null && iconInfo != null) {
605             new AsyncTask<Void, Void, Drawable>() {
606                 @Override
607                 protected Drawable doInBackground(Void... params) {
608                     return loadIconForResolveInfo(iconInfo.getResolveInfo());
609                 }
610 
611                 @Override
612                 protected void onPostExecute(Drawable d) {
613                     iconView.setImageDrawable(d);
614                 }
615             }.execute();
616         }
617     }
618 
619     @VisibleForTesting
620     public UserHandle getUserHandle() {
621         return mResolverListController.getUserHandle();
622     }
623 
624     protected List<ResolvedComponentInfo> getResolversForUser(UserHandle userHandle) {
625         return mResolverListController.getResolversForIntentAsUser(true,
626                 mResolverListCommunicator.shouldGetActivityMetadata(),
627                 mIntents, userHandle);
628     }
629 
630     protected List<Intent> getIntents() {
631         return mIntents;
632     }
633 
634     protected boolean isTabLoaded() {
635         return mIsTabLoaded;
636     }
637 
638     protected void markTabLoaded() {
639         mIsTabLoaded = true;
640     }
641 
642     protected boolean alwaysShowSubLabel() {
643         return false;
644     }
645 
646     /**
647      * Necessary methods to communicate between {@link ResolverListAdapter}
648      * and {@link ResolverActivity}.
649      */
650     interface ResolverListCommunicator {
651 
652         boolean resolveInfoMatch(ResolveInfo lhs, ResolveInfo rhs);
653 
654         Intent getReplacementIntent(ActivityInfo activityInfo, Intent defIntent);
655 
656         void onPostListReady(ResolverListAdapter listAdapter, boolean updateUi,
657                 boolean rebuildCompleted);
658 
659         void sendVoiceChoicesIfNeeded();
660 
661         void updateProfileViewButton();
662 
663         boolean useLayoutWithDefault();
664 
665         boolean shouldGetActivityMetadata();
666 
667         Intent getTargetIntent();
668 
669         void onHandlePackagesChanged(ResolverListAdapter listAdapter);
670     }
671 
672     static class ViewHolder {
673         public View itemView;
674         public Drawable defaultItemViewBackground;
675 
676         public TextView text;
677         public TextView text2;
678         public ImageView icon;
679 
680         ViewHolder(View view) {
681             itemView = view;
682             defaultItemViewBackground = view.getBackground();
683             text = (TextView) view.findViewById(com.android.internal.R.id.text1);
684             text2 = (TextView) view.findViewById(com.android.internal.R.id.text2);
685             icon = (ImageView) view.findViewById(R.id.icon);
686         }
687 
688         public void bindLabel(CharSequence label, CharSequence subLabel, boolean showSubLabel) {
689             text.setText(label);
690 
691             if (TextUtils.equals(label, subLabel)) {
692                 subLabel = null;
693             }
694 
695             text2.setText(subLabel);
696             if (showSubLabel || subLabel != null) {
697                 text2.setVisibility(View.VISIBLE);
698             } else {
699                 text2.setVisibility(View.GONE);
700             }
701 
702             itemView.setContentDescription(null);
703         }
704 
705         public void updateContentDescription(String description) {
706             itemView.setContentDescription(description);
707         }
708 
709         public void bindIcon(TargetInfo info) {
710             icon.setImageDrawable(info.getDisplayIcon(itemView.getContext()));
711             if (info.isSuspended()) {
712                 icon.setColorFilter(getSuspendedColorMatrix());
713             } else {
714                 icon.setColorFilter(null);
715             }
716         }
717     }
718 
719     protected class LoadLabelTask extends AsyncTask<Void, Void, CharSequence[]> {
720         private final DisplayResolveInfo mDisplayResolveInfo;
721         private final ViewHolder mHolder;
722 
723         protected LoadLabelTask(DisplayResolveInfo dri, ViewHolder holder) {
724             mDisplayResolveInfo = dri;
725             mHolder = holder;
726         }
727 
728         @Override
729         protected CharSequence[] doInBackground(Void... voids) {
730             ResolveInfoPresentationGetter pg =
731                     makePresentationGetter(mDisplayResolveInfo.getResolveInfo());
732 
733             if (mIsAudioCaptureDevice) {
734                 // This is an audio capture device, so check record permissions
735                 ActivityInfo activityInfo = mDisplayResolveInfo.getResolveInfo().activityInfo;
736                 String packageName = activityInfo.packageName;
737 
738                 int uid = activityInfo.applicationInfo.uid;
739                 boolean hasRecordPermission =
740                         PermissionChecker.checkPermissionForPreflight(
741                                 mContext,
742                                 android.Manifest.permission.RECORD_AUDIO, -1, uid,
743                                 packageName)
744                                 == android.content.pm.PackageManager.PERMISSION_GRANTED;
745 
746                 if (!hasRecordPermission) {
747                     // Doesn't have record permission, so warn the user
748                     return new CharSequence[] {
749                             pg.getLabel(),
750                             mContext.getString(R.string.usb_device_resolve_prompt_warn)
751                     };
752                 }
753             }
754 
755             return new CharSequence[] {
756                     pg.getLabel(),
757                     pg.getSubLabel()
758             };
759         }
760 
761         @Override
762         protected void onPostExecute(CharSequence[] result) {
763             mDisplayResolveInfo.setDisplayLabel(result[0]);
764             mDisplayResolveInfo.setExtendedInfo(result[1]);
765             mHolder.bindLabel(result[0], result[1], alwaysShowSubLabel());
766         }
767     }
768 
769     class LoadIconTask extends AsyncTask<Void, Void, Drawable> {
770         protected final DisplayResolveInfo mDisplayResolveInfo;
771         private final ResolveInfo mResolveInfo;
772         private ViewHolder mHolder;
773 
774         LoadIconTask(DisplayResolveInfo dri, ViewHolder holder) {
775             mDisplayResolveInfo = dri;
776             mResolveInfo = dri.getResolveInfo();
777             mHolder = holder;
778         }
779 
780         @Override
781         protected Drawable doInBackground(Void... params) {
782             return loadIconForResolveInfo(mResolveInfo);
783         }
784 
785         @Override
786         protected void onPostExecute(Drawable d) {
787             if (getOtherProfile() == mDisplayResolveInfo) {
788                 mResolverListCommunicator.updateProfileViewButton();
789             } else {
790                 mDisplayResolveInfo.setDisplayIcon(d);
791                 mHolder.bindIcon(mDisplayResolveInfo);
792             }
793         }
794 
795         public void setViewHolder(ViewHolder holder) {
796             mHolder = holder;
797             mHolder.bindIcon(mDisplayResolveInfo);
798         }
799     }
800 
801     /**
802      * Loads the icon and label for the provided ResolveInfo.
803      */
804     @VisibleForTesting
805     public static class ResolveInfoPresentationGetter extends ActivityInfoPresentationGetter {
806         private final ResolveInfo mRi;
807         public ResolveInfoPresentationGetter(Context ctx, int iconDpi, ResolveInfo ri) {
808             super(ctx, iconDpi, ri.activityInfo);
809             mRi = ri;
810         }
811 
812         @Override
813         Drawable getIconSubstituteInternal() {
814             Drawable dr = null;
815             try {
816                 // Do not use ResolveInfo#getIconResource() as it defaults to the app
817                 if (mRi.resolvePackageName != null && mRi.icon != 0) {
818                     dr = loadIconFromResource(
819                             mPm.getResourcesForApplication(mRi.resolvePackageName), mRi.icon);
820                 }
821             } catch (PackageManager.NameNotFoundException e) {
822                 Log.e(TAG, "SUBSTITUTE_SHARE_TARGET_APP_NAME_AND_ICON permission granted but "
823                         + "couldn't find resources for package", e);
824             }
825 
826             // Fall back to ActivityInfo if no icon is found via ResolveInfo
827             if (dr == null) dr = super.getIconSubstituteInternal();
828 
829             return dr;
830         }
831 
832         @Override
833         String getAppSubLabelInternal() {
834             // Will default to app name if no intent filter or activity label set, make sure to
835             // check if subLabel matches label before final display
836             return mRi.loadLabel(mPm).toString();
837         }
838     }
839 
840     /**
841      * Loads the icon and label for the provided ActivityInfo.
842      */
843     @VisibleForTesting
844     public static class ActivityInfoPresentationGetter extends
845             TargetPresentationGetter {
846         private final ActivityInfo mActivityInfo;
847         public ActivityInfoPresentationGetter(Context ctx, int iconDpi,
848                 ActivityInfo activityInfo) {
849             super(ctx, iconDpi, activityInfo.applicationInfo);
850             mActivityInfo = activityInfo;
851         }
852 
853         @Override
854         Drawable getIconSubstituteInternal() {
855             Drawable dr = null;
856             try {
857                 // Do not use ActivityInfo#getIconResource() as it defaults to the app
858                 if (mActivityInfo.icon != 0) {
859                     dr = loadIconFromResource(
860                             mPm.getResourcesForApplication(mActivityInfo.applicationInfo),
861                             mActivityInfo.icon);
862                 }
863             } catch (PackageManager.NameNotFoundException e) {
864                 Log.e(TAG, "SUBSTITUTE_SHARE_TARGET_APP_NAME_AND_ICON permission granted but "
865                         + "couldn't find resources for package", e);
866             }
867 
868             return dr;
869         }
870 
871         @Override
872         String getAppSubLabelInternal() {
873             // Will default to app name if no activity label set, make sure to check if subLabel
874             // matches label before final display
875             return (String) mActivityInfo.loadLabel(mPm);
876         }
877     }
878 
879     /**
880      * Loads the icon and label for the provided ApplicationInfo. Defaults to using the application
881      * icon and label over any IntentFilter or Activity icon to increase user understanding, with an
882      * exception for applications that hold the right permission. Always attempts to use available
883      * resources over PackageManager loading mechanisms so badging can be done by iconloader. Uses
884      * Strings to strip creative formatting.
885      */
886     private abstract static class TargetPresentationGetter {
887         @Nullable abstract Drawable getIconSubstituteInternal();
888         @Nullable abstract String getAppSubLabelInternal();
889 
890         private Context mCtx;
891         private final int mIconDpi;
892         private final boolean mHasSubstitutePermission;
893         private final ApplicationInfo mAi;
894 
895         protected PackageManager mPm;
896 
897         TargetPresentationGetter(Context ctx, int iconDpi, ApplicationInfo ai) {
898             mCtx = ctx;
899             mPm = ctx.getPackageManager();
900             mAi = ai;
901             mIconDpi = iconDpi;
902             mHasSubstitutePermission = PackageManager.PERMISSION_GRANTED == mPm.checkPermission(
903                     android.Manifest.permission.SUBSTITUTE_SHARE_TARGET_APP_NAME_AND_ICON,
904                     mAi.packageName);
905         }
906 
907         public Drawable getIcon(UserHandle userHandle) {
908             return new BitmapDrawable(mCtx.getResources(), getIconBitmap(userHandle));
909         }
910 
911         public Bitmap getIconBitmap(UserHandle userHandle) {
912             Drawable dr = null;
913             if (mHasSubstitutePermission) {
914                 dr = getIconSubstituteInternal();
915             }
916 
917             if (dr == null) {
918                 try {
919                     if (mAi.icon != 0) {
920                         dr = loadIconFromResource(mPm.getResourcesForApplication(mAi), mAi.icon);
921                     }
922                 } catch (PackageManager.NameNotFoundException ignore) {
923                 }
924             }
925 
926             // Fall back to ApplicationInfo#loadIcon if nothing has been loaded
927             if (dr == null) {
928                 dr = mAi.loadIcon(mPm);
929             }
930 
931             SimpleIconFactory sif = SimpleIconFactory.obtain(mCtx);
932             Bitmap icon = sif.createUserBadgedIconBitmap(dr, userHandle);
933             sif.recycle();
934 
935             return icon;
936         }
937 
938         public String getLabel() {
939             String label = null;
940             // Apps with the substitute permission will always show the sublabel as their label
941             if (mHasSubstitutePermission) {
942                 label = getAppSubLabelInternal();
943             }
944 
945             if (label == null) {
946                 label = (String) mAi.loadLabel(mPm);
947             }
948 
949             return label;
950         }
951 
952         public String getSubLabel() {
953             // Apps with the substitute permission will never have a sublabel
954             if (mHasSubstitutePermission) return null;
955             return getAppSubLabelInternal();
956         }
957 
958         protected String loadLabelFromResource(Resources res, int resId) {
959             return res.getString(resId);
960         }
961 
962         @Nullable
963         protected Drawable loadIconFromResource(Resources res, int resId) {
964             return res.getDrawableForDensity(resId, mIconDpi);
965         }
966 
967     }
968 }
969