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