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