1 /*
<lambda>null2  * Copyright (C) 2024 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.ui
18 
19 import android.app.Activity
20 import android.app.compat.CompatChanges
21 import android.content.ComponentName
22 import android.content.Context
23 import android.content.Intent
24 import android.content.IntentSender
25 import android.service.chooser.ChooserResult
26 import android.service.chooser.ChooserResult.CHOOSER_RESULT_COPY
27 import android.service.chooser.ChooserResult.CHOOSER_RESULT_EDIT
28 import android.service.chooser.ChooserResult.CHOOSER_RESULT_SELECTED_COMPONENT
29 import android.service.chooser.ChooserResult.CHOOSER_RESULT_UNKNOWN
30 import android.service.chooser.ChooserResult.ResultType
31 import android.util.Log
32 import com.android.intentresolver.inject.Background
33 import com.android.intentresolver.inject.ChooserServiceFlags
34 import com.android.intentresolver.inject.Main
35 import com.android.intentresolver.ui.model.ShareAction
36 import dagger.assisted.Assisted
37 import dagger.assisted.AssistedFactory
38 import dagger.assisted.AssistedInject
39 import dagger.hilt.android.qualifiers.ActivityContext
40 import kotlinx.coroutines.CoroutineDispatcher
41 import kotlinx.coroutines.CoroutineScope
42 import kotlinx.coroutines.launch
43 import kotlinx.coroutines.withContext
44 
45 private const val TAG = "ShareResultSender"
46 
47 /** Reports the result of a share to another process across binder, via an [IntentSender] */
48 interface ShareResultSender {
49     /** Reports user selection of an activity to launch from the provided choices. */
50     fun onComponentSelected(component: ComponentName, directShare: Boolean)
51 
52     /** Reports user invocation of a built-in system action. See [ShareAction]. */
53     fun onActionSelected(action: ShareAction)
54 }
55 
56 @AssistedFactory
57 interface ShareResultSenderFactory {
createnull58     fun create(callerUid: Int, chosenComponentSender: IntentSender): ShareResultSenderImpl
59 }
60 
61 /** Dispatches Intents via IntentSender */
62 fun interface IntentSenderDispatcher {
63     fun dispatchIntent(intentSender: IntentSender, intent: Intent)
64 }
65 
66 class ShareResultSenderImpl(
67     private val flags: ChooserServiceFlags,
68     @Main private val scope: CoroutineScope,
69     @Background val backgroundDispatcher: CoroutineDispatcher,
70     private val callerUid: Int,
71     private val resultSender: IntentSender,
72     private val intentDispatcher: IntentSenderDispatcher
73 ) : ShareResultSender {
74     @AssistedInject
75     constructor(
76         @ActivityContext context: Context,
77         flags: ChooserServiceFlags,
78         @Main scope: CoroutineScope,
79         @Background backgroundDispatcher: CoroutineDispatcher,
80         @Assisted callerUid: Int,
81         @Assisted chosenComponentSender: IntentSender,
82     ) : this(
83         flags,
84         scope,
85         backgroundDispatcher,
86         callerUid,
87         chosenComponentSender,
sendernull88         IntentSenderDispatcher { sender, intent -> sender.dispatchIntent(context, intent) }
89     )
90 
onComponentSelectednull91     override fun onComponentSelected(component: ComponentName, directShare: Boolean) {
92         Log.i(TAG, "onComponentSelected: $component directShare=$directShare")
93         scope.launch {
94             val intent = createChosenComponentIntent(component, directShare)
95             intentDispatcher.dispatchIntent(resultSender, intent)
96         }
97     }
98 
onActionSelectednull99     override fun onActionSelected(action: ShareAction) {
100         Log.i(TAG, "onActionSelected: $action")
101         scope.launch {
102             if (flags.enableChooserResult() && chooserResultSupported(callerUid)) {
103                 @ResultType val chosenAction = shareActionToChooserResult(action)
104                 val intent: Intent = createSelectedActionIntent(chosenAction)
105                 intentDispatcher.dispatchIntent(resultSender, intent)
106             } else {
107                 Log.i(TAG, "Not sending SelectedAction")
108             }
109         }
110     }
111 
createChosenComponentIntentnull112     private suspend fun createChosenComponentIntent(
113         component: ComponentName,
114         direct: Boolean,
115     ): Intent {
116         // Add extra with component name for backwards compatibility.
117         val intent: Intent = Intent().putExtra(Intent.EXTRA_CHOSEN_COMPONENT, component)
118 
119         // Add ChooserResult value for Android V+
120         if (flags.enableChooserResult() && chooserResultSupported(callerUid)) {
121             intent.putExtra(
122                 Intent.EXTRA_CHOOSER_RESULT,
123                 ChooserResult(CHOOSER_RESULT_SELECTED_COMPONENT, component, direct)
124             )
125         } else {
126             Log.i(TAG, "Not including ${Intent.EXTRA_CHOOSER_RESULT}")
127         }
128         return intent
129     }
130 
131     @ResultType
shareActionToChooserResultnull132     private fun shareActionToChooserResult(action: ShareAction) =
133         when (action) {
134             ShareAction.SYSTEM_COPY -> CHOOSER_RESULT_COPY
135             ShareAction.SYSTEM_EDIT -> CHOOSER_RESULT_EDIT
136             ShareAction.APPLICATION_DEFINED -> CHOOSER_RESULT_UNKNOWN
137         }
138 
createSelectedActionIntentnull139     private fun createSelectedActionIntent(@ResultType result: Int): Intent {
140         return Intent().putExtra(Intent.EXTRA_CHOOSER_RESULT, ChooserResult(result, null, false))
141     }
142 
chooserResultSupportednull143     private suspend fun chooserResultSupported(uid: Int): Boolean {
144         return withContext(backgroundDispatcher) {
145             // background -> Binder call to system_server
146             CompatChanges.isChangeEnabled(ChooserResult.SEND_CHOOSER_RESULT, uid)
147         }
148     }
149 }
150 
dispatchIntentnull151 private fun IntentSender.dispatchIntent(context: Context, intent: Intent) {
152     try {
153         sendIntent(
154             /* context = */ context,
155             /* code = */ Activity.RESULT_OK,
156             /* intent = */ intent,
157             /* onFinished = */ null,
158             /* handler = */ null
159         )
160     } catch (e: IntentSender.SendIntentException) {
161         Log.e(TAG, "Failed to send intent to IntentSender", e)
162     }
163 }
164