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.widgets 18 19 import android.content.Intent 20 import android.os.Bundle 21 import android.os.RemoteException 22 import android.util.Log 23 import android.view.IWindowManager 24 import android.view.WindowInsets 25 import androidx.activity.ComponentActivity 26 import androidx.activity.compose.setContent 27 import androidx.activity.result.ActivityResultLauncher 28 import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult 29 import androidx.compose.foundation.background 30 import androidx.compose.foundation.layout.Box 31 import androidx.compose.foundation.layout.fillMaxSize 32 import androidx.compose.ui.Modifier 33 import androidx.lifecycle.lifecycleScope 34 import com.android.compose.theme.LocalAndroidColorScheme 35 import com.android.compose.theme.PlatformTheme 36 import com.android.internal.logging.UiEventLogger 37 import com.android.systemui.communal.shared.log.CommunalUiEvent 38 import com.android.systemui.communal.shared.model.CommunalScenes 39 import com.android.systemui.communal.shared.model.CommunalTransitionKeys 40 import com.android.systemui.communal.shared.model.EditModeState 41 import com.android.systemui.communal.ui.compose.CommunalHub 42 import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel 43 import com.android.systemui.communal.util.WidgetPickerIntentUtils.getWidgetExtraFromIntent 44 import com.android.systemui.log.LogBuffer 45 import com.android.systemui.log.core.Logger 46 import com.android.systemui.log.dagger.CommunalLog 47 import javax.inject.Inject 48 import kotlinx.coroutines.flow.first 49 import kotlinx.coroutines.launch 50 51 /** An Activity for editing the widgets that appear in hub mode. */ 52 class EditWidgetsActivity 53 @Inject 54 constructor( 55 private val communalViewModel: CommunalEditModeViewModel, 56 private var windowManagerService: IWindowManager? = null, 57 private val uiEventLogger: UiEventLogger, 58 private val widgetConfiguratorFactory: WidgetConfigurationController.Factory, 59 @CommunalLog logBuffer: LogBuffer, 60 ) : ComponentActivity() { 61 companion object { 62 private const val TAG = "EditWidgetsActivity" 63 private const val EXTRA_IS_PENDING_WIDGET_DRAG = "is_pending_widget_drag" 64 const val EXTRA_PRESELECTED_KEY = "preselected_key" 65 const val EXTRA_OPEN_WIDGET_PICKER_ON_START = "open_widget_picker_on_start" 66 } 67 68 private val logger = Logger(logBuffer, "EditWidgetsActivity") 69 70 private val widgetConfigurator by lazy { widgetConfiguratorFactory.create(this) } 71 72 private var shouldOpenWidgetPickerOnStart = false 73 74 private val addWidgetActivityLauncher: ActivityResultLauncher<Intent> = 75 registerForActivityResult(StartActivityForResult()) { result -> 76 when (result.resultCode) { 77 RESULT_OK -> { 78 uiEventLogger.log(CommunalUiEvent.COMMUNAL_HUB_WIDGET_PICKER_SHOWN) 79 80 result.data?.let { intent -> 81 val isPendingWidgetDrag = 82 intent.getBooleanExtra(EXTRA_IS_PENDING_WIDGET_DRAG, false) 83 // Nothing to do when a widget is being dragged & dropped. The drop 84 // target in the communal grid will receive the widget to be added (if 85 // the user drops it over). 86 if (!isPendingWidgetDrag) { 87 val (componentName, user) = getWidgetExtraFromIntent(intent) 88 if (componentName != null && user != null) { 89 communalViewModel.onAddWidget( 90 componentName, 91 user, 92 0, 93 widgetConfigurator 94 ) 95 } else { 96 run { Log.w(TAG, "No AppWidgetProviderInfo found in result.") } 97 } 98 } 99 } ?: run { Log.w(TAG, "No data in result.") } 100 } 101 else -> 102 Log.w( 103 TAG, 104 "Failed to receive result from widget picker, code=${result.resultCode}" 105 ) 106 } 107 } 108 109 override fun onCreate(savedInstanceState: Bundle?) { 110 super.onCreate(savedInstanceState) 111 listenForTransitionAndChangeScene() 112 113 communalViewModel.setEditModeOpen(true) 114 115 val windowInsetsController = window.decorView.windowInsetsController 116 windowInsetsController?.hide(WindowInsets.Type.systemBars()) 117 window.setDecorFitsSystemWindows(false) 118 119 val preselectedKey = intent.getStringExtra(EXTRA_PRESELECTED_KEY) 120 shouldOpenWidgetPickerOnStart = 121 intent.getBooleanExtra(EXTRA_OPEN_WIDGET_PICKER_ON_START, false) 122 123 communalViewModel.setSelectedKey(preselectedKey) 124 125 setContent { 126 PlatformTheme { 127 Box( 128 modifier = 129 Modifier.fillMaxSize() 130 .background(LocalAndroidColorScheme.current.outlineVariant), 131 ) { 132 CommunalHub( 133 viewModel = communalViewModel, 134 onOpenWidgetPicker = ::onOpenWidgetPicker, 135 widgetConfigurator = widgetConfigurator, 136 onEditDone = ::onEditDone, 137 ) 138 } 139 } 140 } 141 } 142 143 // Handle scene change to show the activity and animate in its content 144 private fun listenForTransitionAndChangeScene() { 145 lifecycleScope.launch { 146 communalViewModel.canShowEditMode.collect { 147 communalViewModel.changeScene( 148 CommunalScenes.Blank, 149 CommunalTransitionKeys.ToEditMode 150 ) 151 // wait till transitioned to Blank scene, then animate in communal content in 152 // edit mode 153 communalViewModel.currentScene.first { it == CommunalScenes.Blank } 154 communalViewModel.setEditModeState(EditModeState.SHOWING) 155 } 156 } 157 } 158 159 private fun onOpenWidgetPicker() { 160 lifecycleScope.launch { 161 communalViewModel.onOpenWidgetPicker( 162 resources, 163 packageManager, 164 addWidgetActivityLauncher 165 ) 166 } 167 } 168 169 private fun onEditDone() { 170 lifecycleScope.launch { 171 communalViewModel.cleanupEditModeState() 172 173 communalViewModel.changeScene( 174 CommunalScenes.Communal, 175 CommunalTransitionKeys.FromEditMode 176 ) 177 178 // Wait for the current scene to be idle on communal. 179 communalViewModel.isIdleOnCommunal.first { it } 180 181 // Lock to go back to the hub after exiting. 182 lockNow() 183 finish() 184 } 185 } 186 187 override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { 188 super.onActivityResult(requestCode, resultCode, data) 189 if (requestCode == WidgetConfigurationController.REQUEST_CODE) { 190 widgetConfigurator.setConfigurationResult(resultCode) 191 } 192 } 193 194 override fun onStart() { 195 super.onStart() 196 197 if (shouldOpenWidgetPickerOnStart) { 198 onOpenWidgetPicker() 199 shouldOpenWidgetPickerOnStart = false 200 } 201 202 logger.i("Starting the communal widget editor activity") 203 uiEventLogger.log(CommunalUiEvent.COMMUNAL_HUB_EDIT_MODE_SHOWN) 204 } 205 206 override fun onStop() { 207 super.onStop() 208 209 logger.i("Stopping the communal widget editor activity") 210 uiEventLogger.log(CommunalUiEvent.COMMUNAL_HUB_EDIT_MODE_GONE) 211 } 212 213 override fun onDestroy() { 214 super.onDestroy() 215 communalViewModel.cleanupEditModeState() 216 communalViewModel.setEditModeOpen(false) 217 } 218 219 private fun lockNow() { 220 try { 221 checkNotNull(windowManagerService).lockNow(/* options */ null) 222 } catch (e: RemoteException) { 223 Log.e(TAG, "Couldn't lock the device as WindowManager is dead.") 224 } 225 } 226 } 227