blob: 73109c5c1fbc5bd0105884b02befc58772bd0924 [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;
arangelovb91d08f2020-03-05 21:50:14 +000090 private boolean mIsTabLoaded;
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() {
arangelov7981b122020-01-16 10:58:27 +0000117 mResolverListCommunicator.onHandlePackagesChanged(this);
arangelovb0802dc2019-10-18 18:03:44 +0100118 }
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
Song Hue2deffd2020-03-09 15:22:29 -0700156 /**
157 * Returns the list of top K component names which have highest
158 * {@link #getScore(DisplayResolveInfo)}
159 */
160 public List<ComponentName> getTopComponentNames(int topK) {
161 return mResolverListController.getTopComponentNames(topK);
162 }
163
arangelovb0802dc2019-10-18 18:03:44 +0100164 public void updateModel(ComponentName componentName) {
165 mResolverListController.updateModel(componentName);
166 }
167
arangelov4872e682020-04-16 15:57:38 +0100168 public void updateChooserCounts(String packageName, String action) {
169 mResolverListController.updateChooserCounts(
170 packageName, getUserHandle().getIdentifier(), action);
arangelovb0802dc2019-10-18 18:03:44 +0100171 }
172
arangelov38a6fce2019-12-02 18:21:22 +0000173 List<ResolvedComponentInfo> getUnfilteredResolveList() {
174 return mUnfilteredResolveList;
175 }
176
arangelovb0802dc2019-10-18 18:03:44 +0100177 /**
178 * @return true if all items in the display list are defined as browsers by
179 * ResolveInfo.handleAllWebDataURI
180 */
181 public boolean areAllTargetsBrowsers() {
182 return mAllTargetsAreBrowsers;
183 }
184
185 /**
186 * Rebuild the list of resolvers. In some cases some parts will need some asynchronous work
187 * to complete.
188 *
arangelov7981b122020-01-16 10:58:27 +0000189 * The {@code doPostProcessing } parameter is used to specify whether to update the UI and
190 * load additional targets (e.g. direct share) after the list has been rebuilt. This is used
191 * in the case where we want to load the inactive profile's resolved apps to know the
192 * number of targets.
193 *
arangelovb0802dc2019-10-18 18:03:44 +0100194 * @return Whether or not the list building is completed.
195 */
arangelov7981b122020-01-16 10:58:27 +0000196 protected boolean rebuildList(boolean doPostProcessing) {
arangelovb0802dc2019-10-18 18:03:44 +0100197 List<ResolvedComponentInfo> currentResolveList = null;
198 // Clear the value of mOtherProfile from previous call.
199 mOtherProfile = null;
200 mLastChosen = null;
201 mLastChosenPosition = -1;
202 mAllTargetsAreBrowsers = false;
203 mDisplayList.clear();
arangelovb91d08f2020-03-05 21:50:14 +0000204 mIsTabLoaded = false;
arangelov7981b122020-01-16 10:58:27 +0000205
arangelovb0802dc2019-10-18 18:03:44 +0100206 if (mBaseResolveList != null) {
207 currentResolveList = mUnfilteredResolveList = new ArrayList<>();
208 mResolverListController.addResolveListDedupe(currentResolveList,
209 mResolverListCommunicator.getTargetIntent(),
210 mBaseResolveList);
211 } else {
212 currentResolveList = mUnfilteredResolveList =
arangelovcf268642020-01-15 15:09:51 +0000213 mResolverListController.getResolversForIntent(
214 /* shouldGetResolvedFilter= */ true,
arangelovb0802dc2019-10-18 18:03:44 +0100215 mResolverListCommunicator.shouldGetActivityMetadata(),
216 mIntents);
217 if (currentResolveList == null) {
arangelov7981b122020-01-16 10:58:27 +0000218 processSortedList(currentResolveList, doPostProcessing);
arangelovb0802dc2019-10-18 18:03:44 +0100219 return true;
220 }
221 List<ResolvedComponentInfo> originalList =
222 mResolverListController.filterIneligibleActivities(currentResolveList,
223 true);
224 if (originalList != null) {
225 mUnfilteredResolveList = originalList;
226 }
227 }
228
229 // So far we only support a single other profile at a time.
230 // The first one we see gets special treatment.
231 for (ResolvedComponentInfo info : currentResolveList) {
232 ResolveInfo resolveInfo = info.getResolveInfoAt(0);
233 if (resolveInfo.targetUserId != UserHandle.USER_CURRENT) {
234 Intent pOrigIntent = mResolverListCommunicator.getReplacementIntent(
235 resolveInfo.activityInfo,
236 info.getIntentAt(0));
237 Intent replacementIntent = mResolverListCommunicator.getReplacementIntent(
238 resolveInfo.activityInfo,
239 mResolverListCommunicator.getTargetIntent());
240 mOtherProfile = new DisplayResolveInfo(info.getIntentAt(0),
241 resolveInfo,
242 resolveInfo.loadLabel(mPm),
243 resolveInfo.loadLabel(mPm),
244 pOrigIntent != null ? pOrigIntent : replacementIntent,
245 makePresentationGetter(resolveInfo));
246 currentResolveList.remove(info);
247 break;
248 }
249 }
250
251 if (mOtherProfile == null) {
252 try {
253 mLastChosen = mResolverListController.getLastChosen();
254 } catch (RemoteException re) {
255 Log.d(TAG, "Error calling getLastChosenActivity\n" + re);
256 }
257 }
258
259 int n;
260 if ((currentResolveList != null) && ((n = currentResolveList.size()) > 0)) {
261 // We only care about fixing the unfilteredList if the current resolve list and
262 // current resolve list are currently the same.
263 List<ResolvedComponentInfo> originalList =
264 mResolverListController.filterLowPriority(currentResolveList,
265 mUnfilteredResolveList == currentResolveList);
266 if (originalList != null) {
267 mUnfilteredResolveList = originalList;
268 }
269
270 if (currentResolveList.size() > 1) {
271 int placeholderCount = currentResolveList.size();
272 if (mResolverListCommunicator.useLayoutWithDefault()) {
273 --placeholderCount;
274 }
275 setPlaceholderCount(placeholderCount);
arangelov7981b122020-01-16 10:58:27 +0000276 createSortingTask(doPostProcessing).execute(currentResolveList);
277 postListReadyRunnable(doPostProcessing);
arangelovb0802dc2019-10-18 18:03:44 +0100278 return false;
279 } else {
arangelov7981b122020-01-16 10:58:27 +0000280 processSortedList(currentResolveList, doPostProcessing);
arangelovb0802dc2019-10-18 18:03:44 +0100281 return true;
282 }
283 } else {
arangelov7981b122020-01-16 10:58:27 +0000284 processSortedList(currentResolveList, doPostProcessing);
arangelovb0802dc2019-10-18 18:03:44 +0100285 return true;
286 }
287 }
288
289 AsyncTask<List<ResolvedComponentInfo>,
290 Void,
arangelov7981b122020-01-16 10:58:27 +0000291 List<ResolvedComponentInfo>> createSortingTask(boolean doPostProcessing) {
arangelovb0802dc2019-10-18 18:03:44 +0100292 return new AsyncTask<List<ResolvedComponentInfo>,
293 Void,
294 List<ResolvedComponentInfo>>() {
295 @Override
296 protected List<ResolvedComponentInfo> doInBackground(
297 List<ResolvedComponentInfo>... params) {
298 mResolverListController.sort(params[0]);
299 return params[0];
300 }
301 @Override
302 protected void onPostExecute(List<ResolvedComponentInfo> sortedComponents) {
arangelov7981b122020-01-16 10:58:27 +0000303 processSortedList(sortedComponents, doPostProcessing);
arangelovb0802dc2019-10-18 18:03:44 +0100304 notifyDataSetChanged();
arangelov7981b122020-01-16 10:58:27 +0000305 if (doPostProcessing) {
306 mResolverListCommunicator.updateProfileViewButton();
307 }
arangelovb0802dc2019-10-18 18:03:44 +0100308 }
309 };
310 }
311
arangelov7981b122020-01-16 10:58:27 +0000312 protected void processSortedList(List<ResolvedComponentInfo> sortedComponents,
313 boolean doPostProcessing) {
arangelovb0802dc2019-10-18 18:03:44 +0100314 int n;
315 if (sortedComponents != null && (n = sortedComponents.size()) != 0) {
316 mAllTargetsAreBrowsers = mUseLayoutForBrowsables;
317
318 // First put the initial items at the top.
319 if (mInitialIntents != null) {
320 for (int i = 0; i < mInitialIntents.length; i++) {
321 Intent ii = mInitialIntents[i];
322 if (ii == null) {
323 continue;
324 }
325 ActivityInfo ai = ii.resolveActivityInfo(
326 mPm, 0);
327 if (ai == null) {
328 Log.w(TAG, "No activity found for " + ii);
329 continue;
330 }
331 ResolveInfo ri = new ResolveInfo();
332 ri.activityInfo = ai;
333 UserManager userManager =
334 (UserManager) mContext.getSystemService(Context.USER_SERVICE);
335 if (ii instanceof LabeledIntent) {
336 LabeledIntent li = (LabeledIntent) ii;
337 ri.resolvePackageName = li.getSourcePackage();
338 ri.labelRes = li.getLabelResource();
339 ri.nonLocalizedLabel = li.getNonLocalizedLabel();
340 ri.icon = li.getIconResource();
341 ri.iconResourceId = ri.icon;
342 }
343 if (userManager.isManagedProfile()) {
344 ri.noResourceId = true;
345 ri.icon = 0;
346 }
347 mAllTargetsAreBrowsers &= ri.handleAllWebDataURI;
348
349 addResolveInfo(new DisplayResolveInfo(ii, ri,
350 ri.loadLabel(mPm), null, ii, makePresentationGetter(ri)));
351 }
352 }
353
354
355 for (ResolvedComponentInfo rci : sortedComponents) {
356 final ResolveInfo ri = rci.getResolveInfoAt(0);
357 if (ri != null) {
358 mAllTargetsAreBrowsers &= ri.handleAllWebDataURI;
359 addResolveInfoWithAlternates(rci);
360 }
361 }
362 }
363
364 mResolverListCommunicator.sendVoiceChoicesIfNeeded();
arangelov7981b122020-01-16 10:58:27 +0000365 postListReadyRunnable(doPostProcessing);
arangelovb91d08f2020-03-05 21:50:14 +0000366 mIsTabLoaded = true;
arangelovb0802dc2019-10-18 18:03:44 +0100367 }
368
369 /**
370 * Some necessary methods for creating the list are initiated in onCreate and will also
371 * determine the layout known. We therefore can't update the UI inline and post to the
372 * handler thread to update after the current task is finished.
arangelov7981b122020-01-16 10:58:27 +0000373 * @param doPostProcessing Whether to update the UI and load additional direct share targets
374 * after the list has been rebuilt
arangelovb0802dc2019-10-18 18:03:44 +0100375 */
arangelov7981b122020-01-16 10:58:27 +0000376 void postListReadyRunnable(boolean doPostProcessing) {
arangelovb0802dc2019-10-18 18:03:44 +0100377 if (mPostListReadyRunnable == null) {
378 mPostListReadyRunnable = new Runnable() {
379 @Override
380 public void run() {
arangelov7981b122020-01-16 10:58:27 +0000381 mResolverListCommunicator.onPostListReady(ResolverListAdapter.this,
382 doPostProcessing);
arangelovb0802dc2019-10-18 18:03:44 +0100383 mPostListReadyRunnable = null;
384 }
385 };
386 mContext.getMainThreadHandler().post(mPostListReadyRunnable);
387 }
388 }
389
arangelovb0802dc2019-10-18 18:03:44 +0100390 private void addResolveInfoWithAlternates(ResolvedComponentInfo rci) {
391 final int count = rci.getCount();
392 final Intent intent = rci.getIntentAt(0);
393 final ResolveInfo add = rci.getResolveInfoAt(0);
394 final Intent replaceIntent =
395 mResolverListCommunicator.getReplacementIntent(add.activityInfo, intent);
396 final Intent defaultIntent = mResolverListCommunicator.getReplacementIntent(
397 add.activityInfo, mResolverListCommunicator.getTargetIntent());
398 final DisplayResolveInfo
399 dri = new DisplayResolveInfo(intent, add,
400 replaceIntent != null ? replaceIntent : defaultIntent, makePresentationGetter(add));
Alison Cichowlas1fd47152019-11-14 19:50:55 -0500401 dri.setPinned(rci.isPinned());
402 if (rci.isPinned()) {
403 Log.i(TAG, "Pinned item: " + rci.name);
404 }
arangelovb0802dc2019-10-18 18:03:44 +0100405 addResolveInfo(dri);
406 if (replaceIntent == intent) {
407 // Only add alternates if we didn't get a specific replacement from
408 // the caller. If we have one it trumps potential alternates.
409 for (int i = 1, n = count; i < n; i++) {
410 final Intent altIntent = rci.getIntentAt(i);
411 dri.addAlternateSourceIntent(altIntent);
412 }
413 }
414 updateLastChosenPosition(add);
415 }
416
417 private void updateLastChosenPosition(ResolveInfo info) {
418 // If another profile is present, ignore the last chosen entry.
419 if (mOtherProfile != null) {
420 mLastChosenPosition = -1;
421 return;
422 }
423 if (mLastChosen != null
424 && mLastChosen.activityInfo.packageName.equals(info.activityInfo.packageName)
425 && mLastChosen.activityInfo.name.equals(info.activityInfo.name)) {
426 mLastChosenPosition = mDisplayList.size() - 1;
427 }
428 }
429
430 // We assume that at this point we've already filtered out the only intent for a different
431 // targetUserId which we're going to use.
432 private void addResolveInfo(DisplayResolveInfo dri) {
arangelov5fc9e7d2020-01-07 17:59:14 +0000433 // TODO(arangelov): Is that UserHandle.USER_CURRENT check okay?
arangelovb0802dc2019-10-18 18:03:44 +0100434 if (dri != null && dri.getResolveInfo() != null
435 && dri.getResolveInfo().targetUserId == UserHandle.USER_CURRENT) {
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;
441 }
442 }
443 mDisplayList.add(dri);
444 }
445 }
446
447 @Nullable
448 public ResolveInfo resolveInfoForPosition(int position, boolean filtered) {
449 TargetInfo target = targetInfoForPosition(position, filtered);
450 if (target != null) {
451 return target.getResolveInfo();
452 }
453 return null;
454 }
455
456 @Nullable
457 public TargetInfo targetInfoForPosition(int position, boolean filtered) {
458 if (filtered) {
459 return getItem(position);
460 }
461 if (mDisplayList.size() > position) {
462 return mDisplayList.get(position);
463 }
464 return null;
465 }
466
467 public int getCount() {
468 int totalSize = mDisplayList == null || mDisplayList.isEmpty() ? mPlaceholderCount :
469 mDisplayList.size();
470 if (mFilterLastUsed && mLastChosenPosition >= 0) {
471 totalSize--;
472 }
473 return totalSize;
474 }
475
476 public int getUnfilteredCount() {
477 return mDisplayList.size();
478 }
479
480 @Nullable
481 public TargetInfo getItem(int position) {
482 if (mFilterLastUsed && mLastChosenPosition >= 0 && position >= mLastChosenPosition) {
483 position++;
484 }
485 if (mDisplayList.size() > position) {
486 return mDisplayList.get(position);
487 } else {
488 return null;
489 }
490 }
491
492 public long getItemId(int position) {
493 return position;
494 }
495
496 public int getDisplayResolveInfoCount() {
497 return mDisplayList.size();
498 }
499
500 public DisplayResolveInfo getDisplayResolveInfo(int index) {
501 // Used to query services. We only query services for primary targets, not alternates.
502 return mDisplayList.get(index);
503 }
504
505 public final View getView(int position, View convertView, ViewGroup parent) {
506 View view = convertView;
507 if (view == null) {
508 view = createView(parent);
509 }
510 onBindView(view, getItem(position));
511 return view;
512 }
513
514 public final View createView(ViewGroup parent) {
515 final View view = onCreateView(parent);
516 final ViewHolder holder = new ViewHolder(view);
517 view.setTag(holder);
518 return view;
519 }
520
Zhen Zhangbde7b462019-11-11 11:49:33 -0800521 View onCreateView(ViewGroup parent) {
arangelovb0802dc2019-10-18 18:03:44 +0100522 return mInflater.inflate(
523 com.android.internal.R.layout.resolve_list_item, parent, false);
524 }
525
526 public final void bindView(int position, View view) {
527 onBindView(view, getItem(position));
528 }
529
530 protected void onBindView(View view, TargetInfo info) {
531 final ViewHolder holder = (ViewHolder) view.getTag();
532 if (info == null) {
533 holder.icon.setImageDrawable(
534 mContext.getDrawable(R.drawable.resolver_icon_placeholder));
535 return;
536 }
537
538 if (info instanceof DisplayResolveInfo
539 && !((DisplayResolveInfo) info).hasDisplayLabel()) {
540 getLoadLabelTask((DisplayResolveInfo) info, holder).execute();
541 } else {
542 holder.bindLabel(info.getDisplayLabel(), info.getExtendedInfo());
543 }
544
545 if (info.isSuspended()) {
546 holder.icon.setColorFilter(mSuspendedMatrixColorFilter);
547 } else {
548 holder.icon.setColorFilter(null);
549 }
550
551 if (info instanceof DisplayResolveInfo
552 && !((DisplayResolveInfo) info).hasDisplayIcon()) {
553 new ResolverListAdapter.LoadIconTask((DisplayResolveInfo) info, holder.icon).execute();
554 } else {
555 holder.icon.setImageDrawable(info.getDisplayIcon(mContext));
556 }
557 }
558
559 protected LoadLabelTask getLoadLabelTask(DisplayResolveInfo info, ViewHolder holder) {
560 return new LoadLabelTask(info, holder);
561 }
562
563 public void onDestroy() {
564 if (mPostListReadyRunnable != null) {
565 mContext.getMainThreadHandler().removeCallbacks(mPostListReadyRunnable);
566 mPostListReadyRunnable = null;
567 }
568 if (mResolverListController != null) {
569 mResolverListController.destroy();
570 }
571 }
572
573 private ColorMatrixColorFilter createSuspendedColorMatrix() {
574 int grayValue = 127;
575 float scale = 0.5f; // half bright
576
577 ColorMatrix tempBrightnessMatrix = new ColorMatrix();
578 float[] mat = tempBrightnessMatrix.getArray();
579 mat[0] = scale;
580 mat[6] = scale;
581 mat[12] = scale;
582 mat[4] = grayValue;
583 mat[9] = grayValue;
584 mat[14] = grayValue;
585
586 ColorMatrix matrix = new ColorMatrix();
587 matrix.setSaturation(0.0f);
588 matrix.preConcat(tempBrightnessMatrix);
589 return new ColorMatrixColorFilter(matrix);
590 }
591
592 ActivityInfoPresentationGetter makePresentationGetter(ActivityInfo ai) {
593 return new ActivityInfoPresentationGetter(mContext, mIconDpi, ai);
594 }
595
596 ResolveInfoPresentationGetter makePresentationGetter(ResolveInfo ri) {
597 return new ResolveInfoPresentationGetter(mContext, mIconDpi, ri);
598 }
599
600 Drawable loadIconForResolveInfo(ResolveInfo ri) {
601 // Load icons based on the current process. If in work profile icons should be badged.
arangelov5fc9e7d2020-01-07 17:59:14 +0000602 return makePresentationGetter(ri).getIcon(getUserHandle());
arangelovb0802dc2019-10-18 18:03:44 +0100603 }
604
605 void loadFilteredItemIconTaskAsync(@NonNull ImageView iconView) {
606 final DisplayResolveInfo iconInfo = getFilteredItem();
607 if (iconView != null && iconInfo != null) {
608 new LoadIconTask(iconInfo, iconView).execute();
609 }
610 }
611
arangelov5fc9e7d2020-01-07 17:59:14 +0000612 UserHandle getUserHandle() {
613 return mResolverListController.getUserHandle();
614 }
615
arangelov7981b122020-01-16 10:58:27 +0000616 protected List<ResolvedComponentInfo> getResolversForUser(UserHandle userHandle) {
617 return mResolverListController.getResolversForIntentAsUser(true,
618 mResolverListCommunicator.shouldGetActivityMetadata(),
619 mIntents, userHandle);
620 }
621
arangelov590fba32020-02-11 18:05:42 +0000622 protected List<Intent> getIntents() {
623 return mIntents;
624 }
625
arangelovb91d08f2020-03-05 21:50:14 +0000626 protected boolean isTabLoaded() {
627 return mIsTabLoaded;
628 }
629
630 protected void markTabLoaded() {
631 mIsTabLoaded = true;
arangelovf6986d42020-02-12 15:03:22 +0000632 }
633
arangelovb0802dc2019-10-18 18:03:44 +0100634 /**
635 * Necessary methods to communicate between {@link ResolverListAdapter}
636 * and {@link ResolverActivity}.
637 */
638 interface ResolverListCommunicator {
639
640 boolean resolveInfoMatch(ResolveInfo lhs, ResolveInfo rhs);
641
642 Intent getReplacementIntent(ActivityInfo activityInfo, Intent defIntent);
643
arangelov7981b122020-01-16 10:58:27 +0000644 void onPostListReady(ResolverListAdapter listAdapter, boolean updateUi);
arangelovb0802dc2019-10-18 18:03:44 +0100645
646 void sendVoiceChoicesIfNeeded();
647
648 void updateProfileViewButton();
649
650 boolean useLayoutWithDefault();
651
652 boolean shouldGetActivityMetadata();
653
654 Intent getTargetIntent();
655
arangelov7981b122020-01-16 10:58:27 +0000656 void onHandlePackagesChanged(ResolverListAdapter listAdapter);
arangelovb0802dc2019-10-18 18:03:44 +0100657 }
658
659 static class ViewHolder {
660 public View itemView;
661 public Drawable defaultItemViewBackground;
662
663 public TextView text;
664 public TextView text2;
665 public ImageView icon;
666
667 ViewHolder(View view) {
668 itemView = view;
669 defaultItemViewBackground = view.getBackground();
670 text = (TextView) view.findViewById(com.android.internal.R.id.text1);
671 text2 = (TextView) view.findViewById(com.android.internal.R.id.text2);
672 icon = (ImageView) view.findViewById(R.id.icon);
673 }
674
675 public void bindLabel(CharSequence label, CharSequence subLabel) {
676 if (!TextUtils.equals(text.getText(), label)) {
677 text.setText(label);
678 }
679
680 // Always show a subLabel for visual consistency across list items. Show an empty
681 // subLabel if the subLabel is the same as the label
682 if (TextUtils.equals(label, subLabel)) {
683 subLabel = null;
684 }
685
Zhen Zhang6a549b52019-11-04 15:11:45 -0800686 if (!TextUtils.equals(text2.getText(), subLabel)) {
arangelovb0802dc2019-10-18 18:03:44 +0100687 text2.setVisibility(View.VISIBLE);
688 text2.setText(subLabel);
689 }
690 }
691 }
692
693 protected class LoadLabelTask extends AsyncTask<Void, Void, CharSequence[]> {
694 private final DisplayResolveInfo mDisplayResolveInfo;
695 private final ViewHolder mHolder;
696
697 protected LoadLabelTask(DisplayResolveInfo dri, ViewHolder holder) {
698 mDisplayResolveInfo = dri;
699 mHolder = holder;
700 }
701
702 @Override
703 protected CharSequence[] doInBackground(Void... voids) {
704 ResolveInfoPresentationGetter pg =
705 makePresentationGetter(mDisplayResolveInfo.getResolveInfo());
Paul McLean07425c82019-10-18 12:00:11 -0600706
707 if (mIsAudioCaptureDevice) {
708 // This is an audio capture device, so check record permissions
709 ActivityInfo activityInfo = mDisplayResolveInfo.getResolveInfo().activityInfo;
710 String packageName = activityInfo.packageName;
711
712 int uid = activityInfo.applicationInfo.uid;
713 boolean hasRecordPermission =
714 PermissionChecker.checkPermissionForPreflight(
715 mContext,
716 android.Manifest.permission.RECORD_AUDIO, -1, uid,
717 packageName)
718 == android.content.pm.PackageManager.PERMISSION_GRANTED;
719
720 if (!hasRecordPermission) {
721 // Doesn't have record permission, so warn the user
722 return new CharSequence[] {
723 pg.getLabel(),
724 mContext.getString(R.string.usb_device_resolve_prompt_warn)
725 };
726 }
727 }
728
arangelovb0802dc2019-10-18 18:03:44 +0100729 return new CharSequence[] {
730 pg.getLabel(),
731 pg.getSubLabel()
732 };
733 }
734
735 @Override
736 protected void onPostExecute(CharSequence[] result) {
737 mDisplayResolveInfo.setDisplayLabel(result[0]);
738 mDisplayResolveInfo.setExtendedInfo(result[1]);
739 mHolder.bindLabel(result[0], result[1]);
740 }
741 }
742
743 class LoadIconTask extends AsyncTask<Void, Void, Drawable> {
744 protected final com.android.internal.app.chooser.DisplayResolveInfo mDisplayResolveInfo;
745 private final ResolveInfo mResolveInfo;
746 private final ImageView mTargetView;
747
748 LoadIconTask(DisplayResolveInfo dri, ImageView target) {
749 mDisplayResolveInfo = dri;
750 mResolveInfo = dri.getResolveInfo();
751 mTargetView = target;
752 }
753
754 @Override
755 protected Drawable doInBackground(Void... params) {
756 return loadIconForResolveInfo(mResolveInfo);
757 }
758
759 @Override
760 protected void onPostExecute(Drawable d) {
761 if (getOtherProfile() == mDisplayResolveInfo) {
762 mResolverListCommunicator.updateProfileViewButton();
763 } else {
764 mDisplayResolveInfo.setDisplayIcon(d);
765 mTargetView.setImageDrawable(d);
766 }
767 }
768 }
769
770 /**
771 * Loads the icon and label for the provided ResolveInfo.
772 */
773 @VisibleForTesting
774 public static class ResolveInfoPresentationGetter extends ActivityInfoPresentationGetter {
775 private final ResolveInfo mRi;
776 public ResolveInfoPresentationGetter(Context ctx, int iconDpi, ResolveInfo ri) {
777 super(ctx, iconDpi, ri.activityInfo);
778 mRi = ri;
779 }
780
781 @Override
782 Drawable getIconSubstituteInternal() {
783 Drawable dr = null;
784 try {
785 // Do not use ResolveInfo#getIconResource() as it defaults to the app
786 if (mRi.resolvePackageName != null && mRi.icon != 0) {
787 dr = loadIconFromResource(
788 mPm.getResourcesForApplication(mRi.resolvePackageName), mRi.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 // Fall back to ActivityInfo if no icon is found via ResolveInfo
796 if (dr == null) dr = super.getIconSubstituteInternal();
797
798 return dr;
799 }
800
801 @Override
802 String getAppSubLabelInternal() {
803 // Will default to app name if no intent filter or activity label set, make sure to
804 // check if subLabel matches label before final display
805 return (String) mRi.loadLabel(mPm);
806 }
807 }
808
809 /**
810 * Loads the icon and label for the provided ActivityInfo.
811 */
812 @VisibleForTesting
813 public static class ActivityInfoPresentationGetter extends
814 TargetPresentationGetter {
815 private final ActivityInfo mActivityInfo;
816 public ActivityInfoPresentationGetter(Context ctx, int iconDpi,
817 ActivityInfo activityInfo) {
818 super(ctx, iconDpi, activityInfo.applicationInfo);
819 mActivityInfo = activityInfo;
820 }
821
822 @Override
823 Drawable getIconSubstituteInternal() {
824 Drawable dr = null;
825 try {
826 // Do not use ActivityInfo#getIconResource() as it defaults to the app
827 if (mActivityInfo.icon != 0) {
828 dr = loadIconFromResource(
829 mPm.getResourcesForApplication(mActivityInfo.applicationInfo),
830 mActivityInfo.icon);
831 }
832 } catch (PackageManager.NameNotFoundException e) {
833 Log.e(TAG, "SUBSTITUTE_SHARE_TARGET_APP_NAME_AND_ICON permission granted but "
834 + "couldn't find resources for package", e);
835 }
836
837 return dr;
838 }
839
840 @Override
841 String getAppSubLabelInternal() {
842 // Will default to app name if no activity label set, make sure to check if subLabel
843 // matches label before final display
844 return (String) mActivityInfo.loadLabel(mPm);
845 }
846 }
847
848 /**
849 * Loads the icon and label for the provided ApplicationInfo. Defaults to using the application
850 * icon and label over any IntentFilter or Activity icon to increase user understanding, with an
851 * exception for applications that hold the right permission. Always attempts to use available
852 * resources over PackageManager loading mechanisms so badging can be done by iconloader. Uses
853 * Strings to strip creative formatting.
854 */
855 private abstract static class TargetPresentationGetter {
856 @Nullable abstract Drawable getIconSubstituteInternal();
857 @Nullable abstract String getAppSubLabelInternal();
858
859 private Context mCtx;
860 private final int mIconDpi;
861 private final boolean mHasSubstitutePermission;
862 private final ApplicationInfo mAi;
863
864 protected PackageManager mPm;
865
866 TargetPresentationGetter(Context ctx, int iconDpi, ApplicationInfo ai) {
867 mCtx = ctx;
868 mPm = ctx.getPackageManager();
869 mAi = ai;
870 mIconDpi = iconDpi;
871 mHasSubstitutePermission = PackageManager.PERMISSION_GRANTED == mPm.checkPermission(
872 android.Manifest.permission.SUBSTITUTE_SHARE_TARGET_APP_NAME_AND_ICON,
873 mAi.packageName);
874 }
875
876 public Drawable getIcon(UserHandle userHandle) {
877 return new BitmapDrawable(mCtx.getResources(), getIconBitmap(userHandle));
878 }
879
880 public Bitmap getIconBitmap(UserHandle userHandle) {
881 Drawable dr = null;
882 if (mHasSubstitutePermission) {
883 dr = getIconSubstituteInternal();
884 }
885
886 if (dr == null) {
887 try {
888 if (mAi.icon != 0) {
889 dr = loadIconFromResource(mPm.getResourcesForApplication(mAi), mAi.icon);
890 }
891 } catch (PackageManager.NameNotFoundException ignore) {
892 }
893 }
894
895 // Fall back to ApplicationInfo#loadIcon if nothing has been loaded
896 if (dr == null) {
897 dr = mAi.loadIcon(mPm);
898 }
899
900 SimpleIconFactory sif = SimpleIconFactory.obtain(mCtx);
901 Bitmap icon = sif.createUserBadgedIconBitmap(dr, userHandle);
902 sif.recycle();
903
904 return icon;
905 }
906
907 public String getLabel() {
908 String label = null;
909 // Apps with the substitute permission will always show the sublabel as their label
910 if (mHasSubstitutePermission) {
911 label = getAppSubLabelInternal();
912 }
913
914 if (label == null) {
915 label = (String) mAi.loadLabel(mPm);
916 }
917
918 return label;
919 }
920
921 public String getSubLabel() {
922 // Apps with the substitute permission will never have a sublabel
923 if (mHasSubstitutePermission) return null;
924 return getAppSubLabelInternal();
925 }
926
927 protected String loadLabelFromResource(Resources res, int resId) {
928 return res.getString(resId);
929 }
930
931 @Nullable
932 protected Drawable loadIconFromResource(Resources res, int resId) {
933 return res.getDrawableForDensity(resId, mIconDpi);
934 }
935
936 }
937}