diff options
author | 2023-09-12 14:20:36 -0700 | |
---|---|---|
committer | 2023-09-18 09:59:57 -0700 | |
commit | 1c0f4edb1114be0b7b994d6d7a7e3c6f0c5215d0 (patch) | |
tree | 5c3e2ea23f153b49c227a0a1bac138e67fe3d16b /java/src | |
parent | 41e6f25694939bb4c88485d0513e413da772931b (diff) |
Add ResolverListAdapter unit tests
Replace AsyncTask usage with an background executor and posting on a
main thread hanler to facilitate testing.
Tests are mostly around targets resolution logic in the adapter.
Test: atest IntentResolverUnitTests:ResolverListAdapterTest
Change-Id: I7af047226aa718ca3052aa4284d1e9d2a4c43ded
Diffstat (limited to 'java/src')
5 files changed, 129 insertions, 70 deletions
diff --git a/java/src/com/android/intentresolver/ChooserListAdapter.java b/java/src/com/android/intentresolver/ChooserListAdapter.java index d1101f1e..a9ed983d 100644 --- a/java/src/com/android/intentresolver/ChooserListAdapter.java +++ b/java/src/com/android/intentresolver/ChooserListAdapter.java @@ -43,6 +43,9 @@ import android.view.View; import android.view.ViewGroup; import android.widget.TextView; +import androidx.annotation.MainThread; +import androidx.annotation.WorkerThread; + import com.android.intentresolver.chooser.DisplayResolveInfo; import com.android.intentresolver.chooser.MultiDisplayResolveInfo; import com.android.intentresolver.chooser.NotSelectableTargetInfo; @@ -567,7 +570,7 @@ public class ChooserListAdapter extends ResolverListAdapter { protected boolean shouldAddResolveInfo(DisplayResolveInfo dri) { // Checks if this info is already listed in callerTargets. for (TargetInfo existingInfo : mCallerTargets) { - if (mResolverListCommunicator.resolveInfoMatch( + if (ResolveInfoHelpers.resolveInfoMatch( dri.getResolveInfo(), existingInfo.getResolveInfo())) { return false; } @@ -658,30 +661,23 @@ public class ChooserListAdapter extends ResolverListAdapter { * in the head of input list and fill the tail with other elements in undetermined order. */ @Override - AsyncTask<List<ResolvedComponentInfo>, - Void, - List<ResolvedComponentInfo>> createSortingTask(boolean doPostProcessing) { - return new AsyncTask<List<ResolvedComponentInfo>, - Void, - List<ResolvedComponentInfo>>() { - @Override - protected List<ResolvedComponentInfo> doInBackground( - List<ResolvedComponentInfo>... params) { - Trace.beginSection("ChooserListAdapter#SortingTask"); - mResolverListController.topK(params[0], mMaxRankedTargets); - Trace.endSection(); - return params[0]; - } - - @Override - protected void onPostExecute(List<ResolvedComponentInfo> sortedComponents) { - processSortedList(sortedComponents, doPostProcessing); - if (doPostProcessing) { - mResolverListCommunicator.updateProfileViewButton(); - notifyDataSetChanged(); - } - } - }; + @WorkerThread + protected void sortComponents(List<ResolvedComponentInfo> components) { + Trace.beginSection("ChooserListAdapter#SortingTask"); + mResolverListController.topK(components, mMaxRankedTargets); + Trace.endSection(); } + @Override + @MainThread + protected void onComponentsSorted( + @Nullable List<ResolvedComponentInfo> sortedComponents, boolean doPostProcessing) { + processSortedList(sortedComponents, doPostProcessing); + if (doPostProcessing) { + mResolverListCommunicator.updateProfileViewButton(); + //TODO: this method is different from super's only in that `notifyDataSetChanged` is + // called conditionally here; is it really important? + notifyDataSetChanged(); + } + } } diff --git a/java/src/com/android/intentresolver/ResolverActivity.java b/java/src/com/android/intentresolver/ResolverActivity.java index 0d3becc2..47a8bf2a 100644 --- a/java/src/com/android/intentresolver/ResolverActivity.java +++ b/java/src/com/android/intentresolver/ResolverActivity.java @@ -2262,20 +2262,6 @@ public class ResolverActivity extends FragmentActivity implements mRetainInOnStop = retainInOnStop; } - /** - * Check a simple match for the component of two ResolveInfos. - */ - @Override // ResolverListCommunicator - public final boolean resolveInfoMatch(ResolveInfo lhs, ResolveInfo rhs) { - return lhs == null ? rhs == null - : lhs.activityInfo == null ? rhs.activityInfo == null - : Objects.equals(lhs.activityInfo.name, rhs.activityInfo.name) - && Objects.equals(lhs.activityInfo.packageName, rhs.activityInfo.packageName) - // Comparing against resolveInfo.userHandle in case cloned apps are present, - // as they will have the same activityInfo. - && Objects.equals(lhs.userHandle, rhs.userHandle); - } - private boolean inactiveListAdapterHasItems() { if (!shouldShowTabs()) { return false; diff --git a/java/src/com/android/intentresolver/ResolverInfoHelpers.kt b/java/src/com/android/intentresolver/ResolverInfoHelpers.kt new file mode 100644 index 00000000..8d1d8658 --- /dev/null +++ b/java/src/com/android/intentresolver/ResolverInfoHelpers.kt @@ -0,0 +1,34 @@ +/* + * 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. + */ + +@file:JvmName("ResolveInfoHelpers") + +package com.android.intentresolver + +import android.content.pm.ActivityInfo +import android.content.pm.ResolveInfo + +fun resolveInfoMatch(lhs: ResolveInfo?, rhs: ResolveInfo?): Boolean = + (lhs === rhs) || + ((lhs != null && rhs != null) && + activityInfoMatch(lhs.activityInfo, rhs.activityInfo) && + // Comparing against resolveInfo.userHandle in case cloned apps are present, + // as they will have the same activityInfo. + lhs.userHandle == rhs.userHandle) + +private fun activityInfoMatch(lhs: ActivityInfo?, rhs: ActivityInfo?): Boolean = + (lhs === rhs) || + (lhs != null && rhs != null && lhs.name == rhs.name && lhs.packageName == rhs.packageName) diff --git a/java/src/com/android/intentresolver/ResolverListAdapter.java b/java/src/com/android/intentresolver/ResolverListAdapter.java index 282a672f..14ce0e0e 100644 --- a/java/src/com/android/intentresolver/ResolverListAdapter.java +++ b/java/src/com/android/intentresolver/ResolverListAdapter.java @@ -28,6 +28,7 @@ import android.graphics.ColorMatrix; import android.graphics.ColorMatrixColorFilter; import android.graphics.drawable.Drawable; import android.os.AsyncTask; +import android.os.Handler; import android.os.RemoteException; import android.os.Trace; import android.os.UserHandle; @@ -42,6 +43,9 @@ import android.widget.BaseAdapter; import android.widget.ImageView; import android.widget.TextView; +import androidx.annotation.MainThread; +import androidx.annotation.WorkerThread; + import com.android.intentresolver.chooser.DisplayResolveInfo; import com.android.intentresolver.chooser.TargetInfo; import com.android.intentresolver.icons.TargetDataLoader; @@ -53,6 +57,7 @@ import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.concurrent.Executor; public class ResolverListAdapter extends BaseAdapter { private static final String TAG = "ResolverListAdapter"; @@ -75,6 +80,8 @@ public class ResolverListAdapter extends BaseAdapter { private final Set<DisplayResolveInfo> mRequestedIcons = new HashSet<>(); private final Set<DisplayResolveInfo> mRequestedLabels = new HashSet<>(); + private final Executor mBgExecutor; + private final Handler mMainHandler; private ResolveInfo mLastChosen; private DisplayResolveInfo mOtherProfile; @@ -103,6 +110,37 @@ public class ResolverListAdapter extends BaseAdapter { ResolverListCommunicator resolverListCommunicator, UserHandle initialIntentsUserSpace, TargetDataLoader targetDataLoader) { + this( + context, + payloadIntents, + initialIntents, + rList, + filterLastUsed, + resolverListController, + userHandle, + targetIntent, + resolverListCommunicator, + initialIntentsUserSpace, + targetDataLoader, + AsyncTask.SERIAL_EXECUTOR, + context.getMainThreadHandler()); + } + + @VisibleForTesting + public ResolverListAdapter( + Context context, + List<Intent> payloadIntents, + Intent[] initialIntents, + List<ResolveInfo> rList, + boolean filterLastUsed, + ResolverListController resolverListController, + UserHandle userHandle, + Intent targetIntent, + ResolverListCommunicator resolverListCommunicator, + UserHandle initialIntentsUserSpace, + TargetDataLoader targetDataLoader, + Executor bgExecutor, + Handler mainHandler) { mContext = context; mIntents = payloadIntents; mInitialIntents = initialIntents; @@ -117,6 +155,8 @@ public class ResolverListAdapter extends BaseAdapter { mTargetIntent = targetIntent; mResolverListCommunicator = resolverListCommunicator; mInitialIntentsUserSpace = initialIntentsUserSpace; + mBgExecutor = bgExecutor; + mMainHandler = mainHandler; } public final DisplayResolveInfo getFirstDisplayResolveInfo() { @@ -402,35 +442,42 @@ public class ResolverListAdapter extends BaseAdapter { // Send an "incomplete" list-ready while the async task is running. postListReadyRunnable(doPostProcessing, /* rebuildCompleted */ false); - createSortingTask(doPostProcessing).execute(filteredResolveList); + mBgExecutor.execute(() -> { + List<ResolvedComponentInfo> sortedComponents = null; + //TODO: the try-catch logic here is to formally match the AsyncTask's behavior. + // Empirically, we don't need it as in the case on an exception, the app will crash and + // `onComponentsSorted` won't be invoked. + try { + sortComponents(filteredResolveList); + sortedComponents = filteredResolveList; + } catch (Throwable t) { + Log.e(TAG, "Failed to sort components", t); + throw t; + } finally { + final List<ResolvedComponentInfo> result = sortedComponents; + mMainHandler.post(() -> onComponentsSorted(result, doPostProcessing)); + } + }); return false; } - AsyncTask<List<ResolvedComponentInfo>, - Void, - List<ResolvedComponentInfo>> createSortingTask(boolean doPostProcessing) { - return new AsyncTask<List<ResolvedComponentInfo>, - Void, - List<ResolvedComponentInfo>>() { - @Override - protected List<ResolvedComponentInfo> doInBackground( - List<ResolvedComponentInfo>... params) { - mResolverListController.sort(params[0]); - return params[0]; - } - @Override - protected void onPostExecute(List<ResolvedComponentInfo> sortedComponents) { - processSortedList(sortedComponents, doPostProcessing); - notifyDataSetChanged(); - if (doPostProcessing) { - mResolverListCommunicator.updateProfileViewButton(); - } - } - }; + @WorkerThread + protected void sortComponents(List<ResolvedComponentInfo> components) { + mResolverListController.sort(components); } - protected void processSortedList(List<ResolvedComponentInfo> sortedComponents, - boolean doPostProcessing) { + @MainThread + protected void onComponentsSorted( + @Nullable List<ResolvedComponentInfo> sortedComponents, boolean doPostProcessing) { + processSortedList(sortedComponents, doPostProcessing); + notifyDataSetChanged(); + if (doPostProcessing) { + mResolverListCommunicator.updateProfileViewButton(); + } + } + + protected void processSortedList( + @Nullable List<ResolvedComponentInfo> sortedComponents, boolean doPostProcessing) { final int n = sortedComponents != null ? sortedComponents.size() : 0; Trace.beginSection("ResolverListAdapter#processSortedList:" + n); if (n != 0) { @@ -509,7 +556,7 @@ public class ResolverListAdapter extends BaseAdapter { mPostListReadyRunnable = null; } }; - mContext.getMainThreadHandler().post(mPostListReadyRunnable); + mMainHandler.post(mPostListReadyRunnable); } } @@ -572,7 +619,7 @@ public class ResolverListAdapter extends BaseAdapter { protected boolean shouldAddResolveInfo(DisplayResolveInfo dri) { // Checks if this info is already listed in display. for (DisplayResolveInfo existingInfo : mDisplayList) { - if (mResolverListCommunicator + if (ResolveInfoHelpers .resolveInfoMatch(dri.getResolveInfo(), existingInfo.getResolveInfo())) { return false; } @@ -728,7 +775,7 @@ public class ResolverListAdapter extends BaseAdapter { public void onDestroy() { if (mPostListReadyRunnable != null) { - mContext.getMainThreadHandler().removeCallbacks(mPostListReadyRunnable); + mMainHandler.removeCallbacks(mPostListReadyRunnable); mPostListReadyRunnable = null; } if (mResolverListController != null) { @@ -856,8 +903,6 @@ public class ResolverListAdapter extends BaseAdapter { */ interface ResolverListCommunicator { - boolean resolveInfoMatch(ResolveInfo lhs, ResolveInfo rhs); - Intent getReplacementIntent(ActivityInfo activityInfo, Intent defIntent); void onPostListReady(ResolverListAdapter listAdapter, boolean updateUi, diff --git a/java/src/com/android/intentresolver/ResolverListController.java b/java/src/com/android/intentresolver/ResolverListController.java index d5a5fedf..cb56ab30 100644 --- a/java/src/com/android/intentresolver/ResolverListController.java +++ b/java/src/com/android/intentresolver/ResolverListController.java @@ -254,7 +254,6 @@ public class ResolverListController { isComputed = true; } - @VisibleForTesting @WorkerThread public void sort(List<ResolvedComponentInfo> inputList) { try { @@ -273,7 +272,6 @@ public class ResolverListController { } } - @VisibleForTesting @WorkerThread public void topK(List<ResolvedComponentInfo> inputList, int k) { if (inputList == null || inputList.isEmpty() || k <= 0) { |