Merge "Prototype for Sharesheet direct share row append mechanism" into rvc-dev

This commit is contained in:
Song Hu
2020-03-14 01:09:13 +00:00
committed by Android (Google) Code Review
10 changed files with 242 additions and 13 deletions

View File

@@ -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);

View File

@@ -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
@@ -159,6 +161,15 @@ class AppPredictionServiceResolverComparator extends AbstractResolverComparator
return 1.0f - (((float) rank) / consecutiveSumOfRanks);
}
@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) {

View File

@@ -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);
}
}
@@ -3584,6 +3595,10 @@ public class ChooserActivity extends ResolverActivity implements
? mOriginalTarget.getResolveInfo().activityInfo.toString()
: "<connection destroyed>") + "}";
}
public ComponentName getComponentName() {
return mOriginalTarget.getResolveInfo().activityInfo.getComponentName();
}
}
static class ServiceResultInfo {

View File

@@ -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());
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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) {

View File

@@ -377,6 +377,17 @@ public final class SystemUiDeviceConfigFlags {
public static final String NAV_BAR_HANDLE_SHOW_OVER_LOCKSCREEN =
"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.
*/

View File

@@ -98,6 +98,11 @@ public class AbstractResolverComparatorTest {
@Override
void handleResultMessage(Message message) {}
@Override
List<ComponentName> getTopComponentNames(int topK) {
return null;
}
};
return testComparator;
}

View File

@@ -987,7 +987,8 @@ public class ChooserActivityTest {
/* resolveInfoPresentationGetter */ null),
serviceTargets,
TARGET_TYPE_CHOOSER_TARGET,
directShareToShortcutInfos)
directShareToShortcutInfos,
null)
);
// Thread.sleep shouldn't be a thing in an integration test but it's
@@ -1058,7 +1059,8 @@ public class ChooserActivityTest {
/* resolveInfoPresentationGetter */ null),
serviceTargets,
TARGET_TYPE_CHOOSER_TARGET,
directShareToShortcutInfos)
directShareToShortcutInfos,
null)
);
// Thread.sleep shouldn't be a thing in an integration test but it's
// necessary here because of the way the code is structured
@@ -1145,7 +1147,8 @@ public class ChooserActivityTest {
/* resolveInfoPresentationGetter */ null),
serviceTargets,
TARGET_TYPE_CHOOSER_TARGET,
directShareToShortcutInfos)
directShareToShortcutInfos,
null)
);
// Thread.sleep shouldn't be a thing in an integration test but it's
// necessary here because of the way the code is structured