• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * 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 package com.android.intentresolver.ui.viewmodel
17 
18 import android.content.ComponentName
19 import android.content.Intent
20 import android.content.Intent.EXTRA_ALTERNATE_INTENTS
21 import android.content.Intent.EXTRA_CHOOSER_ADDITIONAL_CONTENT_URI
22 import android.content.Intent.EXTRA_CHOOSER_CONTENT_TYPE_HINT
23 import android.content.Intent.EXTRA_CHOOSER_CUSTOM_ACTIONS
24 import android.content.Intent.EXTRA_CHOOSER_FOCUSED_ITEM_POSITION
25 import android.content.Intent.EXTRA_CHOOSER_MODIFY_SHARE_ACTION
26 import android.content.Intent.EXTRA_CHOOSER_REFINEMENT_INTENT_SENDER
27 import android.content.Intent.EXTRA_CHOOSER_RESULT_INTENT_SENDER
28 import android.content.Intent.EXTRA_CHOOSER_TARGETS
29 import android.content.Intent.EXTRA_CHOSEN_COMPONENT_INTENT_SENDER
30 import android.content.Intent.EXTRA_EXCLUDE_COMPONENTS
31 import android.content.Intent.EXTRA_INITIAL_INTENTS
32 import android.content.Intent.EXTRA_INTENT
33 import android.content.Intent.EXTRA_METADATA_TEXT
34 import android.content.Intent.EXTRA_REPLACEMENT_EXTRAS
35 import android.content.Intent.EXTRA_TEXT
36 import android.content.Intent.EXTRA_TITLE
37 import android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK
38 import android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT
39 import android.content.IntentSender
40 import android.net.Uri
41 import android.os.Bundle
42 import android.service.chooser.ChooserAction
43 import android.service.chooser.ChooserSession
44 import android.service.chooser.ChooserTarget
45 import com.android.intentresolver.ChooserActivity
46 import com.android.intentresolver.ContentTypeHint
47 import com.android.intentresolver.Flags.interactiveSession
48 import com.android.intentresolver.R
49 import com.android.intentresolver.data.model.ChooserRequest
50 import com.android.intentresolver.ext.hasSendAction
51 import com.android.intentresolver.ext.ifMatch
52 import com.android.intentresolver.shared.model.ActivityModel
53 import com.android.intentresolver.util.hasValidIcon
54 import com.android.intentresolver.validation.Validation
55 import com.android.intentresolver.validation.ValidationResult
56 import com.android.intentresolver.validation.types.IntentOrUri
57 import com.android.intentresolver.validation.types.array
58 import com.android.intentresolver.validation.types.value
59 import com.android.intentresolver.validation.validateFrom
60 
61 private const val MAX_CHOOSER_ACTIONS = 5
62 private const val MAX_INITIAL_INTENTS = 2
63 private const val EXTRA_CHOOSER_INTERACTIVE_CALLBACK =
64     "com.android.extra.EXTRA_CHOOSER_INTERACTIVE_CALLBACK"
65 
maybeAddSendActionFlagsnull66 internal fun Intent.maybeAddSendActionFlags() =
67     ifMatch(Intent::hasSendAction) {
68         addFlags(FLAG_ACTIVITY_NEW_DOCUMENT)
69         addFlags(FLAG_ACTIVITY_MULTIPLE_TASK)
70     }
71 
readChooserRequestnull72 fun readChooserRequest(
73     model: ActivityModel,
74     savedState: Bundle = model.intent.extras ?: Bundle(),
75 ): ValidationResult<ChooserRequest> {
76     return readChooserRequest(savedState, model.launchedFromPackage, model.referrer)
77 }
78 
readChooserRequestnull79 fun readChooserRequest(
80     savedState: Bundle,
81     launchedFromPackage: String,
82     referrer: Uri?,
83 ): ValidationResult<ChooserRequest> {
84     @Suppress("DEPRECATION")
85     return validateFrom(savedState::get) {
86         val targetIntent = required(IntentOrUri(EXTRA_INTENT)).maybeAddSendActionFlags()
87 
88         val isSendAction = targetIntent.hasSendAction()
89 
90         val additionalTargets = readAlternateIntents() ?: emptyList()
91 
92         val replacementExtras = optional(value<Bundle>(EXTRA_REPLACEMENT_EXTRAS))
93 
94         val (customTitle, defaultTitleResource) =
95             if (isSendAction) {
96                 ignored(
97                     value<CharSequence>(EXTRA_TITLE),
98                     "deprecated in P. You may wish to set a preview title by using EXTRA_TITLE " +
99                         "property of the wrapped EXTRA_INTENT.",
100                 )
101                 null to R.string.chooseActivity
102             } else {
103                 val custom = optional(value<CharSequence>(EXTRA_TITLE))
104                 custom to (custom?.let { 0 } ?: R.string.chooseActivity)
105             }
106 
107         val initialIntents =
108             optional(array<Intent>(EXTRA_INITIAL_INTENTS))?.take(MAX_INITIAL_INTENTS)?.map {
109                 it.maybeAddSendActionFlags()
110             } ?: emptyList()
111 
112         val chosenComponentSender =
113             optional(value<IntentSender>(EXTRA_CHOOSER_RESULT_INTENT_SENDER))
114                 ?: optional(value<IntentSender>(EXTRA_CHOSEN_COMPONENT_INTENT_SENDER))
115 
116         val refinementIntentSender =
117             optional(value<IntentSender>(EXTRA_CHOOSER_REFINEMENT_INTENT_SENDER))
118 
119         val filteredComponents =
120             optional(array<ComponentName>(EXTRA_EXCLUDE_COMPONENTS)) ?: emptyList()
121 
122         @Suppress("DEPRECATION")
123         val callerChooserTargets =
124             optional(array<ChooserTarget>(EXTRA_CHOOSER_TARGETS)) ?: emptyList()
125 
126         val retainInOnStop =
127             optional(value<Boolean>(ChooserActivity.EXTRA_PRIVATE_RETAIN_IN_ON_STOP)) ?: false
128 
129         val sharedTextTitle = targetIntent.getCharSequenceExtra(EXTRA_TITLE)
130         val sharedText = targetIntent.getCharSequenceExtra(EXTRA_TEXT)
131 
132         val chooserActions = readChooserActions() ?: emptyList()
133 
134         val modifyShareAction = optional(value<ChooserAction>(EXTRA_CHOOSER_MODIFY_SHARE_ACTION))
135 
136         val additionalContentUri: Uri?
137         val focusedItemPos: Int
138         if (isSendAction) {
139             additionalContentUri = optional(value<Uri>(EXTRA_CHOOSER_ADDITIONAL_CONTENT_URI))
140             focusedItemPos = optional(value<Int>(EXTRA_CHOOSER_FOCUSED_ITEM_POSITION)) ?: 0
141         } else {
142             additionalContentUri = null
143             focusedItemPos = 0
144         }
145 
146         val contentTypeHint =
147             when (optional(value<Int>(EXTRA_CHOOSER_CONTENT_TYPE_HINT))) {
148                 Intent.CHOOSER_CONTENT_TYPE_ALBUM -> ContentTypeHint.ALBUM
149                 else -> ContentTypeHint.NONE
150             }
151 
152         val metadataText = optional(value<CharSequence>(EXTRA_METADATA_TEXT))
153 
154         val interactiveSessionCallback =
155             if (interactiveSession()) {
156                 optional(value<ChooserSession>(EXTRA_CHOOSER_INTERACTIVE_CALLBACK))
157                     ?.sessionCallbackBinder
158             } else {
159                 null
160             }
161 
162         ChooserRequest(
163             targetIntent = targetIntent,
164             targetAction = targetIntent.action,
165             isSendActionTarget = isSendAction,
166             targetType = targetIntent.type,
167             launchedFromPackage =
168                 requireNotNull(launchedFromPackage) {
169                     "launch.fromPackage was null, See Activity.getLaunchedFromPackage()"
170                 },
171             title = customTitle,
172             defaultTitleResource = defaultTitleResource,
173             referrer = referrer,
174             filteredComponentNames = filteredComponents,
175             callerChooserTargets = callerChooserTargets,
176             chooserActions = chooserActions,
177             modifyShareAction = modifyShareAction,
178             shouldRetainInOnStop = retainInOnStop,
179             additionalTargets = additionalTargets,
180             replacementExtras = replacementExtras,
181             initialIntents = initialIntents,
182             chosenComponentSender = chosenComponentSender,
183             refinementIntentSender = refinementIntentSender,
184             sharedText = sharedText,
185             sharedTextTitle = sharedTextTitle,
186             shareTargetFilter = targetIntent.createIntentFilter(),
187             additionalContentUri = additionalContentUri,
188             focusedItemPosition = focusedItemPos,
189             contentTypeHint = contentTypeHint,
190             metadataText = metadataText,
191             interactiveSessionCallback = interactiveSessionCallback,
192         )
193     }
194 }
195 
Validationnull196 fun Validation.readAlternateIntents(): List<Intent>? =
197     optional(array<Intent>(EXTRA_ALTERNATE_INTENTS))?.map { it.maybeAddSendActionFlags() }
198 
Validationnull199 fun Validation.readChooserActions(): List<ChooserAction>? =
200     optional(array<ChooserAction>(EXTRA_CHOOSER_CUSTOM_ACTIONS))
201         ?.filter { hasValidIcon(it) }
202         ?.take(MAX_CHOOSER_ACTIONS)
203