summaryrefslogtreecommitdiff
path: root/java/src
diff options
context:
space:
mode:
author Joshua Trask <joshtrask@google.com> 2022-10-20 15:10:31 -0400
committer Joshua Trask <joshtrask@google.com> 2022-11-02 18:48:54 +0000
commit13c54bc6fc60fb1d4330f75824ed756157bdff71 (patch)
treedb73828be07c7cb8a9d6336e9286d2c09536a7c8 /java/src
parent61ef701f9e6cb0033307f28b864898e6abe2b146 (diff)
Chooser fragments shouldn't save instance state.
1. I can't think of any way we could possibly trigger the fragment's load-from-saved-state flow since Sharesheet isn't in history. 2. If this flow was ever triggered for the "stacked app" fragment we would crash. Our first step in restoring from a `Bundle` is to make an unsafe downcast to `MultiDisplayResolveInfo` and invoke one of its subclass methods, but `MultiDisplayResolveInfo` doesn't actually implement `Parcelable` correctly; it inherits the relationship via `DisplayResolveInfo`, including its `Creator`, so any attempt to restore the parceled target would get a concrete `DisplayResolveInfo`, slicing off any data specific to its old multi-target type (and, of course, leaving an object that can't use any of the subclass methods). 3. Support for this flow seems to be the only reason that `DisplayResolveInfo` implements `Parcelable` at all, so as part of go/chooser-targetinfo-cleanup we should prune this (broken and dead) code and then go on to remove all of that complexity from the `TargetInfo` API. Test: manual & presubmit Bug: 202167050 Change-Id: Ida3b74edf975e3c1353e757b37e4cfadf716d5c3
Diffstat (limited to 'java/src')
-rw-r--r--java/src/com/android/intentresolver/ChooserActivity.java64
-rw-r--r--java/src/com/android/intentresolver/ChooserStackedAppDialogFragment.java64
-rw-r--r--java/src/com/android/intentresolver/ChooserTargetActionsDialogFragment.java107
-rw-r--r--java/src/com/android/intentresolver/chooser/DisplayResolveInfo.java41
4 files changed, 125 insertions, 151 deletions
diff --git a/java/src/com/android/intentresolver/ChooserActivity.java b/java/src/com/android/intentresolver/ChooserActivity.java
index a73424bf..b0e2450b 100644
--- a/java/src/com/android/intentresolver/ChooserActivity.java
+++ b/java/src/com/android/intentresolver/ChooserActivity.java
@@ -271,8 +271,6 @@ public class ChooserActivity extends ResolverActivity implements
private int mLastNumberOfChildren = -1;
private int mMaxTargetsPerRow = 1;
- private static final String TARGET_DETAILS_FRAGMENT_TAG = "targetDetailsFragment";
-
private static final int MAX_LOG_RANK_POSITION = 12;
private static final int MAX_EXTRA_INITIAL_INTENTS = 2;
@@ -1718,33 +1716,28 @@ public class ChooserActivity extends ResolverActivity implements
return;
}
- ChooserTargetActionsDialogFragment fragment = new ChooserTargetActionsDialogFragment();
- Bundle bundle = new Bundle();
-
- bundle.putParcelable(ChooserTargetActionsDialogFragment.USER_HANDLE_KEY,
- mChooserMultiProfilePagerAdapter.getCurrentUserHandle());
- bundle.putParcelableArrayList(ChooserTargetActionsDialogFragment.TARGET_INFOS_KEY,
- new ArrayList<>(targetList));
-
- if (targetInfo.isSelectableTargetInfo()) {
- // TODO: migrate this condition to polymorphic calls on TargetInfo (maybe in some cases
- // we can safely drop the `isSelectableTargetInfo()` condition and populate the bundle
- // with any non-null values we find, regardless of the target type?)
- bundle.putString(ChooserTargetActionsDialogFragment.SHORTCUT_ID_KEY,
- targetInfo.getChooserTarget().getIntentExtras().getString(
- Intent.EXTRA_SHORTCUT_ID));
- bundle.putBoolean(ChooserTargetActionsDialogFragment.IS_SHORTCUT_PINNED_KEY,
- targetInfo.isPinned());
- bundle.putParcelable(ChooserTargetActionsDialogFragment.INTENT_FILTER_KEY,
- getTargetIntentFilter());
- if (targetInfo.getDisplayLabel() != null) {
- bundle.putString(ChooserTargetActionsDialogFragment.SHORTCUT_TITLE_KEY,
- targetInfo.getDisplayLabel().toString());
- }
- }
-
- fragment.setArguments(bundle);
- fragment.show(getSupportFragmentManager(), TARGET_DETAILS_FRAGMENT_TAG);
+ // 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;
+ String shortcutTitle = targetInfo.isSelectableTargetInfo()
+ ? targetInfo.getDisplayLabel().toString() : null;
+ String shortcutIdKey = targetInfo.isSelectableTargetInfo()
+ ? targetInfo
+ .getChooserTarget()
+ .getIntentExtras()
+ .getString(Intent.EXTRA_SHORTCUT_ID)
+ : null;
+
+ ChooserTargetActionsDialogFragment.show(
+ getSupportFragmentManager(),
+ targetList,
+ mChooserMultiProfilePagerAdapter.getCurrentUserHandle(),
+ shortcutIdKey,
+ shortcutTitle,
+ isShortcutPinned,
+ intentFilter);
}
private void modifyTargetIntent(Intent in) {
@@ -1801,16 +1794,11 @@ public class ChooserActivity extends ResolverActivity implements
if (targetInfo.isMultiDisplayResolveInfo()) {
MultiDisplayResolveInfo mti = (MultiDisplayResolveInfo) targetInfo;
if (!mti.hasSelected()) {
- ChooserStackedAppDialogFragment f = new ChooserStackedAppDialogFragment();
- Bundle b = new Bundle();
- b.putParcelable(ChooserTargetActionsDialogFragment.USER_HANDLE_KEY,
+ ChooserStackedAppDialogFragment.show(
+ getSupportFragmentManager(),
+ mti,
+ which,
mChooserMultiProfilePagerAdapter.getCurrentUserHandle());
- b.putObject(ChooserStackedAppDialogFragment.MULTI_DRI_KEY,
- mti);
- b.putInt(ChooserStackedAppDialogFragment.WHICH_KEY, which);
- f.setArguments(b);
-
- f.show(getSupportFragmentManager(), TARGET_DETAILS_FRAGMENT_TAG);
return;
}
}
diff --git a/java/src/com/android/intentresolver/ChooserStackedAppDialogFragment.java b/java/src/com/android/intentresolver/ChooserStackedAppDialogFragment.java
index b4e427a1..2cfceeae 100644
--- a/java/src/com/android/intentresolver/ChooserStackedAppDialogFragment.java
+++ b/java/src/com/android/intentresolver/ChooserStackedAppDialogFragment.java
@@ -20,9 +20,10 @@ package com.android.intentresolver;
import android.content.DialogInterface;
import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable;
-import android.os.Bundle;
import android.os.UserHandle;
+import androidx.fragment.app.FragmentManager;
+
import com.android.intentresolver.chooser.DisplayResolveInfo;
import com.android.intentresolver.chooser.MultiDisplayResolveInfo;
@@ -30,29 +31,39 @@ import com.android.intentresolver.chooser.MultiDisplayResolveInfo;
* Shows individual actions for a "stacked" app target - such as an app with multiple posting
* streams represented in the Sharesheet.
*/
-public class ChooserStackedAppDialogFragment extends ChooserTargetActionsDialogFragment
- implements DialogInterface.OnClickListener {
-
- static final String WHICH_KEY = "which_key";
- static final String MULTI_DRI_KEY = "multi_dri_key";
-
- private MultiDisplayResolveInfo mMultiDisplayResolveInfo;
- private int mParentWhich;
-
- public ChooserStackedAppDialogFragment() {}
+public class ChooserStackedAppDialogFragment extends ChooserTargetActionsDialogFragment {
- void setStateFromBundle(Bundle b) {
- mMultiDisplayResolveInfo = (MultiDisplayResolveInfo) b.get(MULTI_DRI_KEY);
- mTargetInfos = mMultiDisplayResolveInfo.getAllDisplayTargets();
- mUserHandle = (UserHandle) b.get(USER_HANDLE_KEY);
- mParentWhich = b.getInt(WHICH_KEY);
+ /**
+ * Display a fragment for the user to select one of the members of a target "stack."
+ * @param stackedTarget The display info for the full stack to select within.
+ * @param stackedTargetParentWhich The "which" value that the {@link ChooserActivity} uses to
+ * identify the {@code stackedTarget} as presented in the chooser menu UI. If the user selects
+ * a target in this fragment, the selection will be saved in the {@link MultiDisplayResolveInfo}
+ * and then the {@link ChooserActivity} will receive a {@code #startSelected()} callback using
+ * this "which" value to identify the stack that's now unambiguously resolved.
+ * @param userHandle
+ *
+ * TODO: consider taking a client-provided callback instead of {@code stackedTargetParentWhich}
+ * to avoid coupling with {@link ChooserActivity}'s mechanism for handling the selection.
+ */
+ public static void show(
+ FragmentManager fragmentManager,
+ MultiDisplayResolveInfo stackedTarget,
+ int stackedTargetParentWhich,
+ UserHandle userHandle) {
+ ChooserStackedAppDialogFragment fragment = new ChooserStackedAppDialogFragment(
+ stackedTarget, stackedTargetParentWhich, userHandle);
+ fragment.show(fragmentManager, TARGET_DETAILS_FRAGMENT_TAG);
}
+ private final MultiDisplayResolveInfo mMultiDisplayResolveInfo;
+ private final int mParentWhich;
+
@Override
- public void onSaveInstanceState(Bundle outState) {
- super.onSaveInstanceState(outState);
- outState.putInt(WHICH_KEY, mParentWhich);
- outState.putParcelable(MULTI_DRI_KEY, mMultiDisplayResolveInfo);
+ public void onClick(DialogInterface dialog, int which) {
+ mMultiDisplayResolveInfo.setSelected(which);
+ ((ChooserActivity) getActivity()).startSelected(mParentWhich, false, true);
+ dismiss();
}
@Override
@@ -63,15 +74,16 @@ public class ChooserStackedAppDialogFragment extends ChooserTargetActionsDialogF
@Override
protected Drawable getItemIcon(DisplayResolveInfo dri) {
-
// Show no icon for the group disambig dialog, null hides the imageview
return null;
}
- @Override
- public void onClick(DialogInterface dialog, int which) {
- mMultiDisplayResolveInfo.setSelected(which);
- ((ChooserActivity) getActivity()).startSelected(mParentWhich, false, true);
- dismiss();
+ private ChooserStackedAppDialogFragment(
+ MultiDisplayResolveInfo stackedTarget,
+ int stackedTargetParentWhich,
+ UserHandle userHandle) {
+ super(stackedTarget.getAllDisplayTargets(), userHandle);
+ mMultiDisplayResolveInfo = stackedTarget;
+ mParentWhich = stackedTargetParentWhich;
}
}
diff --git a/java/src/com/android/intentresolver/ChooserTargetActionsDialogFragment.java b/java/src/com/android/intentresolver/ChooserTargetActionsDialogFragment.java
index 61b54fa9..f4d4a6d1 100644
--- a/java/src/com/android/intentresolver/ChooserTargetActionsDialogFragment.java
+++ b/java/src/com/android/intentresolver/ChooserTargetActionsDialogFragment.java
@@ -49,11 +49,11 @@ import android.widget.ImageView;
import android.widget.TextView;
import androidx.fragment.app.DialogFragment;
+import androidx.fragment.app.FragmentManager;
import androidx.recyclerview.widget.RecyclerView;
import com.android.intentresolver.chooser.DisplayResolveInfo;
-import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
@@ -64,68 +64,61 @@ import java.util.stream.Collectors;
public class ChooserTargetActionsDialogFragment extends DialogFragment
implements DialogInterface.OnClickListener {
- protected ArrayList<DisplayResolveInfo> mTargetInfos = new ArrayList<>();
- protected UserHandle mUserHandle;
- protected String mShortcutId;
- protected String mShortcutTitle;
- protected boolean mIsShortcutPinned;
- protected IntentFilter mIntentFilter;
+ protected final static String TARGET_DETAILS_FRAGMENT_TAG = "targetDetailsFragment";
- public static final String USER_HANDLE_KEY = "user_handle";
- public static final String TARGET_INFOS_KEY = "target_infos";
- public static final String SHORTCUT_ID_KEY = "shortcut_id";
- public static final String SHORTCUT_TITLE_KEY = "shortcut_title";
- public static final String IS_SHORTCUT_PINNED_KEY = "is_shortcut_pinned";
- public static final String INTENT_FILTER_KEY = "intent_filter";
+ private final List<DisplayResolveInfo> mTargetInfos;
+ private final UserHandle mUserHandle;
+ private final boolean mIsShortcutPinned;
- public ChooserTargetActionsDialogFragment() {}
+ @Nullable
+ private final String mShortcutId;
+
+ @Nullable
+ private final String mShortcutTitle;
+
+ @Nullable
+ private final IntentFilter mIntentFilter;
+
+ public static void show(
+ FragmentManager fragmentManager,
+ List<DisplayResolveInfo> targetInfos,
+ UserHandle userHandle,
+ @Nullable String shortcutId,
+ @Nullable String shortcutTitle,
+ boolean isShortcutPinned,
+ @Nullable IntentFilter intentFilter) {
+ ChooserTargetActionsDialogFragment fragment = new ChooserTargetActionsDialogFragment(
+ targetInfos,
+ userHandle,
+ shortcutId,
+ shortcutTitle,
+ isShortcutPinned,
+ intentFilter);
+ fragment.show(fragmentManager, TARGET_DETAILS_FRAGMENT_TAG);
+ }
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+
if (savedInstanceState != null) {
- setStateFromBundle(savedInstanceState);
- } else {
- setStateFromBundle(getArguments());
+ // Bail. It's probably not possible to trigger reloading our fragments from a saved
+ // instance since Sharesheet isn't kept in history and the entire session will probably
+ // be lost under any conditions that would've triggered our retention. Nevertheless, if
+ // we ever *did* try to load from a saved state, we wouldn't be able to populate valid
+ // data (since we wouldn't be able to get back our original TargetInfos if we had to
+ // restore them from a Bundle).
+ dismissAllowingStateLoss();
}
}
- void setStateFromBundle(Bundle b) {
- mTargetInfos = (ArrayList<DisplayResolveInfo>) b.get(TARGET_INFOS_KEY);
- mUserHandle = (UserHandle) b.get(USER_HANDLE_KEY);
- mShortcutId = b.getString(SHORTCUT_ID_KEY);
- mShortcutTitle = b.getString(SHORTCUT_TITLE_KEY);
- mIsShortcutPinned = b.getBoolean(IS_SHORTCUT_PINNED_KEY);
- mIntentFilter = (IntentFilter) b.get(INTENT_FILTER_KEY);
- }
-
- @Override
- public void onSaveInstanceState(Bundle outState) {
- super.onSaveInstanceState(outState);
-
- outState.putParcelable(ChooserTargetActionsDialogFragment.USER_HANDLE_KEY,
- mUserHandle);
- outState.putParcelableArrayList(ChooserTargetActionsDialogFragment.TARGET_INFOS_KEY,
- mTargetInfos);
- outState.putString(ChooserTargetActionsDialogFragment.SHORTCUT_ID_KEY, mShortcutId);
- outState.putBoolean(ChooserTargetActionsDialogFragment.IS_SHORTCUT_PINNED_KEY,
- mIsShortcutPinned);
- outState.putString(ChooserTargetActionsDialogFragment.SHORTCUT_TITLE_KEY, mShortcutTitle);
- outState.putParcelable(ChooserTargetActionsDialogFragment.INTENT_FILTER_KEY, mIntentFilter);
- }
-
/**
- * Recreate the layout from scratch to match new Sharesheet redlines
+ * Build the menu UI according to our design spec.
*/
@Override
public View onCreateView(LayoutInflater inflater,
@Nullable ViewGroup container,
Bundle savedInstanceState) {
- if (savedInstanceState != null) {
- setStateFromBundle(savedInstanceState);
- } else {
- setStateFromBundle(getArguments());
- }
// Make the background transparent to show dialog rounding
Optional.of(getDialog()).map(Dialog::getWindow)
.ifPresent(window -> {
@@ -294,4 +287,24 @@ public class ChooserTargetActionsDialogFragment extends DialogFragment
private boolean isShortcutTarget() {
return mShortcutId != null;
}
+
+ protected ChooserTargetActionsDialogFragment(
+ List<DisplayResolveInfo> targetInfos, UserHandle userHandle) {
+ this(targetInfos, userHandle, null, null, false, null);
+ }
+
+ private ChooserTargetActionsDialogFragment(
+ List<DisplayResolveInfo> targetInfos,
+ UserHandle userHandle,
+ @Nullable String shortcutId,
+ @Nullable String shortcutTitle,
+ boolean isShortcutPinned,
+ @Nullable IntentFilter intentFilter) {
+ mTargetInfos = targetInfos;
+ mUserHandle = userHandle;
+ mShortcutId = shortcutId;
+ mShortcutTitle = shortcutTitle;
+ mIsShortcutPinned = isShortcutPinned;
+ mIntentFilter = intentFilter;
+ }
}
diff --git a/java/src/com/android/intentresolver/chooser/DisplayResolveInfo.java b/java/src/com/android/intentresolver/chooser/DisplayResolveInfo.java
index cd6828c7..daa69152 100644
--- a/java/src/com/android/intentresolver/chooser/DisplayResolveInfo.java
+++ b/java/src/com/android/intentresolver/chooser/DisplayResolveInfo.java
@@ -27,8 +27,6 @@ import android.content.pm.ApplicationInfo;
import android.content.pm.ResolveInfo;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
-import android.os.Parcel;
-import android.os.Parcelable;
import android.os.UserHandle;
import com.android.intentresolver.ResolverActivity;
@@ -42,7 +40,7 @@ import java.util.List;
* A TargetInfo plus additional information needed to render it (such as icon and label) and
* resolve it to an activity.
*/
-public class DisplayResolveInfo implements TargetInfo, Parcelable {
+public class DisplayResolveInfo implements TargetInfo {
private final ResolveInfo mResolveInfo;
private CharSequence mDisplayLabel;
private Drawable mDisplayIcon;
@@ -237,41 +235,4 @@ public class DisplayResolveInfo implements TargetInfo, Parcelable {
public void setPinned(boolean pinned) {
mPinned = pinned;
}
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeCharSequence(mDisplayLabel);
- dest.writeCharSequence(mExtendedInfo);
- dest.writeParcelable(mResolvedIntent, 0);
- dest.writeTypedList(mSourceIntents);
- dest.writeBoolean(mIsSuspended);
- dest.writeBoolean(mPinned);
- dest.writeParcelable(mResolveInfo, 0);
- }
-
- public static final Parcelable.Creator<DisplayResolveInfo> CREATOR =
- new Parcelable.Creator<DisplayResolveInfo>() {
- public DisplayResolveInfo createFromParcel(Parcel in) {
- return new DisplayResolveInfo(in);
- }
-
- public DisplayResolveInfo[] newArray(int size) {
- return new DisplayResolveInfo[size];
- }
- };
-
- private DisplayResolveInfo(Parcel in) {
- mDisplayLabel = in.readCharSequence();
- mExtendedInfo = in.readCharSequence();
- mResolvedIntent = in.readParcelable(null /* ClassLoader */, android.content.Intent.class);
- in.readTypedList(mSourceIntents, Intent.CREATOR);
- mIsSuspended = in.readBoolean();
- mPinned = in.readBoolean();
- mResolveInfo = in.readParcelable(null /* ClassLoader */, android.content.pm.ResolveInfo.class);
- }
}