diff options
| author | Song Hu <songhu@google.com> | 2020-03-09 15:22:29 -0700 |
|---|---|---|
| committer | Song Hu <songhu@google.com> | 2020-03-13 13:52:24 -0700 |
| commit | e2deffd7bf7c5ac026c6854a3ef856d7ad1db2c0 (patch) | |
| tree | a5249483a25381d370bf61ec2e8ac0517237f633 /core/java | |
| parent | ec6198e99dbae638cd683577fc1f074508ccf25e (diff) | |
Prototype for Sharesheet direct share row append mechanism
Use this cmd to enable the feature:
adb shell device_config put systemui append_direct_share_enabled true
Use this cmd to adjust timeout threshold (in millisecond):
adb shell device_config put systemui share_sheet_direct_share_timeout 15000
Bug: 151112858
Test: manually tested both prod flow and prototype flow on phones.
Change-Id: I328ecefc9dffad40ec412c033da54e0443f8889a
Diffstat (limited to 'core/java')
8 files changed, 231 insertions, 10 deletions
diff --git a/core/java/com/android/internal/app/AbstractResolverComparator.java b/core/java/com/android/internal/app/AbstractResolverComparator.java index eb746127a351..e0bbc04515e0 100644 --- a/core/java/com/android/internal/app/AbstractResolverComparator.java +++ b/core/java/com/android/internal/app/AbstractResolverComparator.java @@ -221,6 +221,12 @@ public abstract class AbstractResolverComparator implements Comparator<ResolvedC */ abstract float getScore(ComponentName name); + /** + * Returns the list of top K component names which have highest + * {@link #getScore(ComponentName)} + */ + abstract List<ComponentName> getTopComponentNames(int topK); + /** Handles result message sent to mHandler. */ abstract void handleResultMessage(Message message); diff --git a/core/java/com/android/internal/app/AppPredictionServiceResolverComparator.java b/core/java/com/android/internal/app/AppPredictionServiceResolverComparator.java index 04ad7e9ee8a7..ba8c7b32ad02 100644 --- a/core/java/com/android/internal/app/AppPredictionServiceResolverComparator.java +++ b/core/java/com/android/internal/app/AppPredictionServiceResolverComparator.java @@ -36,7 +36,9 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.concurrent.Executors; +import java.util.stream.Collectors; /** * Uses an {@link AppPredictor} to sort Resolver targets. If the AppPredictionService appears to be @@ -160,6 +162,15 @@ class AppPredictionServiceResolverComparator extends AbstractResolverComparator } @Override + List<ComponentName> getTopComponentNames(int topK) { + return mTargetRanks.entrySet().stream() + .sorted(Entry.comparingByValue()) + .limit(topK) + .map(Entry::getKey) + .collect(Collectors.toList()); + } + + @Override void updateModel(ComponentName componentName) { if (mResolverRankerService != null) { mResolverRankerService.updateModel(componentName); diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java index c487e960854b..5181a70e69aa 100644 --- a/core/java/com/android/internal/app/ChooserActivity.java +++ b/core/java/com/android/internal/app/ChooserActivity.java @@ -223,6 +223,11 @@ public class ChooserActivity extends ResolverActivity implements SystemUiDeviceConfigFlags.HASH_SALT_MAX_DAYS, DEFAULT_SALT_EXPIRATION_DAYS); + private boolean mAppendDirectShareEnabled = DeviceConfig.getBoolean( + DeviceConfig.NAMESPACE_SYSTEMUI, + SystemUiDeviceConfigFlags.APPEND_DIRECT_SHARE_ENABLED, + false); + private Bundle mReplacementExtras; private IntentSender mChosenComponentSender; private IntentSender mRefinementIntentSender; @@ -409,6 +414,11 @@ public class ChooserActivity extends ResolverActivity implements private static final int WATCHDOG_TIMEOUT_MAX_MILLIS = 10000; private static final int WATCHDOG_TIMEOUT_MIN_MILLIS = 3000; + private static final int DEFAULT_DIRECT_SHARE_TIMEOUT_MILLIS = 1500; + private int mDirectShareTimeout = DeviceConfig.getInt(DeviceConfig.NAMESPACE_SYSTEMUI, + SystemUiDeviceConfigFlags.SHARE_SHEET_DIRECT_SHARE_TIMEOUT, + DEFAULT_DIRECT_SHARE_TIMEOUT_MILLIS); + private boolean mMinTimeoutPassed = false; private void removeAllMessages() { @@ -427,15 +437,14 @@ public class ChooserActivity extends ResolverActivity implements if (DEBUG) { Log.d(TAG, "queryTargets setting watchdog timer for " - + WATCHDOG_TIMEOUT_MIN_MILLIS + "-" + + mDirectShareTimeout + "-" + WATCHDOG_TIMEOUT_MAX_MILLIS + "ms"); } sendEmptyMessageDelayed(CHOOSER_TARGET_SERVICE_WATCHDOG_MIN_TIMEOUT, WATCHDOG_TIMEOUT_MIN_MILLIS); sendEmptyMessageDelayed(CHOOSER_TARGET_SERVICE_WATCHDOG_MAX_TIMEOUT, - WATCHDOG_TIMEOUT_MAX_MILLIS); - + mAppendDirectShareEnabled ? mDirectShareTimeout : WATCHDOG_TIMEOUT_MAX_MILLIS); } private void maybeStopServiceRequestTimer() { @@ -463,6 +472,7 @@ public class ChooserActivity extends ResolverActivity implements final ServiceResultInfo sri = (ServiceResultInfo) msg.obj; if (!mServiceConnections.contains(sri.connection)) { Log.w(TAG, "ChooserTargetServiceConnection " + sri.connection + + sri.originalTarget.getResolveInfo().activityInfo.packageName + " returned after being removed from active connections." + " Have you considered returning results faster?"); break; @@ -474,7 +484,7 @@ public class ChooserActivity extends ResolverActivity implements if (adapterForUserHandle != null) { adapterForUserHandle.addServiceResults(sri.originalTarget, sri.resultTargets, TARGET_TYPE_CHOOSER_TARGET, - /* directShareShortcutInfoCache */ null); + /* directShareShortcutInfoCache */ null, mServiceConnections); } } unbindService(sri.connection); @@ -489,6 +499,7 @@ public class ChooserActivity extends ResolverActivity implements break; case CHOOSER_TARGET_SERVICE_WATCHDOG_MAX_TIMEOUT: + mMinTimeoutPassed = true; unbindRemainingServices(); maybeStopServiceRequestTimer(); break; @@ -513,7 +524,7 @@ public class ChooserActivity extends ResolverActivity implements if (adapterForUserHandle != null) { adapterForUserHandle.addServiceResults( resultInfo.originalTarget, resultInfo.resultTargets, msg.arg1, - mDirectShareShortcutInfoCache); + mDirectShareShortcutInfoCache, mServiceConnections); } } break; @@ -1481,7 +1492,7 @@ public class ChooserActivity extends ResolverActivity implements /* origTarget */ null, Lists.newArrayList(mCallerChooserTargets), TARGET_TYPE_DEFAULT, - /* directShareShortcutInfoCache */ null); + /* directShareShortcutInfoCache */ null, mServiceConnections); } } @@ -3567,6 +3578,10 @@ public class ChooserActivity extends ResolverActivity implements ? mOriginalTarget.getResolveInfo().activityInfo.toString() : "<connection destroyed>") + "}"; } + + public ComponentName getComponentName() { + return mOriginalTarget.getResolveInfo().activityInfo.getComponentName(); + } } static class ServiceResultInfo { diff --git a/core/java/com/android/internal/app/ChooserListAdapter.java b/core/java/com/android/internal/app/ChooserListAdapter.java index 74ae29117b59..0ea855a6b7a9 100644 --- a/core/java/com/android/internal/app/ChooserListAdapter.java +++ b/core/java/com/android/internal/app/ChooserListAdapter.java @@ -32,8 +32,10 @@ import android.content.pm.ShortcutInfo; import android.os.AsyncTask; import android.os.UserHandle; import android.os.UserManager; +import android.provider.DeviceConfig; import android.service.chooser.ChooserTarget; import android.util.Log; +import android.util.Pair; import android.view.View; import android.view.ViewGroup; @@ -44,17 +46,26 @@ import com.android.internal.app.chooser.DisplayResolveInfo; import com.android.internal.app.chooser.MultiDisplayResolveInfo; import com.android.internal.app.chooser.SelectableTargetInfo; import com.android.internal.app.chooser.TargetInfo; +import com.android.internal.config.sysui.SystemUiDeviceConfigFlags; import java.util.ArrayList; import java.util.Collections; 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 { private static final String TAG = "ChooserListAdapter"; private static final boolean DEBUG = false; + private boolean mAppendDirectShareEnabled = DeviceConfig.getBoolean( + DeviceConfig.NAMESPACE_SYSTEMUI, + SystemUiDeviceConfigFlags.APPEND_DIRECT_SHARE_ENABLED, + false); + private boolean mEnableStackedApps = true; public static final int NO_POSITION = -1; @@ -84,6 +95,11 @@ public class ChooserListAdapter extends ResolverListAdapter { // Reserve spots for incoming direct share targets by adding placeholders private ChooserTargetInfo mPlaceHolderTargetInfo = new ChooserActivity.PlaceHolderTargetInfo(); + private int mValidServiceTargetsNum = 0; + private final Map<ComponentName, Pair<List<ChooserTargetInfo>, Integer>> + mParkingDirectShareTargets = new HashMap<>(); + private Set<ComponentName> mPendingChooserTargetService = new HashSet<>(); + private Set<ComponentName> mShortcutComponents = new HashSet<>(); private final List<ChooserTargetInfo> mServiceTargets = new ArrayList<>(); private final List<TargetInfo> mCallerTargets = new ArrayList<>(); @@ -189,6 +205,9 @@ public class ChooserListAdapter extends ResolverListAdapter { void refreshListView() { if (mListViewDataChanged) { + if (mAppendDirectShareEnabled) { + appendServiceTargetsWithQuota(); + } super.notifyDataSetChanged(); } mListViewDataChanged = false; @@ -198,6 +217,10 @@ public class ChooserListAdapter extends ResolverListAdapter { private void createPlaceHolders() { mNumShortcutResults = 0; mServiceTargets.clear(); + mValidServiceTargetsNum = 0; + mParkingDirectShareTargets.clear(); + mPendingChooserTargetService.clear(); + mShortcutComponents.clear(); for (int i = 0; i < MAX_SERVICE_TARGETS; i++) { mServiceTargets.add(mPlaceHolderTargetInfo); } @@ -393,12 +416,19 @@ public class ChooserListAdapter extends ResolverListAdapter { */ public void addServiceResults(DisplayResolveInfo origTarget, List<ChooserTarget> targets, @ChooserActivity.ShareTargetType int targetType, - Map<ChooserTarget, ShortcutInfo> directShareToShortcutInfos) { + Map<ChooserTarget, ShortcutInfo> directShareToShortcutInfos, + List<ChooserActivity.ChooserTargetServiceConnection> + pendingChooserTargetServiceConnections) { if (DEBUG) { - Log.d(TAG, "addServiceResults " + origTarget + ", " + targets.size() + Log.d(TAG, "addServiceResults " + origTarget.getResolvedComponentName() + ", " + + targets.size() + " targets"); } - + if (mAppendDirectShareEnabled) { + parkTargetIntoMemory(origTarget, targets, targetType, directShareToShortcutInfos, + pendingChooserTargetServiceConnections); + return; + } if (targets.size() == 0) { return; } @@ -449,6 +479,126 @@ public class ChooserListAdapter extends ResolverListAdapter { } } + /** + * Park {@code targets} into memory for the moment to surface them later when view is refreshed. + * Components pending on ChooserTargetService query are also recorded. + */ + private void parkTargetIntoMemory(DisplayResolveInfo origTarget, List<ChooserTarget> targets, + @ChooserActivity.ShareTargetType int targetType, + Map<ChooserTarget, ShortcutInfo> directShareToShortcutInfos, + List<ChooserActivity.ChooserTargetServiceConnection> + pendingChooserTargetServiceConnections) { + ComponentName origComponentName = origTarget.getResolvedComponentName(); + mPendingChooserTargetService = pendingChooserTargetServiceConnections.stream() + .map(ChooserActivity.ChooserTargetServiceConnection::getComponentName) + .filter(componentName -> !componentName.equals(origComponentName)) + .collect(Collectors.toSet()); + // Park targets in memory + if (!targets.isEmpty() && !mParkingDirectShareTargets.containsKey(origComponentName)) { + final boolean isShortcutResult = + (targetType == TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER + || targetType == TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE); + Context contextAsUser = mContext.createContextAsUser(getUserHandle(), + 0 /* flags */); + List<ChooserTargetInfo> parkingTargetInfos = targets.stream() + .map(target -> + new SelectableTargetInfo( + contextAsUser, origTarget, target, target.getScore(), + mSelectableTargetInfoComunicator, + (isShortcutResult ? directShareToShortcutInfos.get(target) + : null)) + ) + .collect(Collectors.toList()); + mParkingDirectShareTargets.put(origComponentName, + new Pair<>(parkingTargetInfos, 0)); + if (isShortcutResult) { + mShortcutComponents.add(origComponentName); + } + } + notifyDataSetChanged(); + } + + /** + * Append targets of top ranked share app into direct share row with quota limit. Remove + * appended ones from memory. + */ + private void appendServiceTargetsWithQuota() { + int maxRankedTargets = mChooserListCommunicator.getMaxRankedTargets(); + List<ComponentName> topComponentNames = getTopComponentNames(maxRankedTargets); + int appRank = 0; + for (ComponentName component : topComponentNames) { + if (!mPendingChooserTargetService.contains(component) + && !mParkingDirectShareTargets.containsKey(component)) { + continue; + } + appRank++; + Pair<List<ChooserTargetInfo>, Integer> parkingTargetsItem = + mParkingDirectShareTargets.get(component); + if (parkingTargetsItem != null && parkingTargetsItem.second == 0) { + List<ChooserTargetInfo> parkingTargets = parkingTargetsItem.first; + int initTargetsQuota = appRank <= maxRankedTargets / 2 ? 2 : 1; + int insertedNum = 0; + while (insertedNum < initTargetsQuota && !parkingTargets.isEmpty()) { + if (!checkDuplicateTarget(parkingTargets.get(0))) { + mServiceTargets.add(mValidServiceTargetsNum, parkingTargets.get(0)); + mValidServiceTargetsNum++; + insertedNum++; + } + parkingTargets.remove(0); + } + mParkingDirectShareTargets.put(component, new Pair<>(parkingTargets, insertedNum)); + if (mShortcutComponents.contains(component)) { + mNumShortcutResults += insertedNum; + } + } + } + } + + /** + * Append all remaining targets (parking in memory) into direct share row as per their ranking. + */ + private void fillAllServiceTargets() { + int maxRankedTargets = mChooserListCommunicator.getMaxRankedTargets(); + List<ComponentName> topComponentNames = getTopComponentNames(maxRankedTargets); + // Append all remaining targets of top recommended components into direct share row. + for (ComponentName component : topComponentNames) { + if (!mParkingDirectShareTargets.containsKey(component)) { + continue; + } + mParkingDirectShareTargets.get(component).first.stream() + .filter(target -> !checkDuplicateTarget(target)) + .forEach(target -> { + if (mShortcutComponents.contains(component)) { + mNumShortcutResults++; + } + mServiceTargets.add(target); + }); + mParkingDirectShareTargets.remove(component); + } + // Append all remaining shortcuts targets into direct share row. + List<ChooserTargetInfo> remainingTargets = new ArrayList<>(); + mParkingDirectShareTargets.entrySet().stream() + .filter(entry -> mShortcutComponents.contains(entry.getKey())) + .map(entry -> entry.getValue()) + .map(pair -> pair.first) + .forEach(remainingTargets::addAll); + remainingTargets.sort( + (t1, t2) -> -Float.compare(t1.getModifiedScore(), t2.getModifiedScore())); + mServiceTargets.addAll(remainingTargets); + mNumShortcutResults += remainingTargets.size(); + mParkingDirectShareTargets.clear(); + } + + private boolean checkDuplicateTarget(ChooserTargetInfo chooserTargetInfo) { + // Check for duplicates and abort if found + for (ChooserTargetInfo otherTargetInfo : mServiceTargets) { + if (chooserTargetInfo.isSimilar(otherTargetInfo)) { + return true; + } + } + return false; + } + int getNumShortcutResults() { return mNumShortcutResults; } @@ -487,7 +637,9 @@ public class ChooserListAdapter extends ResolverListAdapter { */ public void completeServiceTargetLoading() { mServiceTargets.removeIf(o -> o instanceof ChooserActivity.PlaceHolderTargetInfo); - + if (mAppendDirectShareEnabled) { + fillAllServiceTargets(); + } if (mServiceTargets.isEmpty()) { mServiceTargets.add(new ChooserActivity.EmptyTargetInfo()); } diff --git a/core/java/com/android/internal/app/ResolverListAdapter.java b/core/java/com/android/internal/app/ResolverListAdapter.java index 54453d0d0f46..b06750b72e13 100644 --- a/core/java/com/android/internal/app/ResolverListAdapter.java +++ b/core/java/com/android/internal/app/ResolverListAdapter.java @@ -153,6 +153,14 @@ public class ResolverListAdapter extends BaseAdapter { return mResolverListController.getScore(target); } + /** + * Returns the list of top K component names which have highest + * {@link #getScore(DisplayResolveInfo)} + */ + public List<ComponentName> getTopComponentNames(int topK) { + return mResolverListController.getTopComponentNames(topK); + } + public void updateModel(ComponentName componentName) { mResolverListController.updateModel(componentName); } diff --git a/core/java/com/android/internal/app/ResolverListController.java b/core/java/com/android/internal/app/ResolverListController.java index 022aded188fa..51dce5547890 100644 --- a/core/java/com/android/internal/app/ResolverListController.java +++ b/core/java/com/android/internal/app/ResolverListController.java @@ -367,6 +367,14 @@ public class ResolverListController { return mResolverComparator.getScore(target.getResolvedComponentName()); } + /** + * Returns the list of top K component names which have highest + * {@link #getScore(DisplayResolveInfo)} + */ + public List<ComponentName> getTopComponentNames(int topK) { + return mResolverComparator.getTopComponentNames(topK); + } + public void updateModel(ComponentName componentName) { mResolverComparator.updateModel(componentName); } diff --git a/core/java/com/android/internal/app/ResolverRankerServiceResolverComparator.java b/core/java/com/android/internal/app/ResolverRankerServiceResolverComparator.java index 01e0622063cd..286945037ab7 100644 --- a/core/java/com/android/internal/app/ResolverRankerServiceResolverComparator.java +++ b/core/java/com/android/internal/app/ResolverRankerServiceResolverComparator.java @@ -48,6 +48,7 @@ import java.util.List; import java.util.Map; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; /** * Ranks and compares packages based on usage stats and uses the {@link ResolverRankerService}. @@ -252,6 +253,15 @@ class ResolverRankerServiceResolverComparator extends AbstractResolverComparator return 0; } + @Override + List<ComponentName> getTopComponentNames(int topK) { + return mTargetsDict.entrySet().stream() + .sorted((o1, o2) -> -Float.compare(getScore(o1.getKey()), getScore(o2.getKey()))) + .limit(topK) + .map(Map.Entry::getKey) + .collect(Collectors.toList()); + } + // update ranking model when the connection to it is valid. @Override public void updateModel(ComponentName componentName) { diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java index 086b9d83fed5..837cc466a895 100644 --- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java +++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java @@ -378,6 +378,17 @@ public final class SystemUiDeviceConfigFlags { "nav_bar_handle_show_over_lockscreen"; /** + * (int) Timeout threshold, in millisecond, that Sharesheet waits for direct share targets. + */ + public static final String SHARE_SHEET_DIRECT_SHARE_TIMEOUT = + "share_sheet_direct_share_timeout"; + + /** + * (boolean) Whether append direct share on Sharesheet is enabled. + */ + public static final String APPEND_DIRECT_SHARE_ENABLED = "append_direct_share_enabled"; + + /** * (boolean) Whether to enable user-drag resizing for PIP. */ public static final String PIP_USER_RESIZE = "pip_user_resize"; |
