diff options
-rw-r--r-- | java/src/com/android/intentresolver/ChooserActivity.java | 24 | ||||
-rw-r--r-- | java/src/com/android/intentresolver/ChooserRefinementManager.java | 77 |
2 files changed, 69 insertions, 32 deletions
diff --git a/java/src/com/android/intentresolver/ChooserActivity.java b/java/src/com/android/intentresolver/ChooserActivity.java index 4565aa0b..270fc299 100644 --- a/java/src/com/android/intentresolver/ChooserActivity.java +++ b/java/src/com/android/intentresolver/ChooserActivity.java @@ -276,14 +276,15 @@ public class ChooserActivity extends ResolverActivity implements mChooserRequest.getRefinementIntentSender(), (validatedRefinedTarget) -> { maybeRemoveSharedText(validatedRefinedTarget); - if (super.onTargetSelected(validatedRefinedTarget, false)) { - finish(); - } - }, - () -> { - mRefinementManager.destroy(); + + // 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); mChooserContentPreviewUi = new ChooserContentPreviewUi( mChooserRequest.getTargetIntent(), @@ -623,6 +624,15 @@ public class ChooserActivity extends ResolverActivity implements super.onResume(); 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(); + } } @Override diff --git a/java/src/com/android/intentresolver/ChooserRefinementManager.java b/java/src/com/android/intentresolver/ChooserRefinementManager.java index 3ddc1c7c..8d7b1aac 100644 --- a/java/src/com/android/intentresolver/ChooserRefinementManager.java +++ b/java/src/com/android/intentresolver/ChooserRefinementManager.java @@ -17,6 +17,7 @@ package com.android.intentresolver; import android.annotation.Nullable; +import android.annotation.UiThread; import android.app.Activity; import android.content.Context; import android.content.Intent; @@ -25,7 +26,6 @@ import android.content.IntentSender.SendIntentException; import android.os.Bundle; import android.os.Handler; import android.os.Parcel; -import android.os.Parcelable; import android.os.ResultReceiver; import android.util.Log; @@ -41,6 +41,7 @@ import java.util.function.Consumer; * convert the format of the payload, or lazy-download some data that was deferred in the original * call). */ +@UiThread public final class ChooserRefinementManager { private static final String TAG = "ChooserRefinement"; @@ -51,7 +52,7 @@ public final class ChooserRefinementManager { private final Consumer<TargetInfo> mOnSelectionRefined; private final Runnable mOnRefinementCancelled; - @Nullable + @Nullable // Non-null only during an active refinement session. private RefinementResultReceiver mRefinementResultReceiver; public ChooserRefinementManager( @@ -66,6 +67,16 @@ public final class ChooserRefinementManager { } /** + * @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()}. + */ + public boolean isAwaitingRefinementResult() { + return (mRefinementResultReceiver != null); + } + + /** * Delegate the user's {@code selectedTarget} to the refinement flow, if possible. * @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. @@ -77,6 +88,13 @@ public final class ChooserRefinementManager { if (selectedTarget.getAllSourceIntents().isEmpty()) { return false; } + if (selectedTarget.isSuspended()) { + // We expect all launches to fail for this target, so don't make the user go through the + // refinement flow first. Besides, the default (non-refinement) handling displays a + // warning in this case and recovers the session; we won't be equipped to recover if + // problems only come up after refinement. + return false; + } destroy(); // Terminate any prior sessions. mRefinementResultReceiver = new RefinementResultReceiver( @@ -91,7 +109,10 @@ public final class ChooserRefinementManager { mOnRefinementCancelled.run(); } }, - mOnRefinementCancelled, + () -> { + destroy(); + mOnRefinementCancelled.run(); + }, mContext.getMainThreadHandler()); Intent refinementRequest = makeRefinementRequest(mRefinementResultReceiver, selectedTarget); @@ -107,7 +128,7 @@ public final class ChooserRefinementManager { /** Clean up any ongoing refinement session. */ public void destroy() { if (mRefinementResultReceiver != null) { - mRefinementResultReceiver.destroy(); + mRefinementResultReceiver.destroyReceiver(); mRefinementResultReceiver = null; } } @@ -144,7 +165,7 @@ public final class ChooserRefinementManager { mOnRefinementCancelled = onRefinementCancelled; } - public void destroy() { + public void destroyReceiver() { mDestroyed = true; } @@ -154,27 +175,14 @@ public final class ChooserRefinementManager { Log.e(TAG, "Destroyed RefinementResultReceiver received a result"); return; } - if (resultData == null) { - Log.e(TAG, "RefinementResultReceiver received null resultData"); - // TODO: treat as cancellation? - return; - } - switch (resultCode) { - case Activity.RESULT_CANCELED: - mOnRefinementCancelled.run(); - break; - case Activity.RESULT_OK: - Parcelable intentParcelable = resultData.getParcelable(Intent.EXTRA_INTENT); - if (intentParcelable instanceof Intent) { - mOnSelectionRefined.accept((Intent) intentParcelable); - } else { - Log.e(TAG, "No valid Intent.EXTRA_INTENT in 'OK' refinement result data"); - } - break; - default: - Log.w(TAG, "Received unknown refinement result " + resultCode); - break; + destroyReceiver(); // This is the single callback we'll accept from this session. + + Intent refinedResult = tryToExtractRefinedResult(resultCode, resultData); + if (refinedResult == null) { + mOnRefinementCancelled.run(); + } else { + mOnSelectionRefined.accept(refinedResult); } } @@ -190,5 +198,24 @@ public final class ChooserRefinementManager { parcel.recycle(); return receiverForSending; } + + /** + * Get the refinement from the result data, if possible, or log diagnostics and return null. + */ + @Nullable + private static Intent tryToExtractRefinedResult(int resultCode, Bundle resultData) { + if (Activity.RESULT_CANCELED == resultCode) { + Log.i(TAG, "Refinement canceled by caller"); + } else if (Activity.RESULT_OK != resultCode) { + Log.w(TAG, "Canceling refinement on unrecognized result code " + resultCode); + } else if (resultData == null) { + Log.e(TAG, "RefinementResultReceiver received null resultData; canceling"); + } else if (!(resultData.getParcelable(Intent.EXTRA_INTENT) instanceof Intent)) { + Log.e(TAG, "No valid Intent.EXTRA_INTENT in 'OK' refinement result data"); + } else { + return resultData.getParcelable(Intent.EXTRA_INTENT, Intent.class); + } + return null; + } } } |