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