summaryrefslogtreecommitdiff
path: root/java/src
diff options
context:
space:
mode:
author Matt Casey <mrcasey@google.com> 2023-05-08 17:52:11 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2023-05-08 17:52:11 +0000
commit56b29f80e91d5dcbeafb55d0193055ebead9d613 (patch)
tree5d505fd829dfce47478c083e7dadd58ef576f77d /java/src
parentf2c7c0722bb77aa4128c50b75466ca3893058ded (diff)
parent7cc1eb3e660decca55d42105835d7935ef444613 (diff)
Merge "Use a ViewState to preserve refinement state" into udc-dev
Diffstat (limited to 'java/src')
-rw-r--r--java/src/com/android/intentresolver/ChooserActivity.java46
-rw-r--r--java/src/com/android/intentresolver/ChooserRefinementManager.java117
2 files changed, 104 insertions, 59 deletions
diff --git a/java/src/com/android/intentresolver/ChooserActivity.java b/java/src/com/android/intentresolver/ChooserActivity.java
index 7f55f78f..8067876f 100644
--- a/java/src/com/android/intentresolver/ChooserActivity.java
+++ b/java/src/com/android/intentresolver/ChooserActivity.java
@@ -72,6 +72,7 @@ import android.view.animation.LinearInterpolator;
import android.widget.TextView;
import androidx.annotation.MainThread;
+import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.viewpager.widget.ViewPager;
@@ -250,20 +251,25 @@ public class ChooserActivity extends ResolverActivity implements
return;
}
- mRefinementManager = new ChooserRefinementManager(
- this,
- mChooserRequest.getRefinementIntentSender(),
- (validatedRefinedTarget) -> {
- maybeRemoveSharedText(validatedRefinedTarget);
+ mRefinementManager = new ViewModelProvider(this).get(ChooserRefinementManager.class);
+
+ mRefinementManager.getRefinementCompletion().observe(this, completion -> {
+ if (completion.consume()) {
+ TargetInfo targetInfo = completion.getTargetInfo();
+ // targetInfo is non-null if the refinement process was successful.
+ if (targetInfo != null) {
+ maybeRemoveSharedText(targetInfo);
// We already block suspended targets from going to refinement, and we probably
// can't recover a Chooser session if that's the reason the refined target fails
// to launch now. Fire-and-forget the refined launch; ignore the return value
// and just make sure the Sharesheet session gets cleaned up regardless.
- super.onTargetSelected(validatedRefinedTarget, false);
- finish();
- },
- this::finish);
+ ChooserActivity.super.onTargetSelected(targetInfo, false);
+ }
+
+ finish();
+ }
+ });
mChooserContentPreviewUi = new ChooserContentPreviewUi(
mChooserRequest.getTargetIntent(),
@@ -611,14 +617,7 @@ public class ChooserActivity extends ResolverActivity implements
Log.d(TAG, "onResume: " + getComponentName().flattenToShortString());
maybeCancelFinishAnimation();
- if (mRefinementManager.isAwaitingRefinementResult()) {
- // This can happen if the refinement activity terminates without ever sending a response
- // to our `ResultReceiver`. We're probably not prepared to return the user into a valid
- // Chooser session, so we'll treat it as a cancellation instead.
- Log.w(TAG, "Chooser resumed while awaiting refinement result; aborting");
- mRefinementManager.destroy();
- finish();
- }
+ mRefinementManager.onActivityResume();
}
@Override
@@ -730,6 +729,8 @@ public class ChooserActivity extends ResolverActivity implements
@Override
protected void onStop() {
super.onStop();
+ mRefinementManager.onActivityStop(isChangingConfigurations());
+
if (maybeCancelFinishAnimation()) {
finish();
}
@@ -743,11 +744,6 @@ public class ChooserActivity extends ResolverActivity implements
mLatencyTracker.onActionCancel(ACTION_LOAD_SHARE_SHEET);
}
- if (mRefinementManager != null) { // TODO: null-checked in case of early-destroy, or skip?
- mRefinementManager.destroy();
- mRefinementManager = null;
- }
-
mBackgroundThreadPoolExecutor.shutdownNow();
destroyProfileRecords();
@@ -873,7 +869,11 @@ public class ChooserActivity extends ResolverActivity implements
@Override
protected boolean onTargetSelected(TargetInfo target, boolean alwaysCheck) {
- if (mRefinementManager.maybeHandleSelection(target)) {
+ if (mRefinementManager.maybeHandleSelection(
+ target,
+ mChooserRequest.getRefinementIntentSender(),
+ getApplication(),
+ getMainThreadHandler())) {
return false;
}
updateModelAndChooserCounts(target);
diff --git a/java/src/com/android/intentresolver/ChooserRefinementManager.java b/java/src/com/android/intentresolver/ChooserRefinementManager.java
index 8d7b1aac..2ebe48a6 100644
--- a/java/src/com/android/intentresolver/ChooserRefinementManager.java
+++ b/java/src/com/android/intentresolver/ChooserRefinementManager.java
@@ -19,16 +19,19 @@ package com.android.intentresolver;
import android.annotation.Nullable;
import android.annotation.UiThread;
import android.app.Activity;
-import android.content.Context;
+import android.app.Application;
import android.content.Intent;
import android.content.IntentSender;
-import android.content.IntentSender.SendIntentException;
import android.os.Bundle;
import android.os.Handler;
import android.os.Parcel;
import android.os.ResultReceiver;
import android.util.Log;
+import androidx.lifecycle.LiveData;
+import androidx.lifecycle.MutableLiveData;
+import androidx.lifecycle.ViewModel;
+
import com.android.intentresolver.chooser.TargetInfo;
import java.util.List;
@@ -42,38 +45,51 @@ import java.util.function.Consumer;
* call).
*/
@UiThread
-public final class ChooserRefinementManager {
+public final class ChooserRefinementManager extends ViewModel {
private static final String TAG = "ChooserRefinement";
- @Nullable
- private final IntentSender mRefinementIntentSender;
-
- private final Context mContext;
- private final Consumer<TargetInfo> mOnSelectionRefined;
- private final Runnable mOnRefinementCancelled;
-
@Nullable // Non-null only during an active refinement session.
private RefinementResultReceiver mRefinementResultReceiver;
- public ChooserRefinementManager(
- Context context,
- @Nullable IntentSender refinementIntentSender,
- Consumer<TargetInfo> onSelectionRefined,
- Runnable onRefinementCancelled) {
- mContext = context;
- mRefinementIntentSender = refinementIntentSender;
- mOnSelectionRefined = onSelectionRefined;
- mOnRefinementCancelled = onRefinementCancelled;
- }
+ private boolean mConfigurationChangeInProgress = false;
/**
- * @return whether a refinement session has been initiated (i.e., an earlier call to
- * {@link #maybeHandleSelection(TargetInfo)} returned true), and isn't yet complete. The session
- * is complete if the refinement activity calls {@link ResultReceiver#onResultReceived()} (with
- * any result), or if it's cancelled on our side by {@link ChooserRefinementManager#destroy()}.
+ * A token for the completion of a refinement process that can be consumed exactly once.
*/
- public boolean isAwaitingRefinementResult() {
- return (mRefinementResultReceiver != null);
+ public static class RefinementCompletion {
+ private TargetInfo mTargetInfo;
+ private boolean mConsumed;
+
+ RefinementCompletion(TargetInfo targetInfo) {
+ mTargetInfo = targetInfo;
+ }
+
+ /**
+ * @return The output of the completed refinement process. Null if the process was aborted
+ * or failed.
+ */
+ public TargetInfo getTargetInfo() {
+ return mTargetInfo;
+ }
+
+ /**
+ * Mark this event as consumed if it wasn't already.
+ *
+ * @return true if this had not already been consumed.
+ */
+ public boolean consume() {
+ if (!mConsumed) {
+ mConsumed = true;
+ return true;
+ }
+ return false;
+ }
+ }
+
+ private MutableLiveData<RefinementCompletion> mRefinementCompletion = new MutableLiveData<>();
+
+ public LiveData<RefinementCompletion> getRefinementCompletion() {
+ return mRefinementCompletion;
}
/**
@@ -81,8 +97,9 @@ public final class ChooserRefinementManager {
* @return true if the selection should wait for a now-started refinement flow, or false if it
* can proceed by the default (non-refinement) logic.
*/
- public boolean maybeHandleSelection(TargetInfo selectedTarget) {
- if (mRefinementIntentSender == null) {
+ public boolean maybeHandleSelection(TargetInfo selectedTarget,
+ IntentSender refinementIntentSender, Application application, Handler mainHandler) {
+ if (refinementIntentSender == null) {
return false;
}
if (selectedTarget.getAllSourceIntents().isEmpty()) {
@@ -100,33 +117,61 @@ public final class ChooserRefinementManager {
mRefinementResultReceiver = new RefinementResultReceiver(
refinedIntent -> {
destroy();
+
TargetInfo refinedTarget =
selectedTarget.tryToCloneWithAppliedRefinement(refinedIntent);
if (refinedTarget != null) {
- mOnSelectionRefined.accept(refinedTarget);
+ mRefinementCompletion.setValue(new RefinementCompletion(refinedTarget));
} else {
Log.e(TAG, "Failed to apply refinement to any matching source intent");
- mOnRefinementCancelled.run();
+ mRefinementCompletion.setValue(new RefinementCompletion(null));
}
},
() -> {
destroy();
- mOnRefinementCancelled.run();
+ mRefinementCompletion.setValue(new RefinementCompletion(null));
},
- mContext.getMainThreadHandler());
+ mainHandler);
Intent refinementRequest = makeRefinementRequest(mRefinementResultReceiver, selectedTarget);
try {
- mRefinementIntentSender.sendIntent(mContext, 0, refinementRequest, null, null);
+ refinementIntentSender.sendIntent(application, 0, refinementRequest, null, null);
return true;
- } catch (SendIntentException e) {
+ } catch (IntentSender.SendIntentException e) {
Log.e(TAG, "Refinement IntentSender failed to send", e);
}
- return false;
+ return true;
+ }
+
+ /** ChooserActivity has stopped */
+ public void onActivityStop(boolean configurationChanging) {
+ mConfigurationChangeInProgress = configurationChanging;
+ }
+
+ /** ChooserActivity has resumed */
+ public void onActivityResume() {
+ if (mConfigurationChangeInProgress) {
+ mConfigurationChangeInProgress = false;
+ } else {
+ if (mRefinementResultReceiver != null) {
+ // This can happen if the refinement activity terminates without ever sending a
+ // response to our `ResultReceiver`. We're probably not prepared to return the user
+ // into a valid Chooser session, so we'll treat it as a cancellation instead.
+ Log.w(TAG, "Chooser resumed while awaiting refinement result; aborting");
+ destroy();
+ mRefinementCompletion.setValue(new RefinementCompletion(null));
+ }
+ }
+ }
+
+ @Override
+ protected void onCleared() {
+ // App lifecycle over, time to clean up.
+ destroy();
}
/** Clean up any ongoing refinement session. */
- public void destroy() {
+ private void destroy() {
if (mRefinementResultReceiver != null) {
mRefinementResultReceiver.destroyReceiver();
mRefinementResultReceiver = null;