• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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