1 /* <lambda>null2 * Copyright (C) 2023 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.systemui.communal.ui.viewmodel 18 19 import android.appwidget.AppWidgetManager 20 import android.appwidget.AppWidgetProviderInfo 21 import android.content.Intent 22 import android.content.pm.PackageManager 23 import android.content.res.Resources 24 import android.util.Log 25 import androidx.activity.result.ActivityResultLauncher 26 import com.android.internal.logging.UiEventLogger 27 import com.android.systemui.Flags.enableWidgetPickerSizeFilter 28 import com.android.systemui.communal.domain.interactor.CommunalInteractor 29 import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor 30 import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor 31 import com.android.systemui.communal.domain.model.CommunalContentModel 32 import com.android.systemui.communal.shared.log.CommunalUiEvent 33 import com.android.systemui.communal.shared.model.EditModeState 34 import com.android.systemui.dagger.SysUISingleton 35 import com.android.systemui.dagger.qualifiers.Background 36 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor 37 import com.android.systemui.keyguard.shared.model.KeyguardState 38 import com.android.systemui.log.LogBuffer 39 import com.android.systemui.log.core.Logger 40 import com.android.systemui.log.dagger.CommunalLog 41 import com.android.systemui.media.controls.ui.view.MediaHost 42 import com.android.systemui.media.dagger.MediaModule 43 import com.android.systemui.res.R 44 import com.android.systemui.util.kotlin.BooleanFlowOperators.allOf 45 import javax.inject.Inject 46 import javax.inject.Named 47 import kotlinx.coroutines.CoroutineDispatcher 48 import kotlinx.coroutines.flow.Flow 49 import kotlinx.coroutines.flow.MutableStateFlow 50 import kotlinx.coroutines.flow.StateFlow 51 import kotlinx.coroutines.flow.filter 52 import kotlinx.coroutines.flow.first 53 import kotlinx.coroutines.flow.map 54 import kotlinx.coroutines.flow.onEach 55 import kotlinx.coroutines.withContext 56 57 /** The view model for communal hub in edit mode. */ 58 @SysUISingleton 59 class CommunalEditModeViewModel 60 @Inject 61 constructor( 62 communalSceneInteractor: CommunalSceneInteractor, 63 private val communalInteractor: CommunalInteractor, 64 private val communalSettingsInteractor: CommunalSettingsInteractor, 65 keyguardTransitionInteractor: KeyguardTransitionInteractor, 66 @Named(MediaModule.COMMUNAL_HUB) mediaHost: MediaHost, 67 private val uiEventLogger: UiEventLogger, 68 @CommunalLog logBuffer: LogBuffer, 69 @Background private val backgroundDispatcher: CoroutineDispatcher, 70 ) : BaseCommunalViewModel(communalSceneInteractor, communalInteractor, mediaHost) { 71 72 private val logger = Logger(logBuffer, "CommunalEditModeViewModel") 73 74 override val isEditMode = true 75 76 override val isCommunalContentVisible: Flow<Boolean> = 77 communalSceneInteractor.editModeState.map { it == EditModeState.SHOWING } 78 79 /** 80 * Emits when edit mode activity can show, after we've transitioned to [KeyguardState.GONE] 81 * and edit mode is open. 82 */ 83 val canShowEditMode = 84 allOf( 85 keyguardTransitionInteractor.isFinishedInState(KeyguardState.GONE), 86 communalInteractor.editModeOpen 87 ) 88 .filter { it } 89 90 // Only widgets are editable. 91 override val communalContent: Flow<List<CommunalContentModel>> = 92 communalInteractor.widgetContent.onEach { models -> 93 logger.d({ "Content updated: $str1" }) { str1 = models.joinToString { it.key } } 94 } 95 96 private val _reorderingWidgets = MutableStateFlow(false) 97 98 override val reorderingWidgets: StateFlow<Boolean> 99 get() = _reorderingWidgets 100 101 override fun onDeleteWidget(id: Int) = communalInteractor.deleteWidget(id) 102 103 override fun onReorderWidgets(widgetIdToPriorityMap: Map<Int, Int>) = 104 communalInteractor.updateWidgetOrder(widgetIdToPriorityMap) 105 106 override fun onReorderWidgetStart() { 107 // Clear selection status 108 setSelectedKey(null) 109 _reorderingWidgets.value = true 110 uiEventLogger.log(CommunalUiEvent.COMMUNAL_HUB_REORDER_WIDGET_START) 111 } 112 113 override fun onReorderWidgetEnd() { 114 _reorderingWidgets.value = false 115 uiEventLogger.log(CommunalUiEvent.COMMUNAL_HUB_REORDER_WIDGET_FINISH) 116 } 117 118 override fun onReorderWidgetCancel() { 119 _reorderingWidgets.value = false 120 uiEventLogger.log(CommunalUiEvent.COMMUNAL_HUB_REORDER_WIDGET_CANCEL) 121 } 122 123 val isIdleOnCommunal: StateFlow<Boolean> = communalInteractor.isIdleOnCommunal 124 125 /** Launch the widget picker activity using the given {@link ActivityResultLauncher}. */ 126 suspend fun onOpenWidgetPicker( 127 resources: Resources, 128 packageManager: PackageManager, 129 activityLauncher: ActivityResultLauncher<Intent> 130 ): Boolean = 131 withContext(backgroundDispatcher) { 132 val widgets = communalInteractor.widgetContent.first() 133 val excludeList = 134 widgets.filterIsInstance<CommunalContentModel.WidgetContent.Widget>().mapTo( 135 ArrayList() 136 ) { 137 it.providerInfo 138 } 139 getWidgetPickerActivityIntent(resources, packageManager, excludeList)?.let { 140 try { 141 activityLauncher.launch(it) 142 return@withContext true 143 } catch (e: Exception) { 144 Log.e(TAG, "Failed to launch widget picker activity", e) 145 } 146 } 147 false 148 } 149 150 private fun getWidgetPickerActivityIntent( 151 resources: Resources, 152 packageManager: PackageManager, 153 excludeList: ArrayList<AppWidgetProviderInfo> 154 ): Intent? { 155 val packageName = 156 getLauncherPackageName(packageManager) 157 ?: run { 158 Log.e(TAG, "Couldn't resolve launcher package name") 159 return@getWidgetPickerActivityIntent null 160 } 161 162 return Intent(Intent.ACTION_PICK).apply { 163 setPackage(packageName) 164 if (enableWidgetPickerSizeFilter()) { 165 putExtra( 166 EXTRA_DESIRED_WIDGET_WIDTH, 167 resources.getDimensionPixelSize(R.dimen.communal_widget_picker_desired_width) 168 ) 169 putExtra( 170 EXTRA_DESIRED_WIDGET_HEIGHT, 171 resources.getDimensionPixelSize(R.dimen.communal_widget_picker_desired_height) 172 ) 173 } 174 putExtra( 175 AppWidgetManager.EXTRA_CATEGORY_FILTER, 176 communalSettingsInteractor.communalWidgetCategories.value 177 ) 178 putExtra(EXTRA_UI_SURFACE_KEY, EXTRA_UI_SURFACE_VALUE) 179 putParcelableArrayListExtra(EXTRA_ADDED_APP_WIDGETS_KEY, excludeList) 180 } 181 } 182 183 private fun getLauncherPackageName(packageManager: PackageManager): String? { 184 return packageManager 185 .resolveActivity( 186 Intent(Intent.ACTION_MAIN).also { it.addCategory(Intent.CATEGORY_HOME) }, 187 PackageManager.MATCH_DEFAULT_ONLY 188 ) 189 ?.activityInfo 190 ?.packageName 191 } 192 193 /** Sets whether edit mode is currently open */ 194 fun setEditModeOpen(isOpen: Boolean) = communalInteractor.setEditModeOpen(isOpen) 195 196 /** Called when exiting the edit mode, before transitioning back to the communal scene. */ 197 fun cleanupEditModeState() { 198 communalSceneInteractor.setEditModeState(null) 199 } 200 201 companion object { 202 private const val TAG = "CommunalEditModeViewModel" 203 204 private const val EXTRA_DESIRED_WIDGET_WIDTH = "desired_widget_width" 205 private const val EXTRA_DESIRED_WIDGET_HEIGHT = "desired_widget_height" 206 private const val EXTRA_UI_SURFACE_KEY = "ui_surface" 207 private const val EXTRA_UI_SURFACE_VALUE = "widgets_hub" 208 const val EXTRA_ADDED_APP_WIDGETS_KEY = "added_app_widgets" 209 } 210 } 211