Merge "Prototype for Sharesheet direct share row append mechanism" into rvc-dev am: e9851d5fad am: a95b4bfc36

Change-Id: Idb7a9ebfbc6a22c3775038fffa5e6bec87cd3322
This commit is contained in:
Automerger Merge Worker
2020-03-14 01:21:29 +00:00
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); 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. */ /** Handles result message sent to mHandler. */
abstract void handleResultMessage(Message message); abstract void handleResultMessage(Message message);

View File

@@ -36,7 +36,9 @@ import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.stream.Collectors;
/** /**
* Uses an {@link AppPredictor} to sort Resolver targets. If the AppPredictionService appears to be * 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); 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 @Override
void updateModel(ComponentName componentName) { void updateModel(ComponentName componentName) {
if (mResolverRankerService != null) { if (mResolverRankerService != null) {

View File

@@ -223,6 +223,11 @@ public class ChooserActivity extends ResolverActivity implements
SystemUiDeviceConfigFlags.HASH_SALT_MAX_DAYS, SystemUiDeviceConfigFlags.HASH_SALT_MAX_DAYS,
DEFAULT_SALT_EXPIRATION_DAYS); DEFAULT_SALT_EXPIRATION_DAYS);
private boolean mAppendDirectShareEnabled = DeviceConfig.getBoolean(
DeviceConfig.NAMESPACE_SYSTEMUI,
SystemUiDeviceConfigFlags.APPEND_DIRECT_SHARE_ENABLED,
false);
private Bundle mReplacementExtras; private Bundle mReplacementExtras;
private IntentSender mChosenComponentSender; private IntentSender mChosenComponentSender;
private IntentSender mRefinementIntentSender; 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_MAX_MILLIS = 10000;
private static final int WATCHDOG_TIMEOUT_MIN_MILLIS = 3000; 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 boolean mMinTimeoutPassed = false;
private void removeAllMessages() { private void removeAllMessages() {
@@ -427,15 +437,14 @@ public class ChooserActivity extends ResolverActivity implements
if (DEBUG) { if (DEBUG) {
Log.d(TAG, "queryTargets setting watchdog timer for " Log.d(TAG, "queryTargets setting watchdog timer for "
+ WATCHDOG_TIMEOUT_MIN_MILLIS + "-" + mDirectShareTimeout + "-"
+ WATCHDOG_TIMEOUT_MAX_MILLIS + "ms"); + WATCHDOG_TIMEOUT_MAX_MILLIS + "ms");
} }
sendEmptyMessageDelayed(CHOOSER_TARGET_SERVICE_WATCHDOG_MIN_TIMEOUT, sendEmptyMessageDelayed(CHOOSER_TARGET_SERVICE_WATCHDOG_MIN_TIMEOUT,
WATCHDOG_TIMEOUT_MIN_MILLIS); WATCHDOG_TIMEOUT_MIN_MILLIS);
sendEmptyMessageDelayed(CHOOSER_TARGET_SERVICE_WATCHDOG_MAX_TIMEOUT, sendEmptyMessageDelayed(CHOOSER_TARGET_SERVICE_WATCHDOG_MAX_TIMEOUT,
WATCHDOG_TIMEOUT_MAX_MILLIS); mAppendDirectShareEnabled ? mDirectShareTimeout : WATCHDOG_TIMEOUT_MAX_MILLIS);
} }
private void maybeStopServiceRequestTimer() { private void maybeStopServiceRequestTimer() {
@@ -463,6 +472,7 @@ public class ChooserActivity extends ResolverActivity implements
final ServiceResultInfo sri = (ServiceResultInfo) msg.obj; final ServiceResultInfo sri = (ServiceResultInfo) msg.obj;
if (!mServiceConnections.contains(sri.connection)) { if (!mServiceConnections.contains(sri.connection)) {
Log.w(TAG, "ChooserTargetServiceConnection " + sri.connection Log.w(TAG, "ChooserTargetServiceConnection " + sri.connection
+ sri.originalTarget.getResolveInfo().activityInfo.packageName
+ " returned after being removed from active connections." + " returned after being removed from active connections."
+ " Have you considered returning results faster?"); + " Have you considered returning results faster?");
break; break;
@@ -474,7 +484,7 @@ public class ChooserActivity extends ResolverActivity implements
if (adapterForUserHandle != null) { if (adapterForUserHandle != null) {
adapterForUserHandle.addServiceResults(sri.originalTarget, adapterForUserHandle.addServiceResults(sri.originalTarget,
sri.resultTargets, TARGET_TYPE_CHOOSER_TARGET, sri.resultTargets, TARGET_TYPE_CHOOSER_TARGET,
/* directShareShortcutInfoCache */ null); /* directShareShortcutInfoCache */ null, mServiceConnections);
} }
} }
unbindService(sri.connection); unbindService(sri.connection);
@@ -489,6 +499,7 @@ public class ChooserActivity extends ResolverActivity implements
break; break;
case CHOOSER_TARGET_SERVICE_WATCHDOG_MAX_TIMEOUT: case CHOOSER_TARGET_SERVICE_WATCHDOG_MAX_TIMEOUT:
mMinTimeoutPassed = true;
unbindRemainingServices(); unbindRemainingServices();
maybeStopServiceRequestTimer(); maybeStopServiceRequestTimer();
break; break;
@@ -513,7 +524,7 @@ public class ChooserActivity extends ResolverActivity implements
if (adapterForUserHandle != null) { if (adapterForUserHandle != null) {
adapterForUserHandle.addServiceResults( adapterForUserHandle.addServiceResults(
resultInfo.originalTarget, resultInfo.resultTargets, msg.arg1, resultInfo.originalTarget, resultInfo.resultTargets, msg.arg1,
mDirectShareShortcutInfoCache); mDirectShareShortcutInfoCache, mServiceConnections);
} }
} }
break; break;
@@ -1481,7 +1492,7 @@ public class ChooserActivity extends ResolverActivity implements
/* origTarget */ null, /* origTarget */ null,
Lists.newArrayList(mCallerChooserTargets), Lists.newArrayList(mCallerChooserTargets),
TARGET_TYPE_DEFAULT, TARGET_TYPE_DEFAULT,
/* directShareShortcutInfoCache */ null); /* directShareShortcutInfoCache */ null, mServiceConnections);
} }
} }
@@ -3584,6 +3595,10 @@ public class ChooserActivity extends ResolverActivity implements
? mOriginalTarget.getResolveInfo().activityInfo.toString() ? mOriginalTarget.getResolveInfo().activityInfo.toString()
: "<connection destroyed>") + "}"; : "<connection destroyed>") + "}";
} }
public ComponentName getComponentName() {
return mOriginalTarget.getResolveInfo().activityInfo.getComponentName();
}
} }
static class ServiceResultInfo { static class ServiceResultInfo {

View File

@@ -32,8 +32,10 @@ import android.content.pm.ShortcutInfo;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.UserHandle; import android.os.UserHandle;
import android.os.UserManager; import android.os.UserManager;
import android.provider.DeviceConfig;
import android.service.chooser.ChooserTarget; import android.service.chooser.ChooserTarget;
import android.util.Log; import android.util.Log;
import android.util.Pair;
import android.view.View; import android.view.View;
import android.view.ViewGroup; 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.MultiDisplayResolveInfo;
import com.android.internal.app.chooser.SelectableTargetInfo; import com.android.internal.app.chooser.SelectableTargetInfo;
import com.android.internal.app.chooser.TargetInfo; import com.android.internal.app.chooser.TargetInfo;
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
public class ChooserListAdapter extends ResolverListAdapter { public class ChooserListAdapter extends ResolverListAdapter {
private static final String TAG = "ChooserListAdapter"; private static final String TAG = "ChooserListAdapter";
private static final boolean DEBUG = false; private static final boolean DEBUG = false;
private boolean mAppendDirectShareEnabled = DeviceConfig.getBoolean(
DeviceConfig.NAMESPACE_SYSTEMUI,
SystemUiDeviceConfigFlags.APPEND_DIRECT_SHARE_ENABLED,
false);
private boolean mEnableStackedApps = true; private boolean mEnableStackedApps = true;
public static final int NO_POSITION = -1; 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 // Reserve spots for incoming direct share targets by adding placeholders
private ChooserTargetInfo private ChooserTargetInfo
mPlaceHolderTargetInfo = new ChooserActivity.PlaceHolderTargetInfo(); 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<ChooserTargetInfo> mServiceTargets = new ArrayList<>();
private final List<TargetInfo> mCallerTargets = new ArrayList<>(); private final List<TargetInfo> mCallerTargets = new ArrayList<>();
@@ -189,6 +205,9 @@ public class ChooserListAdapter extends ResolverListAdapter {
void refreshListView() { void refreshListView() {
if (mListViewDataChanged) { if (mListViewDataChanged) {
if (mAppendDirectShareEnabled) {
appendServiceTargetsWithQuota();
}
super.notifyDataSetChanged(); super.notifyDataSetChanged();
} }
mListViewDataChanged = false; mListViewDataChanged = false;
@@ -198,6 +217,10 @@ public class ChooserListAdapter extends ResolverListAdapter {
private void createPlaceHolders() { private void createPlaceHolders() {
mNumShortcutResults = 0; mNumShortcutResults = 0;
mServiceTargets.clear(); mServiceTargets.clear();
mValidServiceTargetsNum = 0;
mParkingDirectShareTargets.clear();
mPendingChooserTargetService.clear();
mShortcutComponents.clear();
for (int i = 0; i < MAX_SERVICE_TARGETS; i++) { for (int i = 0; i < MAX_SERVICE_TARGETS; i++) {
mServiceTargets.add(mPlaceHolderTargetInfo); mServiceTargets.add(mPlaceHolderTargetInfo);
} }
@@ -393,12 +416,19 @@ public class ChooserListAdapter extends ResolverListAdapter {
*/ */
public void addServiceResults(DisplayResolveInfo origTarget, List<ChooserTarget> targets, public void addServiceResults(DisplayResolveInfo origTarget, List<ChooserTarget> targets,
@ChooserActivity.ShareTargetType int targetType, @ChooserActivity.ShareTargetType int targetType,
Map<ChooserTarget, ShortcutInfo> directShareToShortcutInfos) { Map<ChooserTarget, ShortcutInfo> directShareToShortcutInfos,
List<ChooserActivity.ChooserTargetServiceConnection>
pendingChooserTargetServiceConnections) {
if (DEBUG) { if (DEBUG) {
Log.d(TAG, "addServiceResults " + origTarget + ", " + targets.size() Log.d(TAG, "addServiceResults " + origTarget.getResolvedComponentName() + ", "
+ targets.size()
+ " targets"); + " targets");
} }
if (mAppendDirectShareEnabled) {
parkTargetIntoMemory(origTarget, targets, targetType, directShareToShortcutInfos,
pendingChooserTargetServiceConnections);
return;
}
if (targets.size() == 0) { if (targets.size() == 0) {
return; 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() { int getNumShortcutResults() {
return mNumShortcutResults; return mNumShortcutResults;
} }
@@ -487,7 +637,9 @@ public class ChooserListAdapter extends ResolverListAdapter {
*/ */
public void completeServiceTargetLoading() { public void completeServiceTargetLoading() {
mServiceTargets.removeIf(o -> o instanceof ChooserActivity.PlaceHolderTargetInfo); mServiceTargets.removeIf(o -> o instanceof ChooserActivity.PlaceHolderTargetInfo);
if (mAppendDirectShareEnabled) {
fillAllServiceTargets();
}
if (mServiceTargets.isEmpty()) { if (mServiceTargets.isEmpty()) {
mServiceTargets.add(new ChooserActivity.EmptyTargetInfo()); mServiceTargets.add(new ChooserActivity.EmptyTargetInfo());
} }

View File

@@ -153,6 +153,14 @@ public class ResolverListAdapter extends BaseAdapter {
return mResolverListController.getScore(target); 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) { public void updateModel(ComponentName componentName) {
mResolverListController.updateModel(componentName); mResolverListController.updateModel(componentName);
} }

View File

@@ -367,6 +367,14 @@ public class ResolverListController {
return mResolverComparator.getScore(target.getResolvedComponentName()); 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) { public void updateModel(ComponentName componentName) {
mResolverComparator.updateModel(componentName); mResolverComparator.updateModel(componentName);
} }

View File

@@ -48,6 +48,7 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/** /**
* Ranks and compares packages based on usage stats and uses the {@link ResolverRankerService}. * Ranks and compares packages based on usage stats and uses the {@link ResolverRankerService}.
@@ -252,6 +253,15 @@ class ResolverRankerServiceResolverComparator extends AbstractResolverComparator
return 0; 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. // update ranking model when the connection to it is valid.
@Override @Override
public void updateModel(ComponentName componentName) { 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 = public static final String NAV_BAR_HANDLE_SHOW_OVER_LOCKSCREEN =
"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. * (boolean) Whether to enable user-drag resizing for PIP.
*/ */

View File

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

View File

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