diff options
Diffstat (limited to 'java')
15 files changed, 822 insertions, 468 deletions
diff --git a/java/src/com/android/intentresolver/ChooserActivity.java b/java/src/com/android/intentresolver/ChooserActivity.java index 014aa2a2..a2dff970 100644 --- a/java/src/com/android/intentresolver/ChooserActivity.java +++ b/java/src/com/android/intentresolver/ChooserActivity.java @@ -90,6 +90,8 @@ import com.android.intentresolver.contentpreview.PreviewViewModel;  import com.android.intentresolver.flags.FeatureFlagRepository;  import com.android.intentresolver.flags.FeatureFlagRepositoryFactory;  import com.android.intentresolver.grid.ChooserGridAdapter; +import com.android.intentresolver.icons.DefaultTargetDataLoader; +import com.android.intentresolver.icons.TargetDataLoader;  import com.android.intentresolver.measurements.Tracer;  import com.android.intentresolver.model.AbstractResolverComparator;  import com.android.intentresolver.model.AppPredictionServiceResolverComparator; @@ -309,7 +311,8 @@ public class ChooserActivity extends ResolverActivity implements                  mChooserRequest.getDefaultTitleResource(),                  mChooserRequest.getInitialIntents(),                  /* rList: List<ResolveInfo> = */ null, -                /* supportsAlwaysUseOption = */ false); +                /* supportsAlwaysUseOption = */ false, +                new DefaultTargetDataLoader(this, getLifecycle(), false));          mChooserShownTime = System.currentTimeMillis();          final long systemCost = mChooserShownTime - intentReceivedTime; @@ -442,13 +445,14 @@ public class ChooserActivity extends ResolverActivity implements      protected AbstractMultiProfilePagerAdapter createMultiProfilePagerAdapter(              Intent[] initialIntents,              List<ResolveInfo> rList, -            boolean filterLastUsed) { +            boolean filterLastUsed, +            TargetDataLoader targetDataLoader) {          if (shouldShowTabs()) {              mChooserMultiProfilePagerAdapter = createChooserMultiProfilePagerAdapterForTwoProfiles( -                    initialIntents, rList, filterLastUsed); +                    initialIntents, rList, filterLastUsed, targetDataLoader);          } else {              mChooserMultiProfilePagerAdapter = createChooserMultiProfilePagerAdapterForOneProfile( -                    initialIntents, rList, filterLastUsed); +                    initialIntents, rList, filterLastUsed, targetDataLoader);          }          return mChooserMultiProfilePagerAdapter;      } @@ -491,14 +495,16 @@ public class ChooserActivity extends ResolverActivity implements      private ChooserMultiProfilePagerAdapter createChooserMultiProfilePagerAdapterForOneProfile(              Intent[] initialIntents,              List<ResolveInfo> rList, -            boolean filterLastUsed) { +            boolean filterLastUsed, +            TargetDataLoader targetDataLoader) {          ChooserGridAdapter adapter = createChooserGridAdapter(                  /* context */ this,                  /* payloadIntents */ mIntents,                  initialIntents,                  rList,                  filterLastUsed, -                /* userHandle */ getPersonalProfileUserHandle()); +                /* userHandle */ getPersonalProfileUserHandle(), +                targetDataLoader);          return new ChooserMultiProfilePagerAdapter(                  /* context */ this,                  adapter, @@ -512,7 +518,8 @@ public class ChooserActivity extends ResolverActivity implements      private ChooserMultiProfilePagerAdapter createChooserMultiProfilePagerAdapterForTwoProfiles(              Intent[] initialIntents,              List<ResolveInfo> rList, -            boolean filterLastUsed) { +            boolean filterLastUsed, +            TargetDataLoader targetDataLoader) {          int selectedProfile = findSelectedProfile();          ChooserGridAdapter personalAdapter = createChooserGridAdapter(                  /* context */ this, @@ -520,14 +527,16 @@ public class ChooserActivity extends ResolverActivity implements                  selectedProfile == PROFILE_PERSONAL ? initialIntents : null,                  rList,                  filterLastUsed, -                /* userHandle */ getPersonalProfileUserHandle()); +                /* userHandle */ getPersonalProfileUserHandle(), +                targetDataLoader);          ChooserGridAdapter workAdapter = createChooserGridAdapter(                  /* context */ this,                  /* payloadIntents */ mIntents,                  selectedProfile == PROFILE_WORK ? initialIntents : null,                  rList,                  filterLastUsed, -                /* userHandle */ getWorkProfileUserHandle()); +                /* userHandle */ getWorkProfileUserHandle(), +                targetDataLoader);          return new ChooserMultiProfilePagerAdapter(                  /* context */ this,                  personalAdapter, @@ -1183,7 +1192,8 @@ public class ChooserActivity extends ResolverActivity implements              Intent[] initialIntents,              List<ResolveInfo> rList,              boolean filterLastUsed, -            UserHandle userHandle) { +            UserHandle userHandle, +            TargetDataLoader targetDataLoader) {          ChooserListAdapter chooserListAdapter = createChooserListAdapter(                  context,                  payloadIntents, @@ -1194,7 +1204,8 @@ public class ChooserActivity extends ResolverActivity implements                  userHandle,                  getTargetIntent(),                  mChooserRequest, -                mMaxTargetsPerRow); +                mMaxTargetsPerRow, +                targetDataLoader);          return new ChooserGridAdapter(                  context, @@ -1252,7 +1263,8 @@ public class ChooserActivity extends ResolverActivity implements              UserHandle userHandle,              Intent targetIntent,              ChooserRequestParameters chooserRequest, -            int maxTargetsPerRow) { +            int maxTargetsPerRow, +            TargetDataLoader targetDataLoader) {          UserHandle initialIntentsUserSpace = isLaunchedAsCloneProfile()                  && userHandle.equals(getPersonalProfileUserHandle())                  ? getCloneProfileUserHandle() : userHandle; @@ -1270,7 +1282,8 @@ public class ChooserActivity extends ResolverActivity implements                  getChooserActivityLogger(),                  chooserRequest,                  maxTargetsPerRow, -                initialIntentsUserSpace); +                initialIntentsUserSpace, +                targetDataLoader);      }      @Override diff --git a/java/src/com/android/intentresolver/ChooserListAdapter.java b/java/src/com/android/intentresolver/ChooserListAdapter.java index c20af20c..b1fa16b0 100644 --- a/java/src/com/android/intentresolver/ChooserListAdapter.java +++ b/java/src/com/android/intentresolver/ChooserListAdapter.java @@ -27,14 +27,10 @@ import android.content.Context;  import android.content.Intent;  import android.content.pm.ActivityInfo;  import android.content.pm.LabeledIntent; -import android.content.pm.LauncherApps;  import android.content.pm.PackageManager;  import android.content.pm.ResolveInfo;  import android.content.pm.ShortcutInfo; -import android.graphics.Bitmap; -import android.graphics.drawable.BitmapDrawable;  import android.graphics.drawable.Drawable; -import android.graphics.drawable.Icon;  import android.os.AsyncTask;  import android.os.Trace;  import android.os.UserHandle; @@ -47,20 +43,20 @@ import android.view.View;  import android.view.ViewGroup;  import android.widget.TextView; -import androidx.annotation.WorkerThread; -  import com.android.intentresolver.chooser.DisplayResolveInfo;  import com.android.intentresolver.chooser.MultiDisplayResolveInfo;  import com.android.intentresolver.chooser.NotSelectableTargetInfo;  import com.android.intentresolver.chooser.SelectableTargetInfo;  import com.android.intentresolver.chooser.TargetInfo; +import com.android.intentresolver.icons.TargetDataLoader;  import com.android.internal.annotations.VisibleForTesting;  import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;  import java.util.ArrayList; -import java.util.HashMap; +import java.util.HashSet;  import java.util.List;  import java.util.Map; +import java.util.Set;  import java.util.stream.Collectors;  public class ChooserListAdapter extends ResolverListAdapter { @@ -86,10 +82,11 @@ public class ChooserListAdapter extends ResolverListAdapter {      private final ChooserActivityLogger mChooserActivityLogger; -    private final Map<TargetInfo, AsyncTask> mIconLoaders = new HashMap<>(); +    private final Set<TargetInfo> mRequestedIcons = new HashSet<>();      // Reserve spots for incoming direct share targets by adding placeholders      private final TargetInfo mPlaceHolderTargetInfo; +    private final TargetDataLoader mTargetDataLoader;      private final List<TargetInfo> mServiceTargets = new ArrayList<>();      private final List<DisplayResolveInfo> mCallerTargets = new ArrayList<>(); @@ -145,7 +142,8 @@ public class ChooserListAdapter extends ResolverListAdapter {              ChooserActivityLogger chooserActivityLogger,              ChooserRequestParameters chooserRequest,              int maxRankedTargets, -            UserHandle initialIntentsUserSpace) { +            UserHandle initialIntentsUserSpace, +            TargetDataLoader targetDataLoader) {          // Don't send the initial intents through the shared ResolverActivity path,          // we want to separate them into a different section.          super( @@ -158,13 +156,14 @@ public class ChooserListAdapter extends ResolverListAdapter {                  userHandle,                  targetIntent,                  resolverListCommunicator, -                false, -                initialIntentsUserSpace); +                initialIntentsUserSpace, +                targetDataLoader);          mChooserRequest = chooserRequest;          mMaxRankedTargets = maxRankedTargets;          mPlaceHolderTargetInfo = NotSelectableTargetInfo.newPlaceHolderTargetInfo(context); +        mTargetDataLoader = targetDataLoader;          createPlaceHolders();          mChooserActivityLogger = chooserActivityLogger;          mShortcutSelectionLogic = new ShortcutSelectionLogic( @@ -227,8 +226,9 @@ public class ChooserListAdapter extends ResolverListAdapter {                      ri.icon = 0;                  }                  ri.userHandle = initialIntentsUserSpace; +                // TODO: remove DisplayResolveInfo dependency on presentation getter                  DisplayResolveInfo displayResolveInfo = DisplayResolveInfo.newDisplayResolveInfo( -                        ii, ri, ii, mPresentationFactory.makePresentationGetter(ri)); +                        ii, ri, ii, mTargetDataLoader.createPresentationGetter(ri));                  mCallerTargets.add(displayResolveInfo);                  if (mCallerTargets.size() == MAX_SUGGESTED_APP_TARGETS) break;              } @@ -344,19 +344,19 @@ public class ChooserListAdapter extends ResolverListAdapter {      }      private void loadDirectShareIcon(SelectableTargetInfo info) { -        LoadDirectShareIconTask task = (LoadDirectShareIconTask) mIconLoaders.get(info); -        if (task == null) { -            task = createLoadDirectShareIconTask(info); -            mIconLoaders.put(info, task); -            task.loadIcon(); +        if (mRequestedIcons.add(info)) { +            mTargetDataLoader.loadDirectShareIcon( +                    info, +                    getUserHandle(), +                    (drawable) -> onDirectShareIconLoaded(info, drawable));          }      } -    @VisibleForTesting -    protected LoadDirectShareIconTask createLoadDirectShareIconTask(SelectableTargetInfo info) { -        return new LoadDirectShareIconTask( -                mContext.createContextAsUser(getUserHandle(), 0), -                info); +    private void onDirectShareIconLoaded(SelectableTargetInfo mTargetInfo, Drawable icon) { +        if (icon != null && !mTargetInfo.hasDisplayIcon()) { +            mTargetInfo.getDisplayIconHolder().setDisplayIcon(icon); +            notifyDataSetChanged(); +        }      }      void updateAlphabeticalList() { @@ -365,6 +365,15 @@ public class ChooserListAdapter extends ResolverListAdapter {          new AsyncTask<Void, Void, List<DisplayResolveInfo>>() {              @Override              protected List<DisplayResolveInfo> doInBackground(Void... voids) { +                try { +                    Trace.beginSection("update-alphabetical-list"); +                    return updateList(); +                } finally { +                    Trace.endSection(); +                } +            } + +            private List<DisplayResolveInfo> updateList() {                  List<DisplayResolveInfo> allTargets = new ArrayList<>();                  allTargets.addAll(getTargetsInCurrentDisplayList());                  allTargets.addAll(mCallerTargets); @@ -660,98 +669,4 @@ public class ChooserListAdapter extends ResolverListAdapter {          };      } -    /** -     * Loads direct share targets icons. -     */ -    @VisibleForTesting -    public class LoadDirectShareIconTask extends AsyncTask<Void, Void, Drawable> { -        private final Context mContext; -        private final SelectableTargetInfo mTargetInfo; - -        private LoadDirectShareIconTask(Context context, SelectableTargetInfo targetInfo) { -            mContext = context; -            mTargetInfo = targetInfo; -        } - -        @Override -        protected Drawable doInBackground(Void... voids) { -            Drawable drawable; -            Trace.beginSection("shortcut-icon"); -            try { -                drawable = getChooserTargetIconDrawable( -                        mContext, -                        mTargetInfo.getChooserTargetIcon(), -                        mTargetInfo.getChooserTargetComponentName(), -                        mTargetInfo.getDirectShareShortcutInfo()); -            } catch (Exception e) { -                Log.e(TAG, -                        "Failed to load shortcut icon for " -                                + mTargetInfo.getChooserTargetComponentName(), -                        e); -                drawable = loadIconPlaceholder(); -            } finally { -                Trace.endSection(); -            } -            return drawable; -        } - -        @Override -        protected void onPostExecute(@Nullable Drawable icon) { -            if (icon != null && !mTargetInfo.hasDisplayIcon()) { -                mTargetInfo.getDisplayIconHolder().setDisplayIcon(icon); -                notifyDataSetChanged(); -            } -        } - -        @WorkerThread -        private Drawable getChooserTargetIconDrawable( -                Context context, -                @Nullable Icon icon, -                ComponentName targetComponentName, -                @Nullable ShortcutInfo shortcutInfo) { -            Drawable directShareIcon = null; - -            // First get the target drawable and associated activity info -            if (icon != null) { -                directShareIcon = icon.loadDrawable(context); -            } else if (shortcutInfo != null) { -                LauncherApps launcherApps = context.getSystemService(LauncherApps.class); -                if (launcherApps != null) { -                    directShareIcon = launcherApps.getShortcutIconDrawable(shortcutInfo, 0); -                } -            } - -            if (directShareIcon == null) { -                return null; -            } - -            ActivityInfo info = null; -            try { -                info = context.getPackageManager().getActivityInfo(targetComponentName, 0); -            } catch (PackageManager.NameNotFoundException error) { -                Log.e(TAG, "Could not find activity associated with ChooserTarget"); -            } - -            if (info == null) { -                return null; -            } - -            // Now fetch app icon and raster with no badging even in work profile -            Bitmap appIcon = mPresentationFactory.makePresentationGetter(info).getIconBitmap(null); - -            // Raster target drawable with appIcon as a badge -            SimpleIconFactory sif = SimpleIconFactory.obtain(context); -            Bitmap directShareBadgedIcon = sif.createAppBadgedIconBitmap(directShareIcon, appIcon); -            sif.recycle(); - -            return new BitmapDrawable(context.getResources(), directShareBadgedIcon); -        } - -        /** -         * An alias for execute to use with unit tests. -         */ -        public void loadIcon() { -            execute(); -        } -    }  } diff --git a/java/src/com/android/intentresolver/ResolverActivity.java b/java/src/com/android/intentresolver/ResolverActivity.java index ac3b9a61..57871532 100644 --- a/java/src/com/android/intentresolver/ResolverActivity.java +++ b/java/src/com/android/intentresolver/ResolverActivity.java @@ -60,7 +60,6 @@ import android.content.pm.UserInfo;  import android.content.res.Configuration;  import android.content.res.TypedArray;  import android.graphics.Insets; -import android.graphics.drawable.Drawable;  import android.net.Uri;  import android.os.Build;  import android.os.Bundle; @@ -108,6 +107,8 @@ import com.android.intentresolver.AbstractMultiProfilePagerAdapter.Profile;  import com.android.intentresolver.NoCrossProfileEmptyStateProvider.DevicePolicyBlockerEmptyState;  import com.android.intentresolver.chooser.DisplayResolveInfo;  import com.android.intentresolver.chooser.TargetInfo; +import com.android.intentresolver.icons.DefaultTargetDataLoader; +import com.android.intentresolver.icons.TargetDataLoader;  import com.android.intentresolver.model.ResolverRankerServiceResolverComparator;  import com.android.intentresolver.widget.ResolverDrawerLayout;  import com.android.internal.annotations.VisibleForTesting; @@ -333,7 +334,7 @@ public class ResolverActivity extends FragmentActivity implements          setSafeForwardingMode(true); -        onCreate(savedInstanceState, intent, null, 0, null, null, true); +        onCreate(savedInstanceState, intent, null, 0, null, null, true, createIconLoader());      }      /** @@ -343,13 +344,26 @@ public class ResolverActivity extends FragmentActivity implements      protected void onCreate(Bundle savedInstanceState, Intent intent,              CharSequence title, Intent[] initialIntents,              List<ResolveInfo> rList, boolean supportsAlwaysUseOption) { -        onCreate(savedInstanceState, intent, title, 0, initialIntents, rList, -                supportsAlwaysUseOption); +        onCreate( +                savedInstanceState, +                intent, +                title, +                0, +                initialIntents, +                rList, +                supportsAlwaysUseOption, +                createIconLoader());      } -    protected void onCreate(Bundle savedInstanceState, Intent intent, -            CharSequence title, int defaultTitleRes, Intent[] initialIntents, -            List<ResolveInfo> rList, boolean supportsAlwaysUseOption) { +    protected void onCreate( +            Bundle savedInstanceState, +            Intent intent, +            CharSequence title, +            int defaultTitleRes, +            Intent[] initialIntents, +            List<ResolveInfo> rList, +            boolean supportsAlwaysUseOption, +            TargetDataLoader targetDataLoader) {          setTheme(appliedThemeResId());          super.onCreate(savedInstanceState); @@ -384,8 +398,9 @@ public class ResolverActivity extends FragmentActivity implements          // provide any more information to help us select between them.          boolean filterLastUsed = mSupportsAlwaysUseOption && !isVoiceInteraction()                  && !shouldShowTabs() && !hasCloneProfile(); -        mMultiProfilePagerAdapter = createMultiProfilePagerAdapter(initialIntents, rList, filterLastUsed); -        if (configureContentView()) { +        mMultiProfilePagerAdapter = createMultiProfilePagerAdapter( +                initialIntents, rList, filterLastUsed, targetDataLoader); +        if (configureContentView(targetDataLoader)) {              return;          } @@ -441,15 +456,16 @@ public class ResolverActivity extends FragmentActivity implements      protected AbstractMultiProfilePagerAdapter createMultiProfilePagerAdapter(              Intent[] initialIntents,              List<ResolveInfo> rList, -            boolean filterLastUsed) { +            boolean filterLastUsed, +            TargetDataLoader targetDataLoader) {          AbstractMultiProfilePagerAdapter resolverMultiProfilePagerAdapter = null;          if (shouldShowTabs()) {              resolverMultiProfilePagerAdapter =                      createResolverMultiProfilePagerAdapterForTwoProfiles( -                            initialIntents, rList, filterLastUsed); +                            initialIntents, rList, filterLastUsed, targetDataLoader);          } else {              resolverMultiProfilePagerAdapter = createResolverMultiProfilePagerAdapterForOneProfile( -                    initialIntents, rList, filterLastUsed); +                    initialIntents, rList, filterLastUsed, targetDataLoader);          }          return resolverMultiProfilePagerAdapter;      } @@ -1023,12 +1039,14 @@ public class ResolverActivity extends FragmentActivity implements      // @NonFinalForTesting      @VisibleForTesting -    protected ResolverListAdapter createResolverListAdapter(Context context, -            List<Intent> payloadIntents, Intent[] initialIntents, List<ResolveInfo> rList, -            boolean filterLastUsed, UserHandle userHandle) { -        Intent startIntent = getIntent(); -        boolean isAudioCaptureDevice = -                startIntent.getBooleanExtra(EXTRA_IS_AUDIO_CAPTURE_DEVICE, false); +    protected ResolverListAdapter createResolverListAdapter( +            Context context, +            List<Intent> payloadIntents, +            Intent[] initialIntents, +            List<ResolveInfo> rList, +            boolean filterLastUsed, +            UserHandle userHandle, +            TargetDataLoader targetDataLoader) {          UserHandle initialIntentsUserSpace = isLaunchedAsCloneProfile()                  && userHandle.equals(getPersonalProfileUserHandle())                  ? getCloneProfileUserHandle() : userHandle; @@ -1042,8 +1060,15 @@ public class ResolverActivity extends FragmentActivity implements                  userHandle,                  getTargetIntent(),                  this, -                isAudioCaptureDevice, -                initialIntentsUserSpace); +                initialIntentsUserSpace, +                targetDataLoader); +    } + +    private TargetDataLoader createIconLoader() { +        Intent startIntent = getIntent(); +        boolean isAudioCaptureDevice = +                startIntent.getBooleanExtra(EXTRA_IS_AUDIO_CAPTURE_DEVICE, false); +        return new DefaultTargetDataLoader(this, getLifecycle(), isAudioCaptureDevice);      }      private LatencyTracker getLatencyTracker() { @@ -1118,14 +1143,16 @@ public class ResolverActivity extends FragmentActivity implements              createResolverMultiProfilePagerAdapterForOneProfile(                      Intent[] initialIntents,                      List<ResolveInfo> rList, -                    boolean filterLastUsed) { +                    boolean filterLastUsed, +                    TargetDataLoader targetDataLoader) {          ResolverListAdapter adapter = createResolverListAdapter(                  /* context */ this,                  /* payloadIntents */ mIntents,                  initialIntents,                  rList,                  filterLastUsed, -                /* userHandle */ getPersonalProfileUserHandle()); +                /* userHandle */ getPersonalProfileUserHandle(), +                targetDataLoader);          return new ResolverMultiProfilePagerAdapter(                  /* context */ this,                  adapter, @@ -1144,7 +1171,8 @@ public class ResolverActivity extends FragmentActivity implements      private ResolverMultiProfilePagerAdapter createResolverMultiProfilePagerAdapterForTwoProfiles(              Intent[] initialIntents,              List<ResolveInfo> rList, -            boolean filterLastUsed) { +            boolean filterLastUsed, +            TargetDataLoader targetDataLoader) {          // In the edge case when we have 0 apps in the current profile and >1 apps in the other,          // the intent resolver is started in the other profile. Since this is the only case when          // this happens, we check for it here and set the current profile's tab. @@ -1172,7 +1200,8 @@ public class ResolverActivity extends FragmentActivity implements                  rList,                  (filterLastUsed && UserHandle.myUserId()                          == getPersonalProfileUserHandle().getIdentifier()), -                /* userHandle */ getPersonalProfileUserHandle()); +                /* userHandle */ getPersonalProfileUserHandle(), +                targetDataLoader);          UserHandle workProfileUserHandle = getWorkProfileUserHandle();          ResolverListAdapter workAdapter = createResolverListAdapter(                  /* context */ this, @@ -1181,7 +1210,8 @@ public class ResolverActivity extends FragmentActivity implements                  rList,                  (filterLastUsed && UserHandle.myUserId()                          == workProfileUserHandle.getIdentifier()), -                /* userHandle */ workProfileUserHandle); +                /* userHandle */ workProfileUserHandle, +                targetDataLoader);          return new ResolverMultiProfilePagerAdapter(                  /* context */ this,                  personalAdapter, @@ -1698,7 +1728,7 @@ public class ResolverActivity extends FragmentActivity implements       * Sets up the content view.       * @return <code>true</code> if the activity is finishing and creation should halt.       */ -    private boolean configureContentView() { +    private boolean configureContentView(TargetDataLoader targetDataLoader) {          if (mMultiProfilePagerAdapter.getActiveListAdapter() == null) {              throw new IllegalStateException("mMultiProfilePagerAdapter.getCurrentListAdapter() "                      + "cannot be null."); @@ -1715,7 +1745,7 @@ public class ResolverActivity extends FragmentActivity implements          }          if (shouldUseMiniResolver()) { -            configureMiniResolverContent(); +            configureMiniResolverContent(targetDataLoader);              Trace.endSection();              return false;          } @@ -1738,7 +1768,7 @@ public class ResolverActivity extends FragmentActivity implements       * and asks the user if they'd like to open that cross-profile app or use the in-profile       * browser.       */ -    private void configureMiniResolverContent() { +    private void configureMiniResolverContent(TargetDataLoader targetDataLoader) {          mLayoutId = R.layout.miniresolver;          setContentView(mLayoutId); @@ -1753,15 +1783,15 @@ public class ResolverActivity extends FragmentActivity implements          // Load the icon asynchronously          ImageView icon = findViewById(com.android.internal.R.id.icon); -        inactiveAdapter.new LoadIconTask(otherProfileResolveInfo) { -            @Override -            protected void onPostExecute(Drawable drawable) { -                if (!isDestroyed()) { -                    otherProfileResolveInfo.getDisplayIconHolder().setDisplayIcon(drawable); -                    new ResolverListAdapter.ViewHolder(icon).bindIcon(otherProfileResolveInfo); -                } -            } -        }.execute(); +        targetDataLoader.loadAppTargetIcon( +                otherProfileResolveInfo, +                inactiveAdapter.getUserHandle(), +                (drawable) -> { +                    if (!isDestroyed()) { +                        otherProfileResolveInfo.getDisplayIconHolder().setDisplayIcon(drawable); +                        new ResolverListAdapter.ViewHolder(icon).bindIcon(otherProfileResolveInfo); +                    } +                });          ((TextView) findViewById(com.android.internal.R.id.open_cross_profile)).setText(                  getResources().getString( diff --git a/java/src/com/android/intentresolver/ResolverListAdapter.java b/java/src/com/android/intentresolver/ResolverListAdapter.java index a5fdd320..282a672f 100644 --- a/java/src/com/android/intentresolver/ResolverListAdapter.java +++ b/java/src/com/android/intentresolver/ResolverListAdapter.java @@ -16,15 +16,10 @@  package com.android.intentresolver; -import static android.content.Context.ACTIVITY_SERVICE; -  import android.annotation.NonNull;  import android.annotation.Nullable; -import android.app.ActivityManager; -import android.content.ComponentName;  import android.content.Context;  import android.content.Intent; -import android.content.PermissionChecker;  import android.content.pm.ActivityInfo;  import android.content.pm.LabeledIntent;  import android.content.pm.PackageManager; @@ -49,15 +44,15 @@ import android.widget.TextView;  import com.android.intentresolver.chooser.DisplayResolveInfo;  import com.android.intentresolver.chooser.TargetInfo; +import com.android.intentresolver.icons.TargetDataLoader;  import com.android.internal.annotations.VisibleForTesting;  import com.google.common.collect.ImmutableList;  import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; +import java.util.HashSet;  import java.util.List; -import java.util.Map; +import java.util.Set;  public class ResolverListAdapter extends BaseAdapter {      private static final String TAG = "ResolverListAdapter"; @@ -69,30 +64,28 @@ public class ResolverListAdapter extends BaseAdapter {      protected final LayoutInflater mInflater;      protected final ResolverListCommunicator mResolverListCommunicator;      protected final ResolverListController mResolverListController; -    protected final TargetPresentationGetter.Factory mPresentationFactory;      private final List<Intent> mIntents;      private final Intent[] mInitialIntents;      private final List<ResolveInfo> mBaseResolveList;      private final PackageManager mPm; -    private final int mIconDpi; -    private final boolean mIsAudioCaptureDevice; +    private final TargetDataLoader mTargetDataLoader;      private final UserHandle mUserHandle;      private final Intent mTargetIntent; -    private final Map<DisplayResolveInfo, LoadIconTask> mIconLoaders = new HashMap<>(); -    private final Map<DisplayResolveInfo, LoadLabelTask> mLabelLoaders = new HashMap<>(); +    private final Set<DisplayResolveInfo> mRequestedIcons = new HashSet<>(); +    private final Set<DisplayResolveInfo> mRequestedLabels = new HashSet<>();      private ResolveInfo mLastChosen;      private DisplayResolveInfo mOtherProfile;      private int mPlaceholderCount;      // This one is the list that the Adapter will actually present. -    private List<DisplayResolveInfo> mDisplayList; +    private final List<DisplayResolveInfo> mDisplayList;      private List<ResolvedComponentInfo> mUnfilteredResolveList;      private int mLastChosenPosition = -1; -    private boolean mFilterLastUsed; +    private final boolean mFilterLastUsed;      private Runnable mPostListReadyRunnable;      private boolean mIsTabLoaded;      // Represents the UserSpace in which the Initial Intents should be resolved. @@ -108,24 +101,21 @@ public class ResolverListAdapter extends BaseAdapter {              UserHandle userHandle,              Intent targetIntent,              ResolverListCommunicator resolverListCommunicator, -            boolean isAudioCaptureDevice, -            UserHandle initialIntentsUserSpace) { +            UserHandle initialIntentsUserSpace, +            TargetDataLoader targetDataLoader) {          mContext = context;          mIntents = payloadIntents;          mInitialIntents = initialIntents;          mBaseResolveList = rList;          mInflater = LayoutInflater.from(context);          mPm = context.getPackageManager(); +        mTargetDataLoader = targetDataLoader;          mDisplayList = new ArrayList<>();          mFilterLastUsed = filterLastUsed;          mResolverListController = resolverListController;          mUserHandle = userHandle;          mTargetIntent = targetIntent;          mResolverListCommunicator = resolverListCommunicator; -        mIsAudioCaptureDevice = isAudioCaptureDevice; -        final ActivityManager am = (ActivityManager) mContext.getSystemService(ACTIVITY_SERVICE); -        mIconDpi = am.getLauncherLargeIconDensity(); -        mPresentationFactory = new TargetPresentationGetter.Factory(mContext, mIconDpi);          mInitialIntentsUserSpace = initialIntentsUserSpace;      } @@ -364,12 +354,11 @@ public class ResolverListAdapter extends BaseAdapter {          if (otherProfileInfo != null) {              mOtherProfile = makeOtherProfileDisplayResolveInfo( -                    mContext,                      otherProfileInfo,                      mPm,                      mTargetIntent,                      mResolverListCommunicator, -                    mIconDpi); +                    mTargetDataLoader);          } else {              mOtherProfile = null;              try { @@ -483,7 +472,7 @@ public class ResolverListAdapter extends BaseAdapter {                              ri.loadLabel(mPm),                              null,                              ii, -                            mPresentationFactory.makePresentationGetter(ri))); +                            mTargetDataLoader.createPresentationGetter(ri)));                  }              } @@ -536,7 +525,7 @@ public class ResolverListAdapter extends BaseAdapter {                  intent,                  add,                  (replaceIntent != null) ? replaceIntent : defaultIntent, -                mPresentationFactory.makePresentationGetter(add)); +                mTargetDataLoader.createPresentationGetter(add));          dri.setPinned(rci.isPinned());          if (rci.isPinned()) {              Log.i(TAG, "Pinned item: " + rci.name); @@ -704,25 +693,37 @@ public class ResolverListAdapter extends BaseAdapter {      }      protected final void loadIcon(DisplayResolveInfo info) { -        LoadIconTask task = mIconLoaders.get(info); -        if (task == null) { -            task = new LoadIconTask(info); -            mIconLoaders.put(info, task); -            task.execute(); +        if (mRequestedIcons.add(info)) { +            mTargetDataLoader.loadAppTargetIcon( +                    info, +                    getUserHandle(), +                    (drawable) -> onIconLoaded(info, drawable)); +        } +    } + +    private void onIconLoaded(DisplayResolveInfo displayResolveInfo, Drawable drawable) { +        if (getOtherProfile() == displayResolveInfo) { +            mResolverListCommunicator.updateProfileViewButton(); +        } else if (!displayResolveInfo.hasDisplayIcon()) { +            displayResolveInfo.getDisplayIconHolder().setDisplayIcon(drawable); +            notifyDataSetChanged();          }      }      private void loadLabel(DisplayResolveInfo info) { -        LoadLabelTask task = mLabelLoaders.get(info); -        if (task == null) { -            task = createLoadLabelTask(info); -            mLabelLoaders.put(info, task); -            task.execute(); +        if (mRequestedLabels.add(info)) { +            mTargetDataLoader.loadLabel(info, (result) -> onLabelLoaded(info, result));          }      } -    protected LoadLabelTask createLoadLabelTask(DisplayResolveInfo info) { -        return new LoadLabelTask(info); +    protected final void onLabelLoaded( +            DisplayResolveInfo displayResolveInfo, CharSequence[] result) { +        if (displayResolveInfo.hasDisplayLabel()) { +            return; +        } +        displayResolveInfo.setDisplayLabel(result[0]); +        displayResolveInfo.setExtendedInfo(result[1]); +        notifyDataSetChanged();      }      public void onDestroy() { @@ -733,16 +734,8 @@ public class ResolverListAdapter extends BaseAdapter {          if (mResolverListController != null) {              mResolverListController.destroy();          } -        cancelTasks(mIconLoaders.values()); -        cancelTasks(mLabelLoaders.values()); -        mIconLoaders.clear(); -        mLabelLoaders.clear(); -    } - -    private <T extends AsyncTask> void cancelTasks(Collection<T> tasks) { -        for (T task: tasks) { -            task.cancel(false); -        } +        mRequestedIcons.clear(); +        mRequestedLabels.clear();      }      private static ColorMatrixColorFilter getSuspendedColorMatrix() { @@ -768,39 +761,15 @@ public class ResolverListAdapter extends BaseAdapter {          return sSuspendedMatrixColorFilter;      } -    Drawable loadIconForResolveInfo(ResolveInfo ri) { -        // Load icons based on userHandle from ResolveInfo. If in work profile/clone profile, icons -        // should be badged. -        return mPresentationFactory.makePresentationGetter(ri) -                .getIcon(ResolverActivity.getResolveInfoUserHandle(ri, getUserHandle())); -    } -      protected final Drawable loadIconPlaceholder() {          return mContext.getDrawable(R.drawable.resolver_icon_placeholder);      }      void loadFilteredItemIconTaskAsync(@NonNull ImageView iconView) {          final DisplayResolveInfo iconInfo = getFilteredItem(); -        if (iconView != null && iconInfo != null) { -            new AsyncTask<Void, Void, Drawable>() { -                @Override -                protected Drawable doInBackground(Void... params) { -                    Drawable drawable; -                    try { -                        drawable = loadIconForResolveInfo(iconInfo.getResolveInfo()); -                    } catch (Exception e) { -                        ComponentName componentName = iconInfo.getResolvedComponentName(); -                        Log.e(TAG, "Failed to load app icon for " + componentName, e); -                        drawable = loadIconPlaceholder(); -                    } -                    return drawable; -                } - -                @Override -                protected void onPostExecute(Drawable d) { -                    iconView.setImageDrawable(d); -                } -            }.execute(); +        if (iconInfo != null) { +            mTargetDataLoader.loadAppTargetIcon( +                    iconInfo, getUserHandle(), iconView::setImageDrawable);          }      } @@ -856,12 +825,11 @@ public class ResolverListAdapter extends BaseAdapter {       * of an element in the resolve list).       */      private static DisplayResolveInfo makeOtherProfileDisplayResolveInfo( -            Context context,              ResolvedComponentInfo resolvedComponentInfo,              PackageManager pm,              Intent targetIntent,              ResolverListCommunicator resolverListCommunicator, -            int iconDpi) { +            TargetDataLoader targetDataLoader) {          ResolveInfo resolveInfo = resolvedComponentInfo.getResolveInfoAt(0);          Intent pOrigIntent = resolverListCommunicator.getReplacementIntent( @@ -871,8 +839,7 @@ public class ResolverListAdapter extends BaseAdapter {                  resolveInfo.activityInfo, targetIntent);          TargetPresentationGetter presentationGetter = -                new TargetPresentationGetter.Factory(context, iconDpi) -                .makePresentationGetter(resolveInfo); +                targetDataLoader.createPresentationGetter(resolveInfo);          return DisplayResolveInfo.newDisplayResolveInfo(                  resolvedComponentInfo.getIntentAt(0), @@ -971,89 +938,4 @@ public class ResolverListAdapter extends BaseAdapter {              }          }      } - -    protected class LoadLabelTask extends AsyncTask<Void, Void, CharSequence[]> { -        private final DisplayResolveInfo mDisplayResolveInfo; - -        protected LoadLabelTask(DisplayResolveInfo dri) { -            mDisplayResolveInfo = dri; -        } - -        @Override -        protected CharSequence[] doInBackground(Void... voids) { -            TargetPresentationGetter pg = mPresentationFactory.makePresentationGetter( -                    mDisplayResolveInfo.getResolveInfo()); - -            if (mIsAudioCaptureDevice) { -                // This is an audio capture device, so check record permissions -                ActivityInfo activityInfo = mDisplayResolveInfo.getResolveInfo().activityInfo; -                String packageName = activityInfo.packageName; - -                int uid = activityInfo.applicationInfo.uid; -                boolean hasRecordPermission = -                        PermissionChecker.checkPermissionForPreflight( -                                mContext, -                                android.Manifest.permission.RECORD_AUDIO, -1, uid, -                                packageName) -                                == android.content.pm.PackageManager.PERMISSION_GRANTED; - -                if (!hasRecordPermission) { -                    // Doesn't have record permission, so warn the user -                    return new CharSequence[] { -                            pg.getLabel(), -                            mContext.getString(R.string.usb_device_resolve_prompt_warn) -                    }; -                } -            } - -            return new CharSequence[] { -                    pg.getLabel(), -                    pg.getSubLabel() -            }; -        } - -        @Override -        protected void onPostExecute(CharSequence[] result) { -            if (mDisplayResolveInfo.hasDisplayLabel()) { -                return; -            } -            mDisplayResolveInfo.setDisplayLabel(result[0]); -            mDisplayResolveInfo.setExtendedInfo(result[1]); -            notifyDataSetChanged(); -        } -    } - -    class LoadIconTask extends AsyncTask<Void, Void, Drawable> { -        protected final DisplayResolveInfo mDisplayResolveInfo; -        private final ResolveInfo mResolveInfo; - -        LoadIconTask(DisplayResolveInfo dri) { -            mDisplayResolveInfo = dri; -            mResolveInfo = dri.getResolveInfo(); -        } - -        @Override -        protected Drawable doInBackground(Void... params) { -            Trace.beginSection("app-icon"); -            try { -                return loadIconForResolveInfo(mResolveInfo); -            } catch (Exception e) { -                ComponentName componentName = mDisplayResolveInfo.getResolvedComponentName(); -                Log.e(TAG, "Failed to load app icon for " + componentName, e); -                return loadIconPlaceholder(); -            } finally { -                Trace.endSection(); -            } -        } - -        @Override -        protected void onPostExecute(Drawable d) { -            if (getOtherProfile() == mDisplayResolveInfo) { -                mResolverListCommunicator.updateProfileViewButton(); -            } else if (!mDisplayResolveInfo.hasDisplayIcon()) { -                mDisplayResolveInfo.getDisplayIconHolder().setDisplayIcon(d); -                notifyDataSetChanged(); -            } -        } -    }  } diff --git a/java/src/com/android/intentresolver/icons/BaseLoadIconTask.java b/java/src/com/android/intentresolver/icons/BaseLoadIconTask.java new file mode 100644 index 00000000..2eceb89c --- /dev/null +++ b/java/src/com/android/intentresolver/icons/BaseLoadIconTask.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.intentresolver.icons; + +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.os.AsyncTask; + +import com.android.intentresolver.R; +import com.android.intentresolver.TargetPresentationGetter; + +import java.util.function.Consumer; + +abstract class BaseLoadIconTask extends AsyncTask<Void, Void, Drawable> { +    protected final Context mContext; +    protected final TargetPresentationGetter.Factory mPresentationFactory; +    private final Consumer<Drawable> mCallback; + +    BaseLoadIconTask( +            Context context, +            TargetPresentationGetter.Factory presentationFactory, +            Consumer<Drawable> callback) { +        mContext = context; +        mPresentationFactory = presentationFactory; +        mCallback = callback; +    } + +    protected final Drawable loadIconPlaceholder() { +        return mContext.getDrawable(R.drawable.resolver_icon_placeholder); +    } + +    @Override +    protected final void onPostExecute(Drawable d) { +        mCallback.accept(d); +    } +} diff --git a/java/src/com/android/intentresolver/icons/DefaultTargetDataLoader.kt b/java/src/com/android/intentresolver/icons/DefaultTargetDataLoader.kt new file mode 100644 index 00000000..0414dea7 --- /dev/null +++ b/java/src/com/android/intentresolver/icons/DefaultTargetDataLoader.kt @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.intentresolver.icons + +import android.app.ActivityManager +import android.content.Context +import android.content.pm.ResolveInfo +import android.graphics.drawable.Drawable +import android.os.AsyncTask +import android.os.UserHandle +import android.util.SparseArray +import androidx.annotation.GuardedBy +import androidx.lifecycle.DefaultLifecycleObserver +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleOwner +import com.android.intentresolver.TargetPresentationGetter +import com.android.intentresolver.chooser.DisplayResolveInfo +import com.android.intentresolver.chooser.SelectableTargetInfo +import java.util.concurrent.atomic.AtomicInteger +import java.util.function.Consumer +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.asExecutor + +/** An actual [TargetDataLoader] implementation. */ +// TODO: replace async tasks with coroutines. +class DefaultTargetDataLoader( +    private val context: Context, +    private val lifecycle: Lifecycle, +    private val isAudioCaptureDevice: Boolean, +) : TargetDataLoader() { +    private val presentationFactory = +        TargetPresentationGetter.Factory( +            context, +            context.getSystemService(ActivityManager::class.java)?.launcherLargeIconDensity +                ?: error("Unable to access ActivityManager") +        ) +    private val nextTaskId = AtomicInteger(0) +    @GuardedBy("self") private val activeTasks = SparseArray<AsyncTask<*, *, *>>() +    private val executor = Dispatchers.IO.asExecutor() + +    init { +        lifecycle.addObserver( +            object : DefaultLifecycleObserver { +                override fun onDestroy(owner: LifecycleOwner) { +                    lifecycle.removeObserver(this) +                    destroy() +                } +            } +        ) +    } + +    override fun loadAppTargetIcon( +        info: DisplayResolveInfo, +        userHandle: UserHandle, +        callback: Consumer<Drawable>, +    ) { +        val taskId = nextTaskId.getAndIncrement() +        LoadIconTask(context, info, userHandle, presentationFactory) { result -> +                removeTask(taskId) +                callback.accept(result) +            } +            .also { addTask(taskId, it) } +            .executeOnExecutor(executor) +    } + +    override fun loadDirectShareIcon( +        info: SelectableTargetInfo, +        userHandle: UserHandle, +        callback: Consumer<Drawable>, +    ) { +        val taskId = nextTaskId.getAndIncrement() +        LoadDirectShareIconTask( +                context.createContextAsUser(userHandle, 0), +                info, +                userHandle, +                presentationFactory, +            ) { result -> +                removeTask(taskId) +                callback.accept(result) +            } +            .also { addTask(taskId, it) } +            .executeOnExecutor(executor) +    } + +    override fun loadLabel(info: DisplayResolveInfo, callback: Consumer<Array<CharSequence?>>) { +        val taskId = nextTaskId.getAndIncrement() +        LoadLabelTask(context, info, isAudioCaptureDevice, presentationFactory) { result -> +                removeTask(taskId) +                callback.accept(result) +            } +            .also { addTask(taskId, it) } +            .executeOnExecutor(executor) +    } + +    override fun createPresentationGetter(info: ResolveInfo): TargetPresentationGetter = +        presentationFactory.makePresentationGetter(info) + +    private fun addTask(id: Int, task: AsyncTask<*, *, *>) { +        synchronized(activeTasks) { activeTasks.put(id, task) } +    } + +    private fun removeTask(id: Int) { +        synchronized(activeTasks) { activeTasks.remove(id) } +    } + +    private fun destroy() { +        synchronized(activeTasks) { +            for (i in 0 until activeTasks.size()) { +                activeTasks.valueAt(i).cancel(false) +            } +            activeTasks.clear() +        } +    } +} diff --git a/java/src/com/android/intentresolver/icons/LoadDirectShareIconTask.java b/java/src/com/android/intentresolver/icons/LoadDirectShareIconTask.java new file mode 100644 index 00000000..b7bacc90 --- /dev/null +++ b/java/src/com/android/intentresolver/icons/LoadDirectShareIconTask.java @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.intentresolver.icons; + +import android.annotation.Nullable; +import android.content.ComponentName; +import android.content.Context; +import android.content.pm.ActivityInfo; +import android.content.pm.LauncherApps; +import android.content.pm.PackageManager; +import android.content.pm.ShortcutInfo; +import android.graphics.Bitmap; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.Icon; +import android.os.Trace; +import android.os.UserHandle; +import android.util.Log; + +import androidx.annotation.WorkerThread; + +import com.android.intentresolver.SimpleIconFactory; +import com.android.intentresolver.TargetPresentationGetter; +import com.android.intentresolver.chooser.SelectableTargetInfo; + +import java.util.function.Consumer; + +/** + * Loads direct share targets icons. + */ +class LoadDirectShareIconTask extends BaseLoadIconTask { +    private static final String TAG = "DirectShareIconTask"; +    private final SelectableTargetInfo mTargetInfo; + +    LoadDirectShareIconTask( +            Context context, +            SelectableTargetInfo targetInfo, +            UserHandle userHandle, +            TargetPresentationGetter.Factory presentationFactory, +            Consumer<Drawable> callback) { +        super(context, presentationFactory, callback); +        mTargetInfo = targetInfo; +    } + +    @Override +    protected Drawable doInBackground(Void... voids) { +        Drawable drawable; +        Trace.beginSection("shortcut-icon"); +        try { +            drawable = getChooserTargetIconDrawable( +                    mContext, +                    mTargetInfo.getChooserTargetIcon(), +                    mTargetInfo.getChooserTargetComponentName(), +                    mTargetInfo.getDirectShareShortcutInfo()); +        } catch (Exception e) { +            Log.e( +                    TAG, +                    "Failed to load shortcut icon for " +                            + mTargetInfo.getChooserTargetComponentName(), +                    e); +            drawable = loadIconPlaceholder(); +        } finally { +            Trace.endSection(); +        } +        return drawable; +    } + +    @WorkerThread +    private Drawable getChooserTargetIconDrawable( +            Context context, +            @Nullable Icon icon, +            ComponentName targetComponentName, +            @Nullable ShortcutInfo shortcutInfo) { +        Drawable directShareIcon = null; + +        // First get the target drawable and associated activity info +        if (icon != null) { +            directShareIcon = icon.loadDrawable(context); +        } else if (shortcutInfo != null) { +            LauncherApps launcherApps = context.getSystemService(LauncherApps.class); +            if (launcherApps != null) { +                directShareIcon = launcherApps.getShortcutIconDrawable(shortcutInfo, 0); +            } +        } + +        if (directShareIcon == null) { +            return null; +        } + +        ActivityInfo info = null; +        try { +            info = context.getPackageManager().getActivityInfo(targetComponentName, 0); +        } catch (PackageManager.NameNotFoundException error) { +            Log.e(TAG, "Could not find activity associated with ChooserTarget"); +        } + +        if (info == null) { +            return null; +        } + +        // Now fetch app icon and raster with no badging even in work profile +        Bitmap appIcon = mPresentationFactory.makePresentationGetter(info).getIconBitmap(null); + +        // Raster target drawable with appIcon as a badge +        SimpleIconFactory sif = SimpleIconFactory.obtain(context); +        Bitmap directShareBadgedIcon = sif.createAppBadgedIconBitmap(directShareIcon, appIcon); +        sif.recycle(); + +        return new BitmapDrawable(context.getResources(), directShareBadgedIcon); +    } +} diff --git a/java/src/com/android/intentresolver/icons/LoadIconTask.java b/java/src/com/android/intentresolver/icons/LoadIconTask.java new file mode 100644 index 00000000..37ce4093 --- /dev/null +++ b/java/src/com/android/intentresolver/icons/LoadIconTask.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.intentresolver.icons; + +import android.content.ComponentName; +import android.content.Context; +import android.content.pm.ResolveInfo; +import android.graphics.drawable.Drawable; +import android.os.Trace; +import android.os.UserHandle; +import android.util.Log; + +import com.android.intentresolver.ResolverActivity; +import com.android.intentresolver.TargetPresentationGetter; +import com.android.intentresolver.chooser.DisplayResolveInfo; + +import java.util.function.Consumer; + +class LoadIconTask extends BaseLoadIconTask { +    private static final String TAG = "IconTask"; +    protected final DisplayResolveInfo mDisplayResolveInfo; +    private final UserHandle mUserHandle; +    private final ResolveInfo mResolveInfo; + +    LoadIconTask( +            Context context, DisplayResolveInfo dri, +            UserHandle userHandle, +            TargetPresentationGetter.Factory presentationFactory, +            Consumer<Drawable> callback) { +        super(context, presentationFactory, callback); +        mUserHandle = userHandle; +        mDisplayResolveInfo = dri; +        mResolveInfo = dri.getResolveInfo(); +    } + +    @Override +    protected Drawable doInBackground(Void... params) { +        Trace.beginSection("app-icon"); +        try { +            return loadIconForResolveInfo(mResolveInfo); +        } catch (Exception e) { +            ComponentName componentName = mDisplayResolveInfo.getResolvedComponentName(); +            Log.e(TAG, "Failed to load app icon for " + componentName, e); +            return loadIconPlaceholder(); +        } finally { +            Trace.endSection(); +        } +    } + +    protected final Drawable loadIconForResolveInfo(ResolveInfo ri) { +        // Load icons based on userHandle from ResolveInfo. If in work profile/clone profile, icons +        // should be badged. +        return mPresentationFactory.makePresentationGetter(ri) +                .getIcon(ResolverActivity.getResolveInfoUserHandle(ri, mUserHandle)); +    } + +} diff --git a/java/src/com/android/intentresolver/icons/LoadLabelTask.java b/java/src/com/android/intentresolver/icons/LoadLabelTask.java new file mode 100644 index 00000000..a0867b8e --- /dev/null +++ b/java/src/com/android/intentresolver/icons/LoadLabelTask.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.intentresolver.icons; + +import android.content.Context; +import android.content.PermissionChecker; +import android.content.pm.ActivityInfo; +import android.os.AsyncTask; +import android.os.Trace; + +import com.android.intentresolver.R; +import com.android.intentresolver.TargetPresentationGetter; +import com.android.intentresolver.chooser.DisplayResolveInfo; + +import java.util.function.Consumer; + +class LoadLabelTask extends AsyncTask<Void, Void, CharSequence[]> { +    private final Context mContext; +    private final DisplayResolveInfo mDisplayResolveInfo; +    private final boolean mIsAudioCaptureDevice; +    protected final TargetPresentationGetter.Factory mPresentationFactory; +    private final Consumer<CharSequence[]> mCallback; + +    LoadLabelTask(Context context, DisplayResolveInfo dri, +            boolean isAudioCaptureDevice, TargetPresentationGetter.Factory presentationFactory, +            Consumer<CharSequence[]> callback) { +        mContext = context; +        mDisplayResolveInfo = dri; +        mIsAudioCaptureDevice = isAudioCaptureDevice; +        mPresentationFactory = presentationFactory; +        mCallback = callback; +    } + +    @Override +    protected CharSequence[] doInBackground(Void... voids) { +        try { +            Trace.beginSection("app-label"); +            return loadLabel(); +        } finally { +            Trace.endSection(); +        } +    } + +    private CharSequence[] loadLabel() { +        TargetPresentationGetter pg = mPresentationFactory.makePresentationGetter( +                mDisplayResolveInfo.getResolveInfo()); + +        if (mIsAudioCaptureDevice) { +            // This is an audio capture device, so check record permissions +            ActivityInfo activityInfo = mDisplayResolveInfo.getResolveInfo().activityInfo; +            String packageName = activityInfo.packageName; + +            int uid = activityInfo.applicationInfo.uid; +            boolean hasRecordPermission = +                    PermissionChecker.checkPermissionForPreflight( +                            mContext, +                            android.Manifest.permission.RECORD_AUDIO, -1, uid, +                            packageName) +                            == android.content.pm.PackageManager.PERMISSION_GRANTED; + +            if (!hasRecordPermission) { +                // Doesn't have record permission, so warn the user +                return new CharSequence[]{ +                        pg.getLabel(), +                        mContext.getString(R.string.usb_device_resolve_prompt_warn) +                }; +            } +        } + +        return new CharSequence[]{ +                pg.getLabel(), +                pg.getSubLabel() +        }; +    } + +    @Override +    protected void onPostExecute(CharSequence[] result) { +        mCallback.accept(result); +    } +} diff --git a/java/src/com/android/intentresolver/icons/TargetDataLoader.kt b/java/src/com/android/intentresolver/icons/TargetDataLoader.kt new file mode 100644 index 00000000..50f731f8 --- /dev/null +++ b/java/src/com/android/intentresolver/icons/TargetDataLoader.kt @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.intentresolver.icons + +import android.content.pm.ResolveInfo +import android.graphics.drawable.Drawable +import android.os.UserHandle +import com.android.intentresolver.TargetPresentationGetter +import com.android.intentresolver.chooser.DisplayResolveInfo +import com.android.intentresolver.chooser.SelectableTargetInfo +import java.util.function.Consumer + +/** A target data loader contract. Added to support testing. */ +abstract class TargetDataLoader { +    /** Load an app target icon */ +    abstract fun loadAppTargetIcon( +        info: DisplayResolveInfo, +        userHandle: UserHandle, +        callback: Consumer<Drawable>, +    ) + +    /** Load a shortcut icon */ +    abstract fun loadDirectShareIcon( +        info: SelectableTargetInfo, +        userHandle: UserHandle, +        callback: Consumer<Drawable>, +    ) + +    /** Load target label */ +    abstract fun loadLabel(info: DisplayResolveInfo, callback: Consumer<Array<CharSequence?>>) + +    /** Create a presentation getter to be used with a [DisplayResolveInfo] */ +    // TODO: get rid of DisplayResolveInfo's dependency on the presentation getter and remove this +    //  method. +    abstract fun createPresentationGetter(info: ResolveInfo): TargetPresentationGetter +} diff --git a/java/tests/src/com/android/intentresolver/ChooserListAdapterTest.kt b/java/tests/src/com/android/intentresolver/ChooserListAdapterTest.kt index 9504f377..4612b430 100644 --- a/java/tests/src/com/android/intentresolver/ChooserListAdapterTest.kt +++ b/java/tests/src/com/android/intentresolver/ChooserListAdapterTest.kt @@ -27,10 +27,10 @@ import android.widget.ImageView  import android.widget.TextView  import androidx.test.ext.junit.runners.AndroidJUnit4  import androidx.test.platform.app.InstrumentationRegistry -import com.android.intentresolver.ChooserListAdapter.LoadDirectShareIconTask  import com.android.intentresolver.chooser.DisplayResolveInfo  import com.android.intentresolver.chooser.SelectableTargetInfo  import com.android.intentresolver.chooser.TargetInfo +import com.android.intentresolver.icons.TargetDataLoader  import com.android.internal.R  import org.junit.Before  import org.junit.Test @@ -40,47 +40,43 @@ import org.mockito.Mockito.verify  @RunWith(AndroidJUnit4::class)  class ChooserListAdapterTest { -    private val PERSONAL_USER_HANDLE: UserHandle = InstrumentationRegistry -            .getInstrumentation().getTargetContext().getUser() +    private val userHandle: UserHandle = +        InstrumentationRegistry.getInstrumentation().targetContext.user -    private val packageManager = mock<PackageManager> { -        whenever( -            resolveActivity(any(), any<ResolveInfoFlags>()) -        ).thenReturn(mock()) -    } -    private val context = InstrumentationRegistry.getInstrumentation().getContext() +    private val packageManager = +        mock<PackageManager> { +            whenever(resolveActivity(any(), any<ResolveInfoFlags>())).thenReturn(mock()) +        } +    private val context = InstrumentationRegistry.getInstrumentation().context      private val resolverListController = mock<ResolverListController>()      private val chooserActivityLogger = mock<ChooserActivityLogger>() +    private val mTargetDataLoader = mock<TargetDataLoader>() -    private fun createChooserListAdapter( -        taskProvider: (TargetInfo?) -> LoadDirectShareIconTask -    ) = object : ChooserListAdapter( +    private val testSubject by lazy { +        ChooserListAdapter(              context,              emptyList(),              emptyArray(),              emptyList(),              false,              resolverListController, -            null, +            userHandle,              Intent(),              mock(),              packageManager,              chooserActivityLogger,              mock(),              0, -            null -        ) { -            override fun createLoadDirectShareIconTask( -                info: SelectableTargetInfo -            ): LoadDirectShareIconTask = taskProvider(info) -        } +            null, +            mTargetDataLoader +        ) +    }      @Before      fun setup() {          // ChooserListAdapter reads DeviceConfig and needs a permission for that. -        InstrumentationRegistry -            .getInstrumentation() -            .getUiAutomation() +        InstrumentationRegistry.getInstrumentation() +            .uiAutomation              .adoptShellPermissionIdentity("android.permission.READ_DEVICE_CONFIG")      } @@ -90,41 +86,56 @@ class ChooserListAdapterTest {          val viewHolder = ResolverListAdapter.ViewHolder(view)          view.tag = viewHolder          val targetInfo = createSelectableTargetInfo() -        val iconTask = mock<LoadDirectShareIconTask>() -        val testSubject = createChooserListAdapter { iconTask }          testSubject.onBindView(view, targetInfo, 0) -        verify(iconTask, times(1)).loadIcon() +        verify(mTargetDataLoader, times(1)).loadDirectShareIcon(any(), any(), any())      }      @Test -    fun testOnlyOneTaskPerTarget() { +    fun onBindView_DirectShareTargetIconAndLabelLoadedOnlyOnce() {          val view = createView()          val viewHolderOne = ResolverListAdapter.ViewHolder(view)          view.tag = viewHolderOne          val targetInfo = createSelectableTargetInfo() -        val iconTaskOne = mock<LoadDirectShareIconTask>() -        val testTaskProvider = mock<() -> LoadDirectShareIconTask> { -            whenever(invoke()).thenReturn(iconTaskOne) -        } -        val testSubject = createChooserListAdapter { testTaskProvider.invoke() }          testSubject.onBindView(view, targetInfo, 0)          val viewHolderTwo = ResolverListAdapter.ViewHolder(view)          view.tag = viewHolderTwo -        whenever(testTaskProvider()).thenReturn(mock())          testSubject.onBindView(view, targetInfo, 0) -        verify(iconTaskOne, times(1)).loadIcon() -        verify(testTaskProvider, times(1)).invoke() +        verify(mTargetDataLoader, times(1)).loadDirectShareIcon(any(), any(), any()) +    } + +    @Test +    fun onBindView_AppTargetIconAndLabelLoadedOnlyOnce() { +        val view = createView() +        val viewHolderOne = ResolverListAdapter.ViewHolder(view) +        view.tag = viewHolderOne +        val targetInfo = +            DisplayResolveInfo.newDisplayResolveInfo( +                Intent(), +                ResolverDataProvider.createResolveInfo(2, 0, userHandle), +                null, +                "extended info", +                Intent(), +                /* resolveInfoPresentationGetter= */ null +            ) +        testSubject.onBindView(view, targetInfo, 0) + +        val viewHolderTwo = ResolverListAdapter.ViewHolder(view) +        view.tag = viewHolderTwo + +        testSubject.onBindView(view, targetInfo, 0) + +        verify(mTargetDataLoader, times(1)).loadAppTargetIcon(any(), any(), any())      }      private fun createSelectableTargetInfo(): TargetInfo =          SelectableTargetInfo.newSelectableTargetInfo(              /* sourceInfo = */ DisplayResolveInfo.newDisplayResolveInfo(                  Intent(), -                ResolverDataProvider.createResolveInfo(2, 0, PERSONAL_USER_HANDLE), +                ResolverDataProvider.createResolveInfo(2, 0, userHandle),                  "label",                  "extended info",                  Intent(), @@ -133,7 +144,10 @@ class ChooserListAdapterTest {              /* backupResolveInfo = */ mock(),              /* resolvedIntent = */ Intent(),              /* chooserTarget = */ createChooserTarget( -                "Target", 0.5f, ComponentName("pkg", "Class"), "id-1" +                "Target", +                0.5f, +                ComponentName("pkg", "Class"), +                "id-1"              ),              /* modifiedScore = */ 1f,              /* shortcutInfo = */ createShortcutInfo("id-1", ComponentName("pkg", "Class"), 1), diff --git a/java/tests/src/com/android/intentresolver/ChooserWrapperActivity.java b/java/tests/src/com/android/intentresolver/ChooserWrapperActivity.java index fa934f87..6ac6b6d3 100644 --- a/java/tests/src/com/android/intentresolver/ChooserWrapperActivity.java +++ b/java/tests/src/com/android/intentresolver/ChooserWrapperActivity.java @@ -39,6 +39,7 @@ import com.android.intentresolver.chooser.DisplayResolveInfo;  import com.android.intentresolver.chooser.TargetInfo;  import com.android.intentresolver.flags.FeatureFlagRepository;  import com.android.intentresolver.grid.ChooserGridAdapter; +import com.android.intentresolver.icons.TargetDataLoader;  import com.android.intentresolver.shortcuts.ShortcutLoader;  import com.android.internal.logging.nano.MetricsProto.MetricsEvent; @@ -72,7 +73,8 @@ public class ChooserWrapperActivity              UserHandle userHandle,              Intent targetIntent,              ChooserRequestParameters chooserRequest, -            int maxTargetsPerRow) { +            int maxTargetsPerRow, +            TargetDataLoader targetDataLoader) {          PackageManager packageManager =                  sOverrides.packageManager == null ? context.getPackageManager()                          : sOverrides.packageManager; @@ -90,7 +92,8 @@ public class ChooserWrapperActivity                  getChooserActivityLogger(),                  chooserRequest,                  maxTargetsPerRow, -                userHandle); +                userHandle, +                targetDataLoader);      }      @Override diff --git a/java/tests/src/com/android/intentresolver/ResolverActivityTest.java b/java/tests/src/com/android/intentresolver/ResolverActivityTest.java index 31c0a498..7233fd3d 100644 --- a/java/tests/src/com/android/intentresolver/ResolverActivityTest.java +++ b/java/tests/src/com/android/intentresolver/ResolverActivityTest.java @@ -109,7 +109,7 @@ public class ResolverActivityTest {          setupResolverControllers(resolvedComponentInfos);          final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent); -        Espresso.registerIdlingResources(activity.getAdapter().getLabelIdlingResource()); +        Espresso.registerIdlingResources(activity.getLabelIdlingResource());          waitForIdle();          assertThat(activity.getAdapter().getCount(), is(2)); @@ -246,7 +246,7 @@ public class ResolverActivityTest {          ResolveInfo toChoose = personalResolvedComponentInfos.get(1).getResolveInfoAt(0);          Intent sendIntent = createSendImageIntent();          final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent); -        Espresso.registerIdlingResources(activity.getAdapter().getLabelIdlingResource()); +        Espresso.registerIdlingResources(activity.getLabelIdlingResource());          waitForIdle();          // The other entry is filtered to the last used slot @@ -280,7 +280,7 @@ public class ResolverActivityTest {          setupResolverControllers(resolvedComponentInfos);          final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent); -        Espresso.registerIdlingResources(activity.getAdapter().getLabelIdlingResource()); +        Espresso.registerIdlingResources(activity.getLabelIdlingResource());          waitForIdle();          // The other entry is filtered to the other profile slot @@ -321,7 +321,7 @@ public class ResolverActivityTest {                  .thenReturn(resolvedComponentInfos.get(1).getResolveInfoAt(0));          final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent); -        Espresso.registerIdlingResources(activity.getAdapter().getLabelIdlingResource()); +        Espresso.registerIdlingResources(activity.getLabelIdlingResource());          waitForIdle();          // The other entry is filtered to the other profile slot @@ -782,7 +782,7 @@ public class ResolverActivityTest {                  .thenReturn(resolvedComponentInfos.get(1).getResolveInfoAt(0));          final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent); -        Espresso.registerIdlingResources(activity.getAdapter().getLabelIdlingResource()); +        Espresso.registerIdlingResources(activity.getLabelIdlingResource());          waitForIdle();          // The other entry is filtered to the last used slot @@ -848,7 +848,7 @@ public class ResolverActivityTest {                  .thenReturn(resolvedComponentInfos.get(0).getResolveInfoAt(0));          final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent); -        Espresso.registerIdlingResources(activity.getAdapter().getLabelIdlingResource()); +        Espresso.registerIdlingResources(activity.getLabelIdlingResource());          waitForIdle();          assertThat(activity.getAdapter().hasFilteredItem(), is(false)); diff --git a/java/tests/src/com/android/intentresolver/ResolverWrapperActivity.java b/java/tests/src/com/android/intentresolver/ResolverWrapperActivity.java index 645e8c72..401ede26 100644 --- a/java/tests/src/com/android/intentresolver/ResolverWrapperActivity.java +++ b/java/tests/src/com/android/intentresolver/ResolverWrapperActivity.java @@ -22,19 +22,26 @@ import static org.mockito.Mockito.mock;  import static org.mockito.Mockito.when;  import android.annotation.Nullable; -import android.app.usage.UsageStatsManager;  import android.content.Context;  import android.content.Intent;  import android.content.pm.PackageManager;  import android.content.pm.ResolveInfo; +import android.graphics.drawable.Drawable;  import android.os.Bundle;  import android.os.UserHandle;  import android.util.Pair; +import androidx.annotation.NonNull; +import androidx.test.espresso.idling.CountingIdlingResource; +  import com.android.intentresolver.AbstractMultiProfilePagerAdapter.CrossProfileIntentsChecker; +import com.android.intentresolver.chooser.DisplayResolveInfo; +import com.android.intentresolver.chooser.SelectableTargetInfo;  import com.android.intentresolver.chooser.TargetInfo; +import com.android.intentresolver.icons.TargetDataLoader;  import java.util.List; +import java.util.function.Consumer;  import java.util.function.Function;  /* @@ -42,7 +49,9 @@ import java.util.function.Function;   */  public class ResolverWrapperActivity extends ResolverActivity {      static final OverrideData sOverrides = new OverrideData(); -    private UsageStatsManager mUsm; + +    private final CountingIdlingResource mLabelIdlingResource = +            new CountingIdlingResource("LoadLabelTask");      public ResolverWrapperActivity() {          super(/* isIntentPicker= */ true); @@ -55,11 +64,20 @@ public class ResolverWrapperActivity extends ResolverActivity {          return 1234;      } +    public CountingIdlingResource getLabelIdlingResource() { +        return mLabelIdlingResource; +    } +      @Override -    public ResolverListAdapter createResolverListAdapter(Context context, -            List<Intent> payloadIntents, Intent[] initialIntents, List<ResolveInfo> rList, -            boolean filterLastUsed, UserHandle userHandle) { -        return new ResolverWrapperAdapter( +    public ResolverListAdapter createResolverListAdapter( +            Context context, +            List<Intent> payloadIntents, +            Intent[] initialIntents, +            List<ResolveInfo> rList, +            boolean filterLastUsed, +            UserHandle userHandle, +            TargetDataLoader targetDataLoader) { +        return new ResolverListAdapter(                  context,                  payloadIntents,                  initialIntents, @@ -69,7 +87,8 @@ public class ResolverWrapperActivity extends ResolverActivity {                  userHandle,                  payloadIntents.get(0),  // TODO: extract upstream                  this, -                userHandle); +                userHandle, +                new TargetDataLoaderWrapper(targetDataLoader, mLabelIdlingResource));      }      @Override @@ -88,8 +107,8 @@ public class ResolverWrapperActivity extends ResolverActivity {          return super.createWorkProfileAvailabilityManager();      } -    ResolverWrapperAdapter getAdapter() { -        return (ResolverWrapperAdapter) mMultiProfilePagerAdapter.getActiveListAdapter(); +    ResolverListAdapter getAdapter() { +        return mMultiProfilePagerAdapter.getActiveListAdapter();      }      ResolverListAdapter getPersonalListAdapter() { @@ -226,4 +245,50 @@ public class ResolverWrapperActivity extends ResolverActivity {                      .thenAnswer(invocation -> hasCrossProfileIntents);          }      } + +    private static class TargetDataLoaderWrapper extends TargetDataLoader { +        private final TargetDataLoader mTargetDataLoader; +        private final CountingIdlingResource mLabelIdlingResource; + +        private TargetDataLoaderWrapper( +                TargetDataLoader targetDataLoader, CountingIdlingResource labelIdlingResource) { +            mTargetDataLoader = targetDataLoader; +            mLabelIdlingResource = labelIdlingResource; +        } + +        @Override +        public void loadAppTargetIcon( +                @NonNull DisplayResolveInfo info, +                @NonNull UserHandle userHandle, +                @NonNull Consumer<Drawable> callback) { +            mTargetDataLoader.loadAppTargetIcon(info, userHandle, callback); +        } + +        @Override +        public void loadDirectShareIcon( +                @NonNull SelectableTargetInfo info, +                @NonNull UserHandle userHandle, +                @NonNull Consumer<Drawable> callback) { +            mTargetDataLoader.loadDirectShareIcon(info, userHandle, callback); +        } + +        @Override +        public void loadLabel( +                @NonNull DisplayResolveInfo info, +                @NonNull Consumer<CharSequence[]> callback) { +            mLabelIdlingResource.increment(); +            mTargetDataLoader.loadLabel( +                    info, +                    (result) -> { +                        mLabelIdlingResource.decrement(); +                        callback.accept(result); +                    }); +        } + +        @NonNull +        @Override +        public TargetPresentationGetter createPresentationGetter(@NonNull ResolveInfo info) { +            return mTargetDataLoader.createPresentationGetter(info); +        } +    }  } diff --git a/java/tests/src/com/android/intentresolver/ResolverWrapperAdapter.java b/java/tests/src/com/android/intentresolver/ResolverWrapperAdapter.java deleted file mode 100644 index fd310fd8..00000000 --- a/java/tests/src/com/android/intentresolver/ResolverWrapperAdapter.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - *      http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.intentresolver; - -import android.content.Context; -import android.content.Intent; -import android.content.pm.ResolveInfo; -import android.os.UserHandle; - -import androidx.test.espresso.idling.CountingIdlingResource; - -import com.android.intentresolver.chooser.DisplayResolveInfo; - -import java.util.List; - -public class ResolverWrapperAdapter extends ResolverListAdapter { - -    private CountingIdlingResource mLabelIdlingResource = -            new CountingIdlingResource("LoadLabelTask"); - -    public ResolverWrapperAdapter( -            Context context, -            List<Intent> payloadIntents, -            Intent[] initialIntents, -            List<ResolveInfo> rList, -            boolean filterLastUsed, -            ResolverListController resolverListController, -            UserHandle userHandle, -            Intent targetIntent, -            ResolverListCommunicator resolverListCommunicator, -            UserHandle initialIntentsUserHandle) { -        super( -                context, -                payloadIntents, -                initialIntents, -                rList, -                filterLastUsed, -                resolverListController, -                userHandle, -                targetIntent, -                resolverListCommunicator, -                false, -                initialIntentsUserHandle); -    } - -    public CountingIdlingResource getLabelIdlingResource() { -        return mLabelIdlingResource; -    } - -    @Override -    protected LoadLabelTask createLoadLabelTask(DisplayResolveInfo info) { -        return new LoadLabelWrapperTask(info); -    } - -    class LoadLabelWrapperTask extends LoadLabelTask { - -        protected LoadLabelWrapperTask(DisplayResolveInfo dri) { -            super(dri); -        } - -        @Override -        protected void onPreExecute() { -            mLabelIdlingResource.increment(); -        } - -        @Override -        protected void onPostExecute(CharSequence[] result) { -            super.onPostExecute(result); -            mLabelIdlingResource.decrement(); -        } -    } -}  |