summaryrefslogtreecommitdiff
path: root/java/src/com
diff options
context:
space:
mode:
author Joshua Trask <joshtrask@google.com> 2022-11-11 15:27:08 -0500
committer Joshua Trask <joshtrask@google.com> 2022-11-28 18:52:31 +0000
commit2a6323fd89e50d655c061b6dda2d2f7ff11493b5 (patch)
treeec5a145f53a9fa375d5e48c0d8a5224c47c57b96 /java/src/com
parent0919a83ee94b469eba0a72ee6e79fddb1e7dc7f7 (diff)
Extact Chooser intent processing; remove delegate
This CL makes two small cleanups with no observable behavior changes: 1. Extract a helper (in a new component) to handle the intent processing that was previously inline in ChooserActivity's onCreate(), storing the validated request data immutably. There's a lot of boilerplate in the processing that was too verbose inline in such an important lifecycle hook, and this refactoring clarifies the lifecycle -- many of the extracted parameters had been individual ivars on ChooserActivity, and it wasn't obvious that they were all assigned in a batch and then never modified. 2. In previous code reviews, ayepin@ and I have discussed the opportunity to remove the ChooserListCommunicator interface now that it's clearly only responsible for providing values that we can precompute and inject upfront; that precomputation mostly involves the parameters extracted in this CL, so I've gone ahead and made that change here. (It's not as straightforward to replace the base ResolverListCommunicator, so for now we're still passing through the same "communicator object" as before, but without the enhanced "chooser-specific" API. Test: atest IntentResolverUnitTests Bug: 202167050 Change-Id: I7c3d47d282591f86ab1fae5e04c3d7d83f2fea0d
Diffstat (limited to 'java/src/com')
-rw-r--r--java/src/com/android/intentresolver/ChooserActivity.java305
-rw-r--r--java/src/com/android/intentresolver/ChooserListAdapter.java68
-rw-r--r--java/src/com/android/intentresolver/ChooserRequestParameters.java441
3 files changed, 574 insertions, 240 deletions
diff --git a/java/src/com/android/intentresolver/ChooserActivity.java b/java/src/com/android/intentresolver/ChooserActivity.java
index 776d34a9..89a9833f 100644
--- a/java/src/com/android/intentresolver/ChooserActivity.java
+++ b/java/src/com/android/intentresolver/ChooserActivity.java
@@ -133,7 +133,6 @@ import java.io.File;
import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-import java.net.URISyntaxException;
import java.text.Collator;
import java.util.ArrayList;
import java.util.Arrays;
@@ -154,13 +153,9 @@ import java.util.function.Supplier;
*
*/
public class ChooserActivity extends ResolverActivity implements
- ChooserListAdapter.ChooserListCommunicator {
+ ResolverListAdapter.ResolverListCommunicator {
private static final String TAG = "ChooserActivity";
- private boolean mShouldDisplayLandscape;
-
- public ChooserActivity() {
- }
/**
* Boolean extra to change the following behavior: Normally, ChooserActivity finishes itself
* in onStop when launched in a new task. If this extra is set to true, we do not finish
@@ -169,7 +164,6 @@ public class ChooserActivity extends ResolverActivity implements
public static final String EXTRA_PRIVATE_RETAIN_IN_ON_STOP
= "com.android.internal.app.ChooserActivity.EXTRA_PRIVATE_RETAIN_IN_ON_STOP";
-
/**
* Transition name for the first image preview.
* To be used for shared element transition into this activity.
@@ -216,9 +210,6 @@ public class ChooserActivity extends ResolverActivity implements
private static final int SCROLL_STATUS_SCROLLING_VERTICAL = 1;
private static final int SCROLL_STATUS_SCROLLING_HORIZONTAL = 2;
- // statsd logger wrapper
- protected ChooserActivityLogger mChooserActivityLogger;
-
@IntDef(flag = false, prefix = { "TARGET_TYPE_" }, value = {
TARGET_TYPE_DEFAULT,
TARGET_TYPE_CHOOSER_TARGET,
@@ -246,14 +237,26 @@ public class ChooserActivity extends ResolverActivity implements
| Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
| Intent.FLAG_GRANT_PREFIX_URI_PERMISSION;
- private Bundle mReplacementExtras;
- private IntentSender mChosenComponentSender;
- private IntentSender mRefinementIntentSender;
- private RefinementResultReceiver mRefinementResultReceiver;
- private ChooserTarget[] mCallerChooserTargets;
- private ArrayList<ComponentName> mFilteredComponentNames;
+ /* TODO: this is `nullable` *primarily* because we have to defer the assignment til onCreate().
+ * We make the only assignment there, and *expect* it to be ready by the time we ever use it --
+ * someday if we move all the usage to a component with a narrower lifecycle (something that
+ * matches our Activity's create/destroy lifecycle, not its Java object lifecycle) then we
+ * should be able to make this assignment as "final." Unfortunately, for now we also have
+ * a vestigial design where ChooserActivity.onCreate() can invalidate a request, but it still
+ * has to call up to ResolverActivity.onCreate() before closing, and the base method delegates
+ * back down to other methods in ChooserActivity that aren't really relevant if we're closing
+ * (and so they'd normally want to assume it was a valid "creation," with non-null parameters).
+ * Any client null checks are workarounds for this condition that can be removed once that
+ * design is cleaned up. */
+ @Nullable
+ private ChooserRequestParameters mChooserRequest;
- private Intent mReferrerFillInIntent;
+ private boolean mShouldDisplayLandscape;
+ // statsd logger wrapper
+ protected ChooserActivityLogger mChooserActivityLogger;
+
+ @Nullable
+ private RefinementResultReceiver mRefinementResultReceiver;
private long mChooserShownTime;
protected boolean mIsSuccessfullySelected;
@@ -265,6 +268,7 @@ public class ChooserActivity extends ResolverActivity implements
private static final int MAX_LOG_RANK_POSITION = 12;
+ // TODO: are these used anywhere? They should probably be migrated to ChooserRequestParameters.
private static final int MAX_EXTRA_INITIAL_INTENTS = 2;
private static final int MAX_EXTRA_CHOOSER_TARGETS = 2;
@@ -291,6 +295,8 @@ public class ChooserActivity extends ResolverActivity implements
private final SparseArray<ProfileRecord> mProfileRecords = new SparseArray<>();
+ public ChooserActivity() {}
+
private void setupPreDrawForSharedElementTransition(View v) {
v.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
@Override
@@ -319,134 +325,42 @@ public class ChooserActivity extends ResolverActivity implements
getChooserActivityLogger().logSharesheetTriggered();
- mIsSuccessfullySelected = false;
- Intent intent = getIntent();
- Parcelable targetParcelable = intent.getParcelableExtra(Intent.EXTRA_INTENT);
- if (targetParcelable instanceof Uri) {
- try {
- targetParcelable = Intent.parseUri(targetParcelable.toString(),
- Intent.URI_INTENT_SCHEME);
- } catch (URISyntaxException ex) {
- // doesn't parse as an intent; let the next test fail and error out
- }
- }
-
- if (!(targetParcelable instanceof Intent)) {
- Log.w("ChooserActivity", "Target is not an intent: " + targetParcelable);
+ try {
+ mChooserRequest = new ChooserRequestParameters(
+ getIntent(), getReferrer(), getNearbySharingComponent());
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, "Caller provided invalid Chooser request parameters", e);
finish();
super.onCreate(null);
return;
}
- final Intent target = (Intent) targetParcelable;
- modifyTargetIntent(target);
- Parcelable[] targetsParcelable
- = intent.getParcelableArrayExtra(Intent.EXTRA_ALTERNATE_INTENTS);
- if (targetsParcelable != null) {
- Intent[] additionalTargets = new Intent[targetsParcelable.length];
- for (int i = 0; i < targetsParcelable.length; i++) {
- if (!(targetsParcelable[i] instanceof Intent)) {
- Log.w(TAG, "EXTRA_ALTERNATE_INTENTS array entry #" + i
- + " is not an Intent: " + targetsParcelable[i]);
- finish();
- super.onCreate(null);
- return;
- }
- final Intent additionalTarget = (Intent) targetsParcelable[i];
- additionalTargets[i] = additionalTarget;
- modifyTargetIntent(additionalTarget);
- }
- setAdditionalTargets(additionalTargets);
- }
- mReplacementExtras = intent.getBundleExtra(Intent.EXTRA_REPLACEMENT_EXTRAS);
+ setAdditionalTargets(mChooserRequest.getAdditionalTargets());
- // Do not allow the title to be changed when sharing content
- CharSequence title = null;
- if (!isSendAction(target)) {
- title = intent.getCharSequenceExtra(Intent.EXTRA_TITLE);
- } else {
- Log.w(TAG, "Ignoring intent's EXTRA_TITLE, deprecated in P. You may wish to set a"
- + " preview title by using EXTRA_TITLE property of the wrapped"
- + " EXTRA_INTENT.");
- }
-
- int defaultTitleRes = 0;
- if (title == null) {
- defaultTitleRes = com.android.internal.R.string.chooseActivity;
- }
-
- Parcelable[] pa = intent.getParcelableArrayExtra(Intent.EXTRA_INITIAL_INTENTS);
- Intent[] initialIntents = null;
- if (pa != null) {
- int count = Math.min(pa.length, MAX_EXTRA_INITIAL_INTENTS);
- initialIntents = new Intent[count];
- for (int i = 0; i < count; i++) {
- if (!(pa[i] instanceof Intent)) {
- Log.w(TAG, "Initial intent #" + i + " not an Intent: " + pa[i]);
- finish();
- super.onCreate(null);
- return;
- }
- final Intent in = (Intent) pa[i];
- modifyTargetIntent(in);
- initialIntents[i] = in;
- }
- }
-
- mReferrerFillInIntent = new Intent().putExtra(Intent.EXTRA_REFERRER, getReferrer());
-
- mChosenComponentSender = intent.getParcelableExtra(
- Intent.EXTRA_CHOSEN_COMPONENT_INTENT_SENDER);
- mRefinementIntentSender = intent.getParcelableExtra(
- Intent.EXTRA_CHOOSER_REFINEMENT_INTENT_SENDER);
setSafeForwardingMode(true);
mPinnedSharedPrefs = getPinnedSharedPrefs(this);
- mFilteredComponentNames = new ArrayList<>();
- try {
- ComponentName[] exclodedComponents = intent.getParcelableArrayExtra(
- Intent.EXTRA_EXCLUDE_COMPONENTS,
- ComponentName.class);
- if (exclodedComponents != null) {
- Collections.addAll(mFilteredComponentNames, exclodedComponents);
- }
- } catch (ClassCastException e) {
- Log.e(TAG, "Excluded components must be of type ComponentName[]", e);
- }
-
- // Exclude Nearby from main list if chip is present, to avoid duplication
- ComponentName nearby = getNearbySharingComponent();
- if (nearby != null) {
- mFilteredComponentNames.add(nearby);
- }
-
- pa = intent.getParcelableArrayExtra(Intent.EXTRA_CHOOSER_TARGETS);
- if (pa != null) {
- int count = Math.min(pa.length, MAX_EXTRA_CHOOSER_TARGETS);
- ChooserTarget[] targets = new ChooserTarget[count];
- for (int i = 0; i < count; i++) {
- if (!(pa[i] instanceof ChooserTarget)) {
- Log.w(TAG, "Chooser target #" + i + " not a ChooserTarget: " + pa[i]);
- targets = null;
- break;
- }
- targets[i] = (ChooserTarget) pa[i];
- }
- mCallerChooserTargets = targets;
- }
-
mMaxTargetsPerRow = getResources().getInteger(R.integer.config_chooser_max_targets_per_row);
mShouldDisplayLandscape =
shouldDisplayLandscape(getResources().getConfiguration().orientation);
- setRetainInOnStop(intent.getBooleanExtra(EXTRA_PRIVATE_RETAIN_IN_ON_STOP, false));
- IntentFilter targetIntentFilter = getTargetIntentFilter(target);
+ setRetainInOnStop(mChooserRequest.shouldRetainInOnStop());
+
createProfileRecords(
new AppPredictorFactory(
getApplicationContext(),
- target.getStringExtra(Intent.EXTRA_TEXT),
- targetIntentFilter),
- targetIntentFilter);
+ mChooserRequest.getSharedText(),
+ mChooserRequest.getTargetIntentFilter()),
+ mChooserRequest.getTargetIntentFilter());
+
+ super.onCreate(
+ savedInstanceState,
+ mChooserRequest.getTargetIntent(),
+ mChooserRequest.getTitle(),
+ mChooserRequest.getDefaultTitleResource(),
+ mChooserRequest.getInitialIntents(),
+ /* rList: List<ResolveInfo> = */ null,
+ /* supportsAlwaysUseOption = */ false);
mPreviewCoordinator = new ChooserContentPreviewCoordinator(
mBackgroundThreadPoolExecutor,
@@ -454,23 +368,21 @@ public class ChooserActivity extends ResolverActivity implements
this::hideContentPreview,
this::setupPreDrawForSharedElementTransition);
- super.onCreate(savedInstanceState, target, title, defaultTitleRes, initialIntents,
- null, false);
-
mChooserShownTime = System.currentTimeMillis();
final long systemCost = mChooserShownTime - intentReceivedTime;
getMetricsLogger().write(new LogMaker(MetricsEvent.ACTION_ACTIVITY_CHOOSER_SHOWN)
.setSubtype(isWorkProfile() ? MetricsEvent.MANAGED_PROFILE :
MetricsEvent.PARENT_PROFILE)
- .addTaggedData(MetricsEvent.FIELD_SHARESHEET_MIMETYPE, target.getType())
+ .addTaggedData(
+ MetricsEvent.FIELD_SHARESHEET_MIMETYPE, mChooserRequest.getTargetType())
.addTaggedData(MetricsEvent.FIELD_TIME_TO_APP_TARGETS, systemCost));
if (mResolverDrawerLayout != null) {
mResolverDrawerLayout.addOnLayoutChangeListener(this::handleLayoutChange);
// expand/shrink direct share 4 -> 8 viewgroup
- if (isSendAction(target)) {
+ if (mChooserRequest.isSendActionTarget()) {
mResolverDrawerLayout.setOnScrollChangeListener(this::handleScroll);
}
@@ -499,13 +411,14 @@ public class ChooserActivity extends ResolverActivity implements
getChooserActivityLogger().logShareStarted(
FrameworkStatsLog.SHARESHEET_STARTED,
getReferrerPackageName(),
- target.getType(),
- mCallerChooserTargets == null ? 0 : mCallerChooserTargets.length,
- initialIntents == null ? 0 : initialIntents.length,
+ mChooserRequest.getTargetType(),
+ mChooserRequest.getCallerChooserTargets().size(),
+ (mChooserRequest.getInitialIntents() == null)
+ ? 0 : mChooserRequest.getInitialIntents().length,
isWorkProfile(),
ChooserContentPreviewUi.findPreferredContentPreview(
getTargetIntent(), getContentResolver(), this::isImageType),
- target.getAction()
+ mChooserRequest.getTargetAction()
);
setEnterSharedElementCallback(new SharedElementCallback() {
@@ -607,7 +520,7 @@ public class ChooserActivity extends ResolverActivity implements
@Override
protected EmptyStateProvider createBlockerEmptyStateProvider() {
- final boolean isSendAction = isSendAction(getTargetIntent());
+ final boolean isSendAction = mChooserRequest.isSendActionTarget();
final EmptyState noWorkToPersonalEmptyState =
new DevicePolicyBlockerEmptyState(
@@ -1194,9 +1107,14 @@ public class ChooserActivity extends ResolverActivity implements
@Override // ResolverListCommunicator
public Intent getReplacementIntent(ActivityInfo aInfo, Intent defIntent) {
+ if (mChooserRequest == null) {
+ return defIntent;
+ }
+
Intent result = defIntent;
- if (mReplacementExtras != null) {
- final Bundle replExtras = mReplacementExtras.getBundle(aInfo.packageName);
+ if (mChooserRequest.getReplacementExtras() != null) {
+ final Bundle replExtras =
+ mChooserRequest.getReplacementExtras().getBundle(aInfo.packageName);
if (replExtras != null) {
result = new Intent(defIntent);
result.putExtras(replExtras);
@@ -1217,12 +1135,13 @@ public class ChooserActivity extends ResolverActivity implements
@Override
public void onActivityStarted(TargetInfo cti) {
- if (mChosenComponentSender != null) {
+ if (mChooserRequest.getChosenComponentSender() != null) {
final ComponentName target = cti.getResolvedComponentName();
if (target != null) {
final Intent fillIn = new Intent().putExtra(Intent.EXTRA_CHOSEN_COMPONENT, target);
try {
- mChosenComponentSender.sendIntent(this, Activity.RESULT_OK, fillIn, null, null);
+ mChooserRequest.getChosenComponentSender().sendIntent(
+ this, Activity.RESULT_OK, fillIn, null, null);
} catch (IntentSender.SendIntentException e) {
Slog.e(TAG, "Unable to launch supplied IntentSender to report "
+ "the chosen component: " + e);
@@ -1233,10 +1152,14 @@ public class ChooserActivity extends ResolverActivity implements
@Override
public void addUseDifferentAppLabelIfNecessary(ResolverListAdapter adapter) {
- if (mCallerChooserTargets != null && mCallerChooserTargets.length > 0) {
+ if (mChooserRequest == null) {
+ return;
+ }
+
+ if (mChooserRequest.getCallerChooserTargets().size() > 0) {
mChooserMultiProfilePagerAdapter.getActiveListAdapter().addServiceResults(
/* origTarget */ null,
- Lists.newArrayList(mCallerChooserTargets),
+ mChooserRequest.getCallerChooserTargets(),
TARGET_TYPE_DEFAULT,
/* directShareShortcutInfoCache */ Collections.emptyMap(),
/* directShareAppTargetCache */ Collections.emptyMap());
@@ -1277,8 +1200,8 @@ public class ChooserActivity extends ResolverActivity implements
// TODO: implement these type-conditioned behaviors polymorphically, and consider moving
// the logic into `ChooserTargetActionsDialogFragment.show()`.
boolean isShortcutPinned = targetInfo.isSelectableTargetInfo() && targetInfo.isPinned();
- IntentFilter intentFilter =
- targetInfo.isSelectableTargetInfo() ? getTargetIntentFilter() : null;
+ IntentFilter intentFilter = targetInfo.isSelectableTargetInfo()
+ ? mChooserRequest.getTargetIntentFilter() : null;
String shortcutTitle = targetInfo.isSelectableTargetInfo()
? targetInfo.getDisplayLabel().toString() : null;
String shortcutIdKey = targetInfo.getDirectShareShortcutId();
@@ -1293,16 +1216,9 @@ public class ChooserActivity extends ResolverActivity implements
intentFilter);
}
- private void modifyTargetIntent(Intent in) {
- if (isSendAction(in)) {
- in.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT |
- Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
- }
- }
-
@Override
protected boolean onTargetSelected(TargetInfo target, boolean alwaysCheck) {
- if (mRefinementIntentSender != null) {
+ if (mChooserRequest.getRefinementIntentSender() != null) {
final Intent fillIn = new Intent();
final List<Intent> sourceIntents = target.getAllSourceIntents();
if (!sourceIntents.isEmpty()) {
@@ -1321,7 +1237,8 @@ public class ChooserActivity extends ResolverActivity implements
fillIn.putExtra(Intent.EXTRA_RESULT_RECEIVER,
mRefinementResultReceiver);
try {
- mRefinementIntentSender.sendIntent(this, 0, fillIn, null, null);
+ mChooserRequest.getRefinementIntentSender().sendIntent(
+ this, 0, fillIn, null, null);
return false;
} catch (SendIntentException e) {
Log.e(TAG, "Refinement IntentSender failed to send", e);
@@ -1372,9 +1289,7 @@ public class ChooserActivity extends ResolverActivity implements
directTargetHashed = targetInfo.getHashedTargetIdForMetrics(this);
directTargetAlsoRanked = getRankedPosition(targetInfo);
- if (mCallerChooserTargets != null) {
- numCallerProvided = mCallerChooserTargets.length;
- }
+ numCallerProvided = mChooserRequest.getCallerChooserTargets().size();
getChooserActivityLogger().logShareTargetSelected(
SELECTION_TYPE_SERVICE,
targetInfo.getResolveInfo().activityInfo.processName,
@@ -1686,7 +1601,7 @@ public class ChooserActivity extends ResolverActivity implements
@Override
boolean isComponentFiltered(ComponentName name) {
- return mFilteredComponentNames != null && mFilteredComponentNames.contains(name);
+ return mChooserRequest.getFilteredComponentNames().contains(name);
}
@Override
@@ -1696,19 +1611,35 @@ public class ChooserActivity extends ResolverActivity implements
}
@VisibleForTesting
- public ChooserGridAdapter createChooserGridAdapter(Context context,
- List<Intent> payloadIntents, Intent[] initialIntents, List<ResolveInfo> rList,
- boolean filterLastUsed, UserHandle userHandle) {
- ChooserListAdapter chooserListAdapter = createChooserListAdapter(context, payloadIntents,
- initialIntents, rList, filterLastUsed,
- createListController(userHandle));
+ public ChooserGridAdapter createChooserGridAdapter(
+ Context context,
+ List<Intent> payloadIntents,
+ Intent[] initialIntents,
+ List<ResolveInfo> rList,
+ boolean filterLastUsed,
+ UserHandle userHandle) {
+ ChooserListAdapter chooserListAdapter = createChooserListAdapter(
+ context,
+ payloadIntents,
+ initialIntents,
+ rList,
+ filterLastUsed,
+ createListController(userHandle),
+ mChooserRequest,
+ mMaxTargetsPerRow);
return new ChooserGridAdapter(chooserListAdapter);
}
@VisibleForTesting
- public ChooserListAdapter createChooserListAdapter(Context context,
- List<Intent> payloadIntents, Intent[] initialIntents, List<ResolveInfo> rList,
- boolean filterLastUsed, ResolverListController resolverListController) {
+ public ChooserListAdapter createChooserListAdapter(
+ Context context,
+ List<Intent> payloadIntents,
+ Intent[] initialIntents,
+ List<ResolveInfo> rList,
+ boolean filterLastUsed,
+ ResolverListController resolverListController,
+ ChooserRequestParameters chooserRequest,
+ int maxTargetsPerRow) {
return new ChooserListAdapter(
context,
payloadIntents,
@@ -1718,7 +1649,9 @@ public class ChooserActivity extends ResolverActivity implements
resolverListController,
this,
context.getPackageManager(),
- getChooserActivityLogger());
+ getChooserActivityLogger(),
+ chooserRequest,
+ maxTargetsPerRow);
}
@VisibleForTesting
@@ -1949,16 +1882,6 @@ public class ChooserActivity extends ResolverActivity implements
super.onHandlePackagesChanged(listAdapter);
}
- @Override // SelectableTargetInfoCommunicator
- public Intent getReferrerFillInIntent() {
- return mReferrerFillInIntent;
- }
-
- @Override // ChooserListCommunicator
- public int getMaxRankedTargets() {
- return mMaxTargetsPerRow;
- }
-
@Override
public void onListRebuilt(ResolverListAdapter listAdapter, boolean rebuildComplete) {
setupScrollListener();
@@ -2093,24 +2016,6 @@ public class ChooserActivity extends ResolverActivity implements
});
}
- @Override // ChooserListCommunicator
- public boolean isSendAction(Intent targetIntent) {
- if (targetIntent == null) {
- return false;
- }
-
- String action = targetIntent.getAction();
- if (action == null) {
- return false;
- }
-
- if (Intent.ACTION_SEND.equals(action) || Intent.ACTION_SEND_MULTIPLE.equals(action)) {
- return true;
- }
-
- return false;
- }
-
/**
* The sticky content preview is shown only when we have a tabbed view. It's shown above
* the tabs so it is not part of the scrollable list. If we are not in tabbed view,
@@ -2132,7 +2037,7 @@ public class ChooserActivity extends ResolverActivity implements
* @return true if we want to show the content preview area
*/
protected boolean shouldShowContentPreview() {
- return isSendAction(getTargetIntent());
+ return (mChooserRequest != null) && mChooserRequest.isSendActionTarget();
}
private void updateStickyContentPreview() {
@@ -2762,7 +2667,7 @@ public class ChooserActivity extends ResolverActivity implements
position -= getSystemRowCount() + getProfileRowCount();
final int serviceCount = mChooserListAdapter.getServiceTargetCount();
- final int serviceRows = (int) Math.ceil((float) serviceCount / getMaxRankedTargets());
+ final int serviceRows = (int) Math.ceil((float) serviceCount / mMaxTargetsPerRow);
if (position < serviceRows) {
return position * mMaxTargetsPerRow;
}
diff --git a/java/src/com/android/intentresolver/ChooserListAdapter.java b/java/src/com/android/intentresolver/ChooserListAdapter.java
index e31bf2ab..b18d2718 100644
--- a/java/src/com/android/intentresolver/ChooserListAdapter.java
+++ b/java/src/com/android/intentresolver/ChooserListAdapter.java
@@ -83,7 +83,9 @@ public class ChooserListAdapter extends ResolverListAdapter {
/** {@link #getBaseScore} */
public static final float SHORTCUT_TARGET_SCORE_BOOST = 90.f;
- private final ChooserListCommunicator mChooserListCommunicator;
+ private final ChooserRequestParameters mChooserRequest;
+ private final int mMaxRankedTargets;
+
private final ChooserActivityLogger mChooserActivityLogger;
private final Map<TargetInfo, AsyncTask> mIconLoaders = new HashMap<>();
@@ -136,15 +138,19 @@ public class ChooserListAdapter extends ResolverListAdapter {
List<ResolveInfo> rList,
boolean filterLastUsed,
ResolverListController resolverListController,
- ChooserListCommunicator chooserListCommunicator,
+ ResolverListCommunicator resolverListCommunicator,
PackageManager packageManager,
- ChooserActivityLogger chooserActivityLogger) {
+ ChooserActivityLogger chooserActivityLogger,
+ ChooserRequestParameters chooserRequest,
+ int maxRankedTargets) {
// Don't send the initial intents through the shared ResolverActivity path,
// we want to separate them into a different section.
super(context, payloadIntents, null, rList, filterLastUsed,
- resolverListController, chooserListCommunicator, false);
+ resolverListController, resolverListCommunicator, false);
+
+ mChooserRequest = chooserRequest;
+ mMaxRankedTargets = maxRankedTargets;
- mChooserListCommunicator = chooserListCommunicator;
mPlaceHolderTargetInfo = NotSelectableTargetInfo.newPlaceHolderTargetInfo(context);
createPlaceHolders();
mChooserActivityLogger = chooserActivityLogger;
@@ -221,13 +227,13 @@ public class ChooserListAdapter extends ResolverListAdapter {
Log.d(TAG, "clearing queryTargets on package change");
}
createPlaceHolders();
- mChooserListCommunicator.onHandlePackagesChanged(this);
+ mResolverListCommunicator.onHandlePackagesChanged(this);
}
private void createPlaceHolders() {
mServiceTargets.clear();
- for (int i = 0; i < mChooserListCommunicator.getMaxRankedTargets(); i++) {
+ for (int i = 0; i < mMaxRankedTargets; ++i) {
mServiceTargets.add(mPlaceHolderTargetInfo);
}
}
@@ -367,8 +373,9 @@ public class ChooserListAdapter extends ResolverListAdapter {
@Override
public int getUnfilteredCount() {
int appTargets = super.getUnfilteredCount();
- if (appTargets > mChooserListCommunicator.getMaxRankedTargets()) {
- appTargets = appTargets + mChooserListCommunicator.getMaxRankedTargets();
+ if (appTargets > mMaxRankedTargets) {
+ // TODO: what does this condition mean?
+ appTargets = appTargets + mMaxRankedTargets;
}
return appTargets + getSelectableServiceTargetCount() + getCallerTargetCount();
}
@@ -392,9 +399,8 @@ public class ChooserListAdapter extends ResolverListAdapter {
}
public int getServiceTargetCount() {
- if (mChooserListCommunicator.isSendAction(mChooserListCommunicator.getTargetIntent())
- && !ActivityManager.isLowRamDeviceStatic()) {
- return Math.min(mServiceTargets.size(), mChooserListCommunicator.getMaxRankedTargets());
+ if (mChooserRequest.isSendActionTarget() && !ActivityManager.isLowRamDeviceStatic()) {
+ return Math.min(mServiceTargets.size(), mMaxRankedTargets);
}
return 0;
@@ -403,15 +409,14 @@ public class ChooserListAdapter extends ResolverListAdapter {
int getAlphaTargetCount() {
int groupedCount = mSortedList.size();
int ungroupedCount = mCallerTargets.size() + mDisplayList.size();
- return ungroupedCount > mChooserListCommunicator.getMaxRankedTargets() ? groupedCount : 0;
+ return (ungroupedCount > mMaxRankedTargets) ? groupedCount : 0;
}
/**
* Fetch ranked app target count
*/
public int getRankedTargetCount() {
- int spacesAvailable =
- mChooserListCommunicator.getMaxRankedTargets() - getCallerTargetCount();
+ int spacesAvailable = mMaxRankedTargets - getCallerTargetCount();
return Math.min(spacesAvailable, super.getCount());
}
@@ -508,8 +513,8 @@ 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(dri.getResolveInfo(), existingInfo.getResolveInfo())) {
+ if (mResolverListCommunicator.resolveInfoMatch(
+ dri.getResolveInfo(), existingInfo.getResolveInfo())) {
return false;
}
}
@@ -520,9 +525,8 @@ public class ChooserListAdapter extends ResolverListAdapter {
* Fetch surfaced direct share target info
*/
public List<TargetInfo> getSurfacedTargetInfo() {
- int maxSurfacedTargets = mChooserListCommunicator.getMaxRankedTargets();
return mServiceTargets.subList(0,
- Math.min(maxSurfacedTargets, getSelectableServiceTargetCount()));
+ Math.min(mMaxRankedTargets, getSelectableServiceTargetCount()));
}
@@ -550,9 +554,9 @@ public class ChooserListAdapter extends ResolverListAdapter {
directShareToShortcutInfos,
directShareToAppTargets,
mContext.createContextAsUser(getUserHandle(), 0),
- mChooserListCommunicator.getTargetIntent(),
- mChooserListCommunicator.getReferrerFillInIntent(),
- mChooserListCommunicator.getMaxRankedTargets(),
+ mChooserRequest.getTargetIntent(),
+ mChooserRequest.getReferrerFillInIntent(),
+ mMaxRankedTargets,
mServiceTargets);
if (isUpdated) {
notifyDataSetChanged();
@@ -616,8 +620,7 @@ public class ChooserListAdapter extends ResolverListAdapter {
protected List<ResolvedComponentInfo> doInBackground(
List<ResolvedComponentInfo>... params) {
Trace.beginSection("ChooserListAdapter#SortingTask");
- mResolverListController.topK(params[0],
- mChooserListCommunicator.getMaxRankedTargets());
+ mResolverListController.topK(params[0], mMaxRankedTargets);
Trace.endSection();
return params[0];
}
@@ -625,7 +628,7 @@ public class ChooserListAdapter extends ResolverListAdapter {
protected void onPostExecute(List<ResolvedComponentInfo> sortedComponents) {
processSortedList(sortedComponents, doPostProcessing);
if (doPostProcessing) {
- mChooserListCommunicator.updateProfileViewButton();
+ mResolverListCommunicator.updateProfileViewButton();
notifyDataSetChanged();
}
}
@@ -633,21 +636,6 @@ public class ChooserListAdapter extends ResolverListAdapter {
}
/**
- * Necessary methods to communicate between {@link ChooserListAdapter}
- * and {@link ChooserActivity}.
- */
- interface ChooserListCommunicator extends ResolverListCommunicator {
-
- int getMaxRankedTargets();
-
- boolean isSendAction(Intent targetIntent);
-
- Intent getTargetIntent();
-
- Intent getReferrerFillInIntent();
- }
-
- /**
* Loads direct share targets icons.
*/
@VisibleForTesting
diff --git a/java/src/com/android/intentresolver/ChooserRequestParameters.java b/java/src/com/android/intentresolver/ChooserRequestParameters.java
new file mode 100644
index 00000000..81481bf1
--- /dev/null
+++ b/java/src/com/android/intentresolver/ChooserRequestParameters.java
@@ -0,0 +1,441 @@
+/*
+ * Copyright (C) 2008 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.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.IntentSender;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.os.PatternMatcher;
+import android.service.chooser.ChooserTarget;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.Pair;
+
+import com.google.common.collect.ImmutableList;
+
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collector;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * Utility to parse and validate parameters from the client-supplied {@link Intent} that launched
+ * the Sharesheet {@link ChooserActivity}. The validated parameters are stored as immutable ivars.
+ *
+ * TODO: field nullability in this class reflects legacy use, and typically would indicate that the
+ * client's intent didn't provide the respective data. In some cases we may be able to provide
+ * defaults instead of nulls -- especially for methods that return nullable lists or arrays, if the
+ * client code could instead handle empty collections equally well.
+ *
+ * TODO: some of these fields (especially getTargetIntent() and any other getters that delegate to
+ * it internally) differ from the legacy model because they're computed directly from the initial
+ * Chooser intent, where in the past they've been relayed up to ResolverActivity and then retrieved
+ * through methods on the base class. The base always seems to return them exactly as they were
+ * provided, so this should be safe -- and clients can reasonably switch to retrieving through these
+ * parameters instead. For now, the other convention is still used in some places. Ideally we'd like
+ * to normalize on a single source of truth, but we'll have to clean up the delegation up to the
+ * resolver (or perhaps this needs to be a subclass of some `ResolverRequestParameters` class?).
+ */
+public class ChooserRequestParameters {
+ private static final String TAG = "ChooserActivity";
+
+ private static final int LAUNCH_FLAGS_FOR_SEND_ACTION =
+ Intent.FLAG_ACTIVITY_NEW_DOCUMENT | Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
+
+ private final Intent mTarget;
+ private final Pair<CharSequence, Integer> mTitleSpec;
+ private final Intent mReferrerFillInIntent;
+ private final ImmutableList<ComponentName> mFilteredComponentNames;
+ private final ImmutableList<ChooserTarget> mCallerChooserTargets;
+ private final boolean mRetainInOnStop;
+
+ @Nullable
+ private final ImmutableList<Intent> mAdditionalTargets;
+
+ @Nullable
+ private final Bundle mReplacementExtras;
+
+ @Nullable
+ private final ImmutableList<Intent> mInitialIntents;
+
+ @Nullable
+ private final IntentSender mChosenComponentSender;
+
+ @Nullable
+ private final IntentSender mRefinementIntentSender;
+
+ @Nullable
+ private final String mSharedText;
+
+ @Nullable
+ private final IntentFilter mTargetIntentFilter;
+
+ public ChooserRequestParameters(
+ final Intent clientIntent,
+ final Uri referrer,
+ @Nullable final ComponentName nearbySharingComponent) {
+ final Intent requestedTarget = parseTargetIntentExtra(
+ clientIntent.getParcelableExtra(Intent.EXTRA_INTENT));
+ mTarget = intentWithModifiedLaunchFlags(requestedTarget);
+
+ mAdditionalTargets = intentsWithModifiedLaunchFlagsFromExtraIfPresent(
+ clientIntent, Intent.EXTRA_ALTERNATE_INTENTS);
+
+ mReplacementExtras = clientIntent.getBundleExtra(Intent.EXTRA_REPLACEMENT_EXTRAS);
+
+ mTitleSpec = makeTitleSpec(
+ clientIntent.getCharSequenceExtra(Intent.EXTRA_TITLE),
+ isSendAction(mTarget.getAction()));
+
+ mInitialIntents = intentsWithModifiedLaunchFlagsFromExtraIfPresent(
+ clientIntent, Intent.EXTRA_INITIAL_INTENTS);
+
+ mReferrerFillInIntent = new Intent().putExtra(Intent.EXTRA_REFERRER, referrer);
+
+ mChosenComponentSender = clientIntent.getParcelableExtra(
+ Intent.EXTRA_CHOSEN_COMPONENT_INTENT_SENDER);
+ mRefinementIntentSender = clientIntent.getParcelableExtra(
+ Intent.EXTRA_CHOOSER_REFINEMENT_INTENT_SENDER);
+
+ mFilteredComponentNames = getFilteredComponentNames(clientIntent, nearbySharingComponent);
+
+ mCallerChooserTargets = parseCallerTargetsFromClientIntent(clientIntent);
+
+ mRetainInOnStop = clientIntent.getBooleanExtra(
+ ChooserActivity.EXTRA_PRIVATE_RETAIN_IN_ON_STOP, false);
+
+ mSharedText = mTarget.getStringExtra(Intent.EXTRA_TEXT);
+
+ mTargetIntentFilter = getTargetIntentFilter(mTarget);
+ }
+
+ public Intent getTargetIntent() {
+ return mTarget;
+ }
+
+ @Nullable
+ public String getTargetAction() {
+ return getTargetIntent().getAction();
+ }
+
+ public boolean isSendActionTarget() {
+ return isSendAction(getTargetAction());
+ }
+
+ @Nullable
+ public String getTargetType() {
+ return getTargetIntent().getType();
+ }
+
+ @Nullable
+ public CharSequence getTitle() {
+ return mTitleSpec.first;
+ }
+
+ public int getDefaultTitleResource() {
+ return mTitleSpec.second;
+ }
+
+ public Intent getReferrerFillInIntent() {
+ return mReferrerFillInIntent;
+ }
+
+ public ImmutableList<ComponentName> getFilteredComponentNames() {
+ return mFilteredComponentNames;
+ }
+
+ public ImmutableList<ChooserTarget> getCallerChooserTargets() {
+ return mCallerChooserTargets;
+ }
+
+ /**
+ * Whether the {@link ChooserActivity.EXTRA_PRIVATE_RETAIN_IN_ON_STOP} behavior was requested.
+ */
+ public boolean shouldRetainInOnStop() {
+ return mRetainInOnStop;
+ }
+
+ /**
+ * TODO: this returns a nullable array for convenience, but if the legacy APIs can be
+ * refactored, returning {@link mAdditionalTargets} directly is simpler and safer.
+ */
+ @Nullable
+ public Intent[] getAdditionalTargets() {
+ return (mAdditionalTargets == null) ? null : mAdditionalTargets.toArray(new Intent[0]);
+ }
+
+ @Nullable
+ public Bundle getReplacementExtras() {
+ return mReplacementExtras;
+ }
+
+ /**
+ * TODO: this returns a nullable array for convenience, but if the legacy APIs can be
+ * refactored, returning {@link mInitialIntents} directly is simpler and safer.
+ */
+ @Nullable
+ public Intent[] getInitialIntents() {
+ return (mInitialIntents == null) ? null : mInitialIntents.toArray(new Intent[0]);
+ }
+
+ @Nullable
+ public IntentSender getChosenComponentSender() {
+ return mChosenComponentSender;
+ }
+
+ @Nullable
+ public IntentSender getRefinementIntentSender() {
+ return mRefinementIntentSender;
+ }
+
+ @Nullable
+ public String getSharedText() {
+ return mSharedText;
+ }
+
+ @Nullable
+ public IntentFilter getTargetIntentFilter() {
+ return mTargetIntentFilter;
+ }
+
+ private static boolean isSendAction(@Nullable String action) {
+ return (Intent.ACTION_SEND.equals(action) || Intent.ACTION_SEND_MULTIPLE.equals(action));
+ }
+
+ private static Intent parseTargetIntentExtra(@Nullable Parcelable targetParcelable) {
+ if (targetParcelable instanceof Uri) {
+ try {
+ targetParcelable = Intent.parseUri(targetParcelable.toString(),
+ Intent.URI_INTENT_SCHEME);
+ } catch (URISyntaxException ex) {
+ throw new IllegalArgumentException("Failed to parse EXTRA_INTENT from URI", ex);
+ }
+ }
+
+ if (!(targetParcelable instanceof Intent)) {
+ throw new IllegalArgumentException(
+ "EXTRA_INTENT is neither an Intent nor a Uri: " + targetParcelable);
+ }
+
+ return ((Intent) targetParcelable);
+ }
+
+ private static Intent intentWithModifiedLaunchFlags(Intent intent) {
+ if (isSendAction(intent.getAction())) {
+ intent.addFlags(LAUNCH_FLAGS_FOR_SEND_ACTION);
+ }
+ return intent;
+ }
+
+ /**
+ * Build a pair of values specifying the title to use from the client request. The first
+ * ({@link CharSequence}) value is the client-specified title, if there was one and their
+ * requested target <em>wasn't</em> a send action; otherwise it is null. The second value is
+ * the resource ID of a default title string; this is nonzero only if the first value is null.
+ *
+ * TODO: change the API for how these are passed up to {@link ResolverActivity#onCreate()}, or
+ * create a real type (not {@link Pair}) to express the semantics described in this comment.
+ */
+ private static Pair<CharSequence, Integer> makeTitleSpec(
+ @Nullable CharSequence requestedTitle, boolean hasSendActionTarget) {
+ if (hasSendActionTarget && (requestedTitle != null)) {
+ // Do not allow the title to be changed when sharing content
+ Log.w(TAG, "Ignoring intent's EXTRA_TITLE, deprecated in P. You may wish to set a"
+ + " preview title by using EXTRA_TITLE property of the wrapped"
+ + " EXTRA_INTENT.");
+ requestedTitle = null;
+ }
+
+ int defaultTitleRes =
+ (requestedTitle == null) ? com.android.internal.R.string.chooseActivity : 0;
+
+ return Pair.create(requestedTitle, defaultTitleRes);
+ }
+
+ private static ImmutableList<ComponentName> getFilteredComponentNames(
+ Intent clientIntent, @Nullable ComponentName nearbySharingComponent) {
+ Stream<ComponentName> filteredComponents = streamParcelableArrayExtra(
+ clientIntent, Intent.EXTRA_EXCLUDE_COMPONENTS, ComponentName.class, true, true);
+
+ if (nearbySharingComponent != null) {
+ // Exclude Nearby from main list if chip is present, to avoid duplication.
+ // TODO: we don't have an explicit guarantee that the chip will be displayed just
+ // because we have a non-null component; that's ultimately determined by the preview
+ // layout. Maybe we can make that decision further upstream?
+ filteredComponents = Stream.concat(
+ filteredComponents, Stream.of(nearbySharingComponent));
+ }
+
+ return filteredComponents.collect(toImmutableList());
+ }
+
+ private static ImmutableList<ChooserTarget> parseCallerTargetsFromClientIntent(
+ Intent clientIntent) {
+ return
+ streamParcelableArrayExtra(
+ clientIntent, Intent.EXTRA_CHOOSER_TARGETS, ChooserTarget.class, true, true)
+ .collect(toImmutableList());
+ }
+
+ private static <T> Collector<T, ?, ImmutableList<T>> toImmutableList() {
+ return Collectors.collectingAndThen(Collectors.toList(), ImmutableList::copyOf);
+ }
+
+ @Nullable
+ private static ImmutableList<Intent> intentsWithModifiedLaunchFlagsFromExtraIfPresent(
+ Intent clientIntent, String extra) {
+ Stream<Intent> intents =
+ streamParcelableArrayExtra(clientIntent, extra, Intent.class, true, false);
+ if (intents == null) {
+ return null;
+ }
+ return intents
+ .map(ChooserRequestParameters::intentWithModifiedLaunchFlags)
+ .collect(toImmutableList());
+ }
+
+ /**
+ * Make a {@link Stream} of the {@link Parcelable} objects given in the provided {@link Intent}
+ * as the optional parcelable array extra with key {@code extra}. The stream elements, if any,
+ * are all of the type specified by {@code clazz}.
+ *
+ * @param intent The intent that may contain the optional extras.
+ * @param extra The extras key to identify the parcelable array.
+ * @param clazz A class that is assignable from any elements in the result stream.
+ * @param warnOnTypeError Whether to log a warning (and ignore) if the client extra doesn't have
+ * the required type. If false, throw an {@link IllegalArgumentException} if the extra is
+ * non-null but can't be assigned to variables of type {@code T}.
+ * @param streamEmptyIfNull Whether to return an empty stream if the optional extra isn't
+ * present in the intent (or if it had the wrong type, but {@link warnOnTypeError} is true).
+ * If false, return null in these cases, and only return an empty stream if the intent
+ * explicitly provided an empty array for the specified extra.
+ */
+ @Nullable
+ private static <T extends Parcelable> Stream<T> streamParcelableArrayExtra(
+ final Intent intent,
+ String extra,
+ @NonNull Class<T> clazz,
+ boolean warnOnTypeError,
+ boolean streamEmptyIfNull) {
+ T[] result = null;
+
+ try {
+ result = getParcelableArrayExtraIfPresent(intent, extra, clazz);
+ } catch (IllegalArgumentException e) {
+ if (warnOnTypeError) {
+ Log.w(TAG, "Ignoring client-requested " + extra, e);
+ } else {
+ throw e;
+ }
+ }
+
+ if (result != null) {
+ return Arrays.stream(result);
+ } else if (streamEmptyIfNull) {
+ return Stream.empty();
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * If the specified {@code extra} is provided in the {@code intent}, cast it to type {@code T[]}
+ * or throw an {@code IllegalArgumentException} if the cast fails. If the {@code extra} isn't
+ * present in the {@code intent}, return null.
+ */
+ @Nullable
+ private static <T extends Parcelable> T[] getParcelableArrayExtraIfPresent(
+ final Intent intent, String extra, @NonNull Class<T> clazz) throws
+ IllegalArgumentException {
+ if (!intent.hasExtra(extra)) {
+ return null;
+ }
+
+ T[] castResult = intent.getParcelableArrayExtra(extra, clazz);
+ if (castResult == null) {
+ Parcelable[] actualExtrasArray = intent.getParcelableArrayExtra(extra);
+ if (actualExtrasArray != null) {
+ throw new IllegalArgumentException(
+ String.format(
+ "%s is not of type %s[]: %s",
+ extra,
+ clazz.getSimpleName(),
+ Arrays.toString(actualExtrasArray)));
+ } else if (intent.getParcelableExtra(extra) != null) {
+ throw new IllegalArgumentException(
+ String.format(
+ "%s is not of type %s[] (or any array type): %s",
+ extra,
+ clazz.getSimpleName(),
+ intent.getParcelableExtra(extra)));
+ } else {
+ throw new IllegalArgumentException(
+ String.format(
+ "%s is not of type %s (or any Parcelable type): %s",
+ extra,
+ clazz.getSimpleName(),
+ intent.getExtras().get(extra)));
+ }
+ }
+
+ return castResult;
+ }
+
+ private static IntentFilter getTargetIntentFilter(final Intent intent) {
+ try {
+ String dataString = intent.getDataString();
+ if (intent.getType() == null) {
+ if (!TextUtils.isEmpty(dataString)) {
+ return new IntentFilter(intent.getAction(), dataString);
+ }
+ Log.e(TAG, "Failed to get target intent filter: intent data and type are null");
+ return null;
+ }
+ IntentFilter intentFilter = new IntentFilter(intent.getAction(), intent.getType());
+ List<Uri> contentUris = new ArrayList<>();
+ if (Intent.ACTION_SEND.equals(intent.getAction())) {
+ Uri uri = (Uri) intent.getParcelableExtra(Intent.EXTRA_STREAM);
+ if (uri != null) {
+ contentUris.add(uri);
+ }
+ } else {
+ List<Uri> uris = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
+ if (uris != null) {
+ contentUris.addAll(uris);
+ }
+ }
+ for (Uri uri : contentUris) {
+ intentFilter.addDataScheme(uri.getScheme());
+ intentFilter.addDataAuthority(uri.getAuthority(), null);
+ intentFilter.addDataPath(uri.getPath(), PatternMatcher.PATTERN_LITERAL);
+ }
+ return intentFilter;
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to get target intent filter", e);
+ return null;
+ }
+ }
+}