diff options
| author | 2024-03-06 10:51:18 -0500 | |
|---|---|---|
| committer | 2024-03-07 14:53:22 -0500 | |
| commit | e53d019e8c14c7d17c99b6d75843cdb03fb81d81 (patch) | |
| tree | 65fe3e68d4b20ae4c9852c449f83825664e5f242 /java | |
| parent | 32ac1d1ed6491880439fbbf8e1bca9fb677f5c54 (diff) | |
Introduce payloadtoggle data layer
Bug: 302691505
Flag: ACONFIG android.service.chooser.chooser_payload_toggling DEVELOPMENT
Test: N/A - code isn't live
Change-Id: I5f96b97919db21f3705add6d67fd97ae267c09e2
Diffstat (limited to 'java')
11 files changed, 427 insertions, 0 deletions
diff --git a/java/src/com/android/intentresolver/contentpreview/payloadtoggle/data/model/CustomActionModel.kt b/java/src/com/android/intentresolver/contentpreview/payloadtoggle/data/model/CustomActionModel.kt new file mode 100644 index 00000000..b7945005 --- /dev/null +++ b/java/src/com/android/intentresolver/contentpreview/payloadtoggle/data/model/CustomActionModel.kt @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.intentresolver.contentpreview.payloadtoggle.data.model + +import android.graphics.drawable.Icon + +/** Data model for a custom action the user can take. */ +data class CustomActionModel( + /** Label presented to the user identifying this action. */ + val label: CharSequence, + /** Icon presented to the user for this action. */ + val icon: Icon, + /** When invoked, performs this action. */ + val performAction: () -> Unit, +) diff --git a/java/src/com/android/intentresolver/contentpreview/payloadtoggle/data/repository/ActivityResultRepository.kt b/java/src/com/android/intentresolver/contentpreview/payloadtoggle/data/repository/ActivityResultRepository.kt new file mode 100644 index 00000000..c3bb88c8 --- /dev/null +++ b/java/src/com/android/intentresolver/contentpreview/payloadtoggle/data/repository/ActivityResultRepository.kt @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.intentresolver.contentpreview.payloadtoggle.data.repository + +import dagger.hilt.android.scopes.ActivityRetainedScoped +import javax.inject.Inject +import kotlinx.coroutines.flow.MutableStateFlow + +/** Tracks the result of the current activity. */ +@ActivityRetainedScoped +class ActivityResultRepository @Inject constructor() { + /** The result of the current activity, or `null` if the activity is still active. */ + val activityResult = MutableStateFlow<Int?>(null) +} diff --git a/java/src/com/android/intentresolver/contentpreview/payloadtoggle/data/repository/CursorPreviewsRepository.kt b/java/src/com/android/intentresolver/contentpreview/payloadtoggle/data/repository/CursorPreviewsRepository.kt new file mode 100644 index 00000000..b104d4bf --- /dev/null +++ b/java/src/com/android/intentresolver/contentpreview/payloadtoggle/data/repository/CursorPreviewsRepository.kt @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.intentresolver.contentpreview.payloadtoggle.data.repository + +import com.android.intentresolver.contentpreview.payloadtoggle.shared.model.PreviewsModel +import dagger.hilt.android.scopes.ActivityRetainedScoped +import javax.inject.Inject +import kotlinx.coroutines.flow.MutableStateFlow + +/** + * Stores previews for Shareousel UI that have been cached locally from a remote + * [android.database.Cursor]. + */ +@ActivityRetainedScoped +class CursorPreviewsRepository @Inject constructor() { + /** Previews available for display within Shareousel. */ + val previewsModel = MutableStateFlow<PreviewsModel?>(null) +} diff --git a/java/src/com/android/intentresolver/contentpreview/payloadtoggle/data/repository/PreviewSelectionsRepository.kt b/java/src/com/android/intentresolver/contentpreview/payloadtoggle/data/repository/PreviewSelectionsRepository.kt new file mode 100644 index 00000000..8035580d --- /dev/null +++ b/java/src/com/android/intentresolver/contentpreview/payloadtoggle/data/repository/PreviewSelectionsRepository.kt @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.intentresolver.contentpreview.payloadtoggle.data.repository + +import com.android.intentresolver.contentpreview.payloadtoggle.shared.model.PreviewModel +import dagger.hilt.android.scopes.ViewModelScoped +import javax.inject.Inject +import kotlinx.coroutines.flow.MutableStateFlow + +/** Stores set of selected previews. */ +@ViewModelScoped +class PreviewSelectionsRepository @Inject constructor() { + /** Set of selected previews. */ + val selections = MutableStateFlow<Set<PreviewModel>>(emptySet()) +} diff --git a/java/src/com/android/intentresolver/contentpreview/payloadtoggle/data/repository/TargetIntentRepository.kt b/java/src/com/android/intentresolver/contentpreview/payloadtoggle/data/repository/TargetIntentRepository.kt new file mode 100644 index 00000000..c8436846 --- /dev/null +++ b/java/src/com/android/intentresolver/contentpreview/payloadtoggle/data/repository/TargetIntentRepository.kt @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.intentresolver.contentpreview.payloadtoggle.data.repository + +import android.content.Intent +import com.android.intentresolver.contentpreview.payloadtoggle.data.model.CustomActionModel +import com.android.intentresolver.inject.TargetIntent +import dagger.hilt.android.scopes.ViewModelScoped +import javax.inject.Inject +import kotlinx.coroutines.flow.MutableStateFlow + +/** Stores the target intent of the share sheet, and custom actions derived from the intent. */ +@ViewModelScoped +class TargetIntentRepository +@Inject +constructor( + @TargetIntent initialIntent: Intent, + initialActions: List<CustomActionModel>, +) { + val targetIntent = MutableStateFlow(initialIntent) + + // TODO: this can probably be derived from [targetIntent]; right now, the [initialActions] are + // coming from a different place (ChooserRequest) than later ones (SelectionChangeCallback) + // and so this serves as the source of truth between the two. + val customActions = MutableStateFlow(initialActions) +} diff --git a/java/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/intent/CustomActionPendingIntentSender.kt b/java/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/intent/CustomActionPendingIntentSender.kt new file mode 100644 index 00000000..faad5bbf --- /dev/null +++ b/java/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/intent/CustomActionPendingIntentSender.kt @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.intentresolver.contentpreview.payloadtoggle.domain.intent + +import android.app.ActivityOptions +import android.app.PendingIntent +import android.content.Context +import com.android.intentresolver.R +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.android.qualifiers.ApplicationContext +import dagger.hilt.components.SingletonComponent +import javax.inject.Inject +import javax.inject.Qualifier + +/** [PendingIntentSender] for Shareousel custom actions. */ +class CustomActionPendingIntentSender +@Inject +constructor( + @ApplicationContext private val context: Context, +) : PendingIntentSender { + override fun send(pendingIntent: PendingIntent) { + pendingIntent.send( + /* context = */ null, + /* code = */ 0, + /* intent = */ null, + /* onFinished = */ null, + /* handler = */ null, + /* requiredPermission = */ null, + /* options = */ ActivityOptions.makeCustomAnimation( + context, + R.anim.slide_in_right, + R.anim.slide_out_left, + ) + .toBundle() + ) + } + + @Module + @InstallIn(SingletonComponent::class) + interface Binding { + @Binds + @CustomAction + fun bindSender(sender: CustomActionPendingIntentSender): PendingIntentSender + } +} + +/** [PendingIntentSender] for Shareousel custom actions. */ +@Qualifier @MustBeDocumented @Retention(AnnotationRetention.RUNTIME) annotation class CustomAction diff --git a/java/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/intent/InitialCustomActionsModule.kt b/java/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/intent/InitialCustomActionsModule.kt new file mode 100644 index 00000000..d75884d5 --- /dev/null +++ b/java/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/intent/InitialCustomActionsModule.kt @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.intentresolver.contentpreview.payloadtoggle.domain.intent + +import android.app.PendingIntent +import android.service.chooser.ChooserAction +import android.util.Log +import com.android.intentresolver.contentpreview.payloadtoggle.data.model.CustomActionModel +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.components.ViewModelComponent + +@Module +@InstallIn(ViewModelComponent::class) +object InitialCustomActionsModule { + @Provides + fun initialCustomActionModels( + chooserActions: List<ChooserAction>, + @CustomAction pendingIntentSender: PendingIntentSender, + ): List<CustomActionModel> = chooserActions.map { it.toCustomActionModel(pendingIntentSender) } +} + +/** + * Returns a [CustomActionModel] that sends this [ChooserAction]'s + * [PendingIntent][ChooserAction.getAction]. + */ +fun ChooserAction.toCustomActionModel(pendingIntentSender: PendingIntentSender) = + CustomActionModel( + label = label, + icon = icon, + performAction = { + try { + pendingIntentSender.send(action) + } catch (_: PendingIntent.CanceledException) { + Log.d(TAG, "Custom action, $label, has been cancelled") + } + } + ) + +private const val TAG = "CustomShareActions" diff --git a/java/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/intent/PendingIntentSender.kt b/java/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/intent/PendingIntentSender.kt new file mode 100644 index 00000000..23ba31ba --- /dev/null +++ b/java/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/intent/PendingIntentSender.kt @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.intentresolver.contentpreview.payloadtoggle.domain.intent + +import android.app.PendingIntent + +/** Sends [PendingIntent]s. */ +fun interface PendingIntentSender { + fun send(pendingIntent: PendingIntent) +} diff --git a/java/src/com/android/intentresolver/contentpreview/payloadtoggle/shared/model/PreviewModel.kt b/java/src/com/android/intentresolver/contentpreview/payloadtoggle/shared/model/PreviewModel.kt new file mode 100644 index 00000000..ff96a9f4 --- /dev/null +++ b/java/src/com/android/intentresolver/contentpreview/payloadtoggle/shared/model/PreviewModel.kt @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.intentresolver.contentpreview.payloadtoggle.shared.model + +import android.net.Uri + +/** An individual preview presented in Shareousel. */ +data class PreviewModel( + /** + * Uri for this preview; if this preview is selected, this will be shared with the target app. + */ + val uri: Uri, + /** Mimetype for the data [uri] points to. */ + val mimeType: String?, +) diff --git a/java/src/com/android/intentresolver/contentpreview/payloadtoggle/shared/model/PreviewsModel.kt b/java/src/com/android/intentresolver/contentpreview/payloadtoggle/shared/model/PreviewsModel.kt new file mode 100644 index 00000000..0ac99bd3 --- /dev/null +++ b/java/src/com/android/intentresolver/contentpreview/payloadtoggle/shared/model/PreviewsModel.kt @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.intentresolver.contentpreview.payloadtoggle.shared.model + +/** A dataset of previews for Shareousel. */ +data class PreviewsModel( + /** All available [PreviewModel]s. */ + val previewModels: Set<PreviewModel>, + /** Index into [previewModels] that should be initially displayed to the user. */ + val startIdx: Int, + /** + * Signals that more data should be loaded to the left of this dataset. A `null` value indicates + * that there is no more data to load in that direction. + */ + val loadMoreLeft: (() -> Unit)?, + /** + * Signals that more data should be loaded to the right of this dataset. A `null` value + * indicates that there is no more data to load in that direction. + */ + val loadMoreRight: (() -> Unit)?, +) diff --git a/java/src/com/android/intentresolver/inject/ActivityModelModule.kt b/java/src/com/android/intentresolver/inject/ActivityModelModule.kt new file mode 100644 index 00000000..9a8b8768 --- /dev/null +++ b/java/src/com/android/intentresolver/inject/ActivityModelModule.kt @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.intentresolver.inject + +import android.content.Intent +import android.service.chooser.ChooserAction +import androidx.lifecycle.SavedStateHandle +import com.android.intentresolver.v2.ui.model.ActivityModel +import com.android.intentresolver.v2.ui.model.ChooserRequest +import com.android.intentresolver.v2.ui.viewmodel.readChooserRequest +import com.android.intentresolver.v2.validation.Valid +import com.android.intentresolver.v2.validation.ValidationResult +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.components.ViewModelComponent +import dagger.hilt.android.scopes.ViewModelScoped +import javax.inject.Qualifier + +@Module +@InstallIn(ViewModelComponent::class) +object ActivityModelModule { + @Provides + fun provideActivityModel(savedStateHandle: SavedStateHandle): ActivityModel = + requireNotNull(savedStateHandle[ActivityModel.ACTIVITY_MODEL_KEY]) { + "ActivityModel missing in SavedStateHandle! (${ActivityModel.ACTIVITY_MODEL_KEY})" + } + + @Provides + @ViewModelScoped + fun provideChooserRequest( + activityModel: ActivityModel, + flags: ChooserServiceFlags, + ): ValidationResult<ChooserRequest> = readChooserRequest(activityModel, flags) + + @Provides + @TargetIntent + fun targetIntent(chooserReq: ValidationResult<ChooserRequest>): Intent = + requireNotNull((chooserReq as? Valid)?.value?.targetIntent) { "no target intent available" } + + @Provides + fun customActions(chooserReq: ValidationResult<ChooserRequest>): List<ChooserAction> = + requireNotNull((chooserReq as? Valid)?.value?.chooserActions) { + "no chooser actions available" + } +} + +@Qualifier @MustBeDocumented @Retention(AnnotationRetention.RUNTIME) annotation class TargetIntent |