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