1 /* 2 * Copyright (C) 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.intentresolver; 18 19 import android.annotation.Nullable; 20 import android.app.Activity; 21 import android.content.Context; 22 import android.content.Intent; 23 import android.content.IntentSender; 24 import android.content.IntentSender.SendIntentException; 25 import android.os.Bundle; 26 import android.os.Handler; 27 import android.os.Parcel; 28 import android.os.Parcelable; 29 import android.os.ResultReceiver; 30 import android.util.Log; 31 32 import com.android.intentresolver.chooser.TargetInfo; 33 34 import java.util.List; 35 import java.util.function.Consumer; 36 37 /** 38 * Helper class to manage Sharesheet's "refinement" flow, where callers supply a "refinement 39 * activity" that will be invoked when a target is selected, allowing the calling app to add 40 * additional extras and other refinements (subject to {@link Intent#filterEquals()}), e.g., to 41 * convert the format of the payload, or lazy-download some data that was deferred in the original 42 * call). 43 */ 44 public final class ChooserRefinementManager { 45 private static final String TAG = "ChooserRefinement"; 46 47 @Nullable 48 private final IntentSender mRefinementIntentSender; 49 50 private final Context mContext; 51 private final Consumer<TargetInfo> mOnSelectionRefined; 52 private final Runnable mOnRefinementCancelled; 53 54 @Nullable 55 private RefinementResultReceiver mRefinementResultReceiver; 56 ChooserRefinementManager( Context context, @Nullable IntentSender refinementIntentSender, Consumer<TargetInfo> onSelectionRefined, Runnable onRefinementCancelled)57 public ChooserRefinementManager( 58 Context context, 59 @Nullable IntentSender refinementIntentSender, 60 Consumer<TargetInfo> onSelectionRefined, 61 Runnable onRefinementCancelled) { 62 mContext = context; 63 mRefinementIntentSender = refinementIntentSender; 64 mOnSelectionRefined = onSelectionRefined; 65 mOnRefinementCancelled = onRefinementCancelled; 66 } 67 68 /** 69 * Delegate the user's {@code selectedTarget} to the refinement flow, if possible. 70 * @return true if the selection should wait for a now-started refinement flow, or false if it 71 * can proceed by the default (non-refinement) logic. 72 */ maybeHandleSelection(TargetInfo selectedTarget)73 public boolean maybeHandleSelection(TargetInfo selectedTarget) { 74 if (mRefinementIntentSender == null) { 75 return false; 76 } 77 if (selectedTarget.getAllSourceIntents().isEmpty()) { 78 return false; 79 } 80 81 destroy(); // Terminate any prior sessions. 82 mRefinementResultReceiver = new RefinementResultReceiver( 83 refinedIntent -> { 84 destroy(); 85 TargetInfo refinedTarget = 86 selectedTarget.tryToCloneWithAppliedRefinement(refinedIntent); 87 if (refinedTarget != null) { 88 mOnSelectionRefined.accept(refinedTarget); 89 } else { 90 Log.e(TAG, "Failed to apply refinement to any matching source intent"); 91 mOnRefinementCancelled.run(); 92 } 93 }, 94 mOnRefinementCancelled, 95 mContext.getMainThreadHandler()); 96 97 Intent refinementRequest = makeRefinementRequest(mRefinementResultReceiver, selectedTarget); 98 try { 99 mRefinementIntentSender.sendIntent(mContext, 0, refinementRequest, null, null); 100 return true; 101 } catch (SendIntentException e) { 102 Log.e(TAG, "Refinement IntentSender failed to send", e); 103 } 104 return false; 105 } 106 107 /** Clean up any ongoing refinement session. */ destroy()108 public void destroy() { 109 if (mRefinementResultReceiver != null) { 110 mRefinementResultReceiver.destroy(); 111 mRefinementResultReceiver = null; 112 } 113 } 114 makeRefinementRequest( RefinementResultReceiver resultReceiver, TargetInfo originalTarget)115 private static Intent makeRefinementRequest( 116 RefinementResultReceiver resultReceiver, TargetInfo originalTarget) { 117 final Intent fillIn = new Intent(); 118 final List<Intent> sourceIntents = originalTarget.getAllSourceIntents(); 119 fillIn.putExtra(Intent.EXTRA_INTENT, sourceIntents.get(0)); 120 final int sourceIntentCount = sourceIntents.size(); 121 if (sourceIntentCount > 1) { 122 fillIn.putExtra( 123 Intent.EXTRA_ALTERNATE_INTENTS, 124 sourceIntents 125 .subList(1, sourceIntentCount) 126 .toArray(new Intent[sourceIntentCount - 1])); 127 } 128 fillIn.putExtra(Intent.EXTRA_RESULT_RECEIVER, resultReceiver.copyForSending()); 129 return fillIn; 130 } 131 132 private static class RefinementResultReceiver extends ResultReceiver { 133 private final Consumer<Intent> mOnSelectionRefined; 134 private final Runnable mOnRefinementCancelled; 135 136 private boolean mDestroyed; 137 RefinementResultReceiver( Consumer<Intent> onSelectionRefined, Runnable onRefinementCancelled, Handler handler)138 RefinementResultReceiver( 139 Consumer<Intent> onSelectionRefined, 140 Runnable onRefinementCancelled, 141 Handler handler) { 142 super(handler); 143 mOnSelectionRefined = onSelectionRefined; 144 mOnRefinementCancelled = onRefinementCancelled; 145 } 146 destroy()147 public void destroy() { 148 mDestroyed = true; 149 } 150 151 @Override onReceiveResult(int resultCode, Bundle resultData)152 protected void onReceiveResult(int resultCode, Bundle resultData) { 153 if (mDestroyed) { 154 Log.e(TAG, "Destroyed RefinementResultReceiver received a result"); 155 return; 156 } 157 if (resultData == null) { 158 Log.e(TAG, "RefinementResultReceiver received null resultData"); 159 // TODO: treat as cancellation? 160 return; 161 } 162 163 switch (resultCode) { 164 case Activity.RESULT_CANCELED: 165 mOnRefinementCancelled.run(); 166 break; 167 case Activity.RESULT_OK: 168 Parcelable intentParcelable = resultData.getParcelable(Intent.EXTRA_INTENT); 169 if (intentParcelable instanceof Intent) { 170 mOnSelectionRefined.accept((Intent) intentParcelable); 171 } else { 172 Log.e(TAG, "No valid Intent.EXTRA_INTENT in 'OK' refinement result data"); 173 } 174 break; 175 default: 176 Log.w(TAG, "Received unknown refinement result " + resultCode); 177 break; 178 } 179 } 180 181 /** 182 * Apps can't load this class directly, so we need a regular ResultReceiver copy for 183 * sending. Obtain this by parceling and unparceling (one weird trick). 184 */ copyForSending()185 ResultReceiver copyForSending() { 186 Parcel parcel = Parcel.obtain(); 187 writeToParcel(parcel, 0); 188 parcel.setDataPosition(0); 189 ResultReceiver receiverForSending = ResultReceiver.CREATOR.createFromParcel(parcel); 190 parcel.recycle(); 191 return receiverForSending; 192 } 193 } 194 } 195