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.ChooserTarget
44 import com.android.intentresolver.ChooserActivity
45 import com.android.intentresolver.ContentTypeHint
46 import com.android.intentresolver.R
47 import com.android.intentresolver.data.model.ChooserRequest
48 import com.android.intentresolver.ext.hasSendAction
49 import com.android.intentresolver.ext.ifMatch
50 import com.android.intentresolver.shared.model.ActivityModel
51 import com.android.intentresolver.util.hasValidIcon
52 import com.android.intentresolver.validation.Validation
53 import com.android.intentresolver.validation.ValidationResult
54 import com.android.intentresolver.validation.types.IntentOrUri
55 import com.android.intentresolver.validation.types.array
56 import com.android.intentresolver.validation.types.value
57 import com.android.intentresolver.validation.validateFrom
58
59 private const val MAX_CHOOSER_ACTIONS = 5
60 private const val MAX_INITIAL_INTENTS = 2
61
maybeAddSendActionFlagsnull62 internal fun Intent.maybeAddSendActionFlags() =
63 ifMatch(Intent::hasSendAction) {
64 addFlags(FLAG_ACTIVITY_NEW_DOCUMENT)
65 addFlags(FLAG_ACTIVITY_MULTIPLE_TASK)
66 }
67
readChooserRequestnull68 fun readChooserRequest(
69 model: ActivityModel,
70 savedState: Bundle = model.intent.extras ?: Bundle(),
71 ): ValidationResult<ChooserRequest> {
72 @Suppress("DEPRECATION")
73 return validateFrom(savedState::get) {
74 val targetIntent = required(IntentOrUri(EXTRA_INTENT)).maybeAddSendActionFlags()
75
76 val isSendAction = targetIntent.hasSendAction()
77
78 val additionalTargets = readAlternateIntents() ?: emptyList()
79
80 val replacementExtras = optional(value<Bundle>(EXTRA_REPLACEMENT_EXTRAS))
81
82 val (customTitle, defaultTitleResource) =
83 if (isSendAction) {
84 ignored(
85 value<CharSequence>(EXTRA_TITLE),
86 "deprecated in P. You may wish to set a preview title by using EXTRA_TITLE " +
87 "property of the wrapped EXTRA_INTENT.",
88 )
89 null to R.string.chooseActivity
90 } else {
91 val custom = optional(value<CharSequence>(EXTRA_TITLE))
92 custom to (custom?.let { 0 } ?: R.string.chooseActivity)
93 }
94
95 val initialIntents =
96 optional(array<Intent>(EXTRA_INITIAL_INTENTS))?.take(MAX_INITIAL_INTENTS)?.map {
97 it.maybeAddSendActionFlags()
98 } ?: emptyList()
99
100 val chosenComponentSender =
101 optional(value<IntentSender>(EXTRA_CHOOSER_RESULT_INTENT_SENDER))
102 ?: optional(value<IntentSender>(EXTRA_CHOSEN_COMPONENT_INTENT_SENDER))
103
104 val refinementIntentSender =
105 optional(value<IntentSender>(EXTRA_CHOOSER_REFINEMENT_INTENT_SENDER))
106
107 val filteredComponents =
108 optional(array<ComponentName>(EXTRA_EXCLUDE_COMPONENTS)) ?: emptyList()
109
110 @Suppress("DEPRECATION")
111 val callerChooserTargets =
112 optional(array<ChooserTarget>(EXTRA_CHOOSER_TARGETS)) ?: emptyList()
113
114 val retainInOnStop =
115 optional(value<Boolean>(ChooserActivity.EXTRA_PRIVATE_RETAIN_IN_ON_STOP)) ?: false
116
117 val sharedTextTitle = targetIntent.getCharSequenceExtra(EXTRA_TITLE)
118 val sharedText = targetIntent.getCharSequenceExtra(EXTRA_TEXT)
119
120 val chooserActions = readChooserActions() ?: emptyList()
121
122 val modifyShareAction = optional(value<ChooserAction>(EXTRA_CHOOSER_MODIFY_SHARE_ACTION))
123
124 val additionalContentUri: Uri?
125 val focusedItemPos: Int
126 if (isSendAction) {
127 additionalContentUri = optional(value<Uri>(EXTRA_CHOOSER_ADDITIONAL_CONTENT_URI))
128 focusedItemPos = optional(value<Int>(EXTRA_CHOOSER_FOCUSED_ITEM_POSITION)) ?: 0
129 } else {
130 additionalContentUri = null
131 focusedItemPos = 0
132 }
133
134 val contentTypeHint =
135 when (optional(value<Int>(EXTRA_CHOOSER_CONTENT_TYPE_HINT))) {
136 Intent.CHOOSER_CONTENT_TYPE_ALBUM -> ContentTypeHint.ALBUM
137 else -> ContentTypeHint.NONE
138 }
139
140 val metadataText = optional(value<CharSequence>(EXTRA_METADATA_TEXT))
141
142 ChooserRequest(
143 targetIntent = targetIntent,
144 targetAction = targetIntent.action,
145 isSendActionTarget = isSendAction,
146 targetType = targetIntent.type,
147 launchedFromPackage =
148 requireNotNull(model.launchedFromPackage) {
149 "launch.fromPackage was null, See Activity.getLaunchedFromPackage()"
150 },
151 title = customTitle,
152 defaultTitleResource = defaultTitleResource,
153 referrer = model.referrer,
154 filteredComponentNames = filteredComponents,
155 callerChooserTargets = callerChooserTargets,
156 chooserActions = chooserActions,
157 modifyShareAction = modifyShareAction,
158 shouldRetainInOnStop = retainInOnStop,
159 additionalTargets = additionalTargets,
160 replacementExtras = replacementExtras,
161 initialIntents = initialIntents,
162 chosenComponentSender = chosenComponentSender,
163 refinementIntentSender = refinementIntentSender,
164 sharedText = sharedText,
165 sharedTextTitle = sharedTextTitle,
166 shareTargetFilter = targetIntent.createIntentFilter(),
167 additionalContentUri = additionalContentUri,
168 focusedItemPosition = focusedItemPos,
169 contentTypeHint = contentTypeHint,
170 metadataText = metadataText,
171 )
172 }
173 }
174
Validationnull175 fun Validation.readAlternateIntents(): List<Intent>? =
176 optional(array<Intent>(EXTRA_ALTERNATE_INTENTS))?.map { it.maybeAddSendActionFlags() }
177
Validationnull178 fun Validation.readChooserActions(): List<ChooserAction>? =
179 optional(array<ChooserAction>(EXTRA_CHOOSER_CUSTOM_ACTIONS))
180 ?.filter { hasValidIcon(it) }
181 ?.take(MAX_CHOOSER_ACTIONS)
182