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.app.Activity 20 import android.app.Application.ActivityLifecycleCallbacks 21 import android.content.Intent 22 import android.content.IntentSender 23 import android.os.Bundle 24 import android.os.RemoteException 25 import android.util.Log 26 import android.view.IWindowManager 27 import android.view.WindowInsets 28 import androidx.activity.ComponentActivity 29 import androidx.activity.compose.setContent 30 import androidx.compose.foundation.background 31 import androidx.compose.foundation.layout.Box 32 import androidx.compose.foundation.layout.fillMaxSize 33 import androidx.compose.material3.MaterialTheme 34 import androidx.compose.ui.Modifier 35 import androidx.lifecycle.lifecycleScope 36 import com.android.app.tracing.coroutines.launchTraced as launch 37 import com.android.compose.theme.PlatformTheme 38 import com.android.internal.logging.UiEventLogger 39 import com.android.systemui.Flags.communalEditWidgetsActivityFinishFix 40 import com.android.systemui.communal.shared.log.CommunalUiEvent 41 import com.android.systemui.communal.shared.model.CommunalScenes 42 import com.android.systemui.communal.shared.model.CommunalTransitionKeys 43 import com.android.systemui.communal.shared.model.EditModeState 44 import com.android.systemui.communal.ui.compose.CommunalHub 45 import com.android.systemui.communal.ui.view.layout.sections.CommunalAppWidgetSection 46 import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel 47 import com.android.systemui.communal.util.WidgetPickerIntentUtils.getWidgetExtraFromIntent 48 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor 49 import com.android.systemui.keyguard.shared.model.KeyguardState 50 import com.android.systemui.log.LogBuffer 51 import com.android.systemui.log.core.Logger 52 import com.android.systemui.log.dagger.CommunalLog 53 import com.android.systemui.scene.shared.flag.SceneContainerFlag 54 import com.android.systemui.settings.UserTracker 55 import javax.inject.Inject 56 import kotlinx.coroutines.flow.first 57 58 /** An Activity for editing the widgets that appear in hub mode. */ 59 class EditWidgetsActivity 60 @Inject 61 constructor( 62 private val communalViewModel: CommunalEditModeViewModel, 63 private val keyguardInteractor: KeyguardInteractor, 64 private var windowManagerService: IWindowManager? = null, 65 private val uiEventLogger: UiEventLogger, 66 private val widgetConfiguratorFactory: WidgetConfigurationController.Factory, 67 private val widgetSection: CommunalAppWidgetSection, 68 private val userTracker: UserTracker, 69 @CommunalLog logBuffer: LogBuffer, 70 ) : ComponentActivity() { 71 companion object { 72 private const val TAG = "EditWidgetsActivity" 73 private const val EXTRA_IS_PENDING_WIDGET_DRAG = "is_pending_widget_drag" 74 const val EXTRA_OPEN_WIDGET_PICKER_ON_START = "open_widget_picker_on_start" 75 76 private const val REQUEST_CODE_WIDGET_PICKER = 200 77 } 78 79 /** 80 * [ActivityController] handles closing the activity in the case it is backgrounded without 81 * waiting for an activity result 82 */ 83 interface ActivityController { 84 /** 85 * Invoked when waiting for an activity result changes, either initiating such wait or 86 * finishing due to the return of a result. 87 */ 88 fun onWaitingForResult(waitingForResult: Boolean) {} 89 90 /** Set the visibility of the activity under control. */ 91 fun setActivityFullyVisible(fullyVisible: Boolean) {} 92 } 93 94 /** 95 * A nop ActivityController to be use when the communalEditWidgetsActivityFinishFix flag is 96 * false. 97 */ 98 class NopActivityController : ActivityController 99 100 /** 101 * A functional ActivityController to be used when the communalEditWidgetsActivityFinishFix flag 102 * is true. 103 */ 104 class ActivityControllerImpl(activity: Activity) : ActivityController { 105 companion object { 106 private const val STATE_EXTRA_IS_WAITING_FOR_RESULT = "extra_is_waiting_for_result" 107 } 108 109 private var waitingForResult = false 110 private var activityFullyVisible = false 111 112 init { 113 activity.registerActivityLifecycleCallbacks( 114 object : ActivityLifecycleCallbacks { 115 override fun onActivityCreated( 116 activity: Activity, 117 savedInstanceState: Bundle?, 118 ) { 119 waitingForResult = 120 savedInstanceState?.getBoolean(STATE_EXTRA_IS_WAITING_FOR_RESULT) 121 ?: false 122 } 123 124 override fun onActivityStarted(activity: Activity) { 125 // Nothing to implement. 126 } 127 128 override fun onActivityResumed(activity: Activity) { 129 // Nothing to implement. 130 } 131 132 override fun onActivityPaused(activity: Activity) { 133 // Nothing to implement. 134 } 135 136 override fun onActivityStopped(activity: Activity) { 137 // If we're not backgrounded due to waiting for a result (either widget 138 // selection or configuration), and we are fully visible, then finish the 139 // activity. 140 if ( 141 !waitingForResult && 142 activityFullyVisible && 143 !activity.isChangingConfigurations 144 ) { 145 activity.finish() 146 } 147 } 148 149 override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) { 150 outState.putBoolean(STATE_EXTRA_IS_WAITING_FOR_RESULT, waitingForResult) 151 } 152 153 override fun onActivityDestroyed(activity: Activity) { 154 // Nothing to implement. 155 } 156 } 157 ) 158 } 159 160 override fun onWaitingForResult(waitingForResult: Boolean) { 161 this.waitingForResult = waitingForResult 162 } 163 164 override fun setActivityFullyVisible(fullyVisible: Boolean) { 165 activityFullyVisible = fullyVisible 166 } 167 } 168 169 private val logger = Logger(logBuffer, "EditWidgetsActivity") 170 171 private val widgetConfigurator by lazy { widgetConfiguratorFactory.create(this) } 172 173 private var shouldOpenWidgetPickerOnStart = false 174 175 private val activityController: ActivityController = 176 if (communalEditWidgetsActivityFinishFix()) ActivityControllerImpl(this) 177 else NopActivityController() 178 179 override fun onCreate(savedInstanceState: Bundle?) { 180 super.onCreate(savedInstanceState) 181 182 listenForTransitionAndChangeScene() 183 184 activityController.setActivityFullyVisible(false) 185 communalViewModel.setEditModeOpen(true) 186 187 val windowInsetsController = window.decorView.windowInsetsController 188 windowInsetsController?.hide(WindowInsets.Type.systemBars()) 189 window.setDecorFitsSystemWindows(false) 190 191 shouldOpenWidgetPickerOnStart = 192 intent.getBooleanExtra(EXTRA_OPEN_WIDGET_PICKER_ON_START, false) 193 194 setContent { 195 PlatformTheme { 196 Box( 197 modifier = 198 Modifier.fillMaxSize().background(MaterialTheme.colorScheme.surfaceDim) 199 ) { 200 CommunalHub( 201 viewModel = communalViewModel, 202 onOpenWidgetPicker = ::onOpenWidgetPicker, 203 widgetConfigurator = widgetConfigurator, 204 onEditDone = ::onEditDone, 205 widgetSection = widgetSection, 206 ) 207 } 208 } 209 } 210 } 211 212 // Handle scene change to show the activity and animate in its content 213 private fun listenForTransitionAndChangeScene() { 214 lifecycleScope.launch { 215 communalViewModel.canShowEditMode.collect { 216 if (!SceneContainerFlag.isEnabled) { 217 communalViewModel.changeScene( 218 scene = CommunalScenes.Blank, 219 loggingReason = "edit mode opening", 220 transitionKey = CommunalTransitionKeys.ToEditMode, 221 keyguardState = KeyguardState.GONE, 222 ) 223 // wait till transitioned to Blank scene, then animate in communal content in 224 // edit mode 225 communalViewModel.currentScene.first { it == CommunalScenes.Blank } 226 } 227 228 // Wait for dream to exit, if we were previously dreaming. 229 keyguardInteractor.isDreaming.first { !it } 230 231 communalViewModel.setEditModeState(EditModeState.SHOWING) 232 233 // Inform the ActivityController that we are now fully visible. 234 activityController.setActivityFullyVisible(true) 235 236 // Show the widget picker, if necessary, after the edit activity has animated in. 237 // Waiting until after the activity has appeared avoids transitions issues. 238 if (shouldOpenWidgetPickerOnStart) { 239 onOpenWidgetPicker() 240 shouldOpenWidgetPickerOnStart = false 241 } 242 } 243 } 244 } 245 246 private fun onOpenWidgetPicker() { 247 lifecycleScope.launch { 248 communalViewModel.onOpenWidgetPicker(resources) { intent: Intent -> 249 startActivityForResultAsUser( 250 intent, 251 REQUEST_CODE_WIDGET_PICKER, 252 userTracker.userHandle, 253 ) 254 } 255 } 256 } 257 258 private fun onEditDone() { 259 lifecycleScope.launch { 260 communalViewModel.cleanupEditModeState() 261 262 communalViewModel.changeScene( 263 scene = CommunalScenes.Communal, 264 loggingReason = "edit mode closing", 265 transitionKey = CommunalTransitionKeys.FromEditMode, 266 ) 267 268 // Wait for the current scene to be idle on communal. 269 communalViewModel.isIdleOnCommunal.first { it } 270 271 // Lock to go back to the hub after exiting. 272 lockNow() 273 finish() 274 } 275 } 276 277 override fun startActivityForResult(intent: Intent, requestCode: Int, options: Bundle?) { 278 activityController.onWaitingForResult(true) 279 super.startActivityForResult(intent, requestCode, options) 280 } 281 282 override fun startIntentSenderForResult( 283 intent: IntentSender, 284 requestCode: Int, 285 fillInIntent: Intent?, 286 flagsMask: Int, 287 flagsValues: Int, 288 extraFlags: Int, 289 options: Bundle?, 290 ) { 291 activityController.onWaitingForResult(true) 292 super.startIntentSenderForResult( 293 intent, 294 requestCode, 295 fillInIntent, 296 flagsMask, 297 flagsValues, 298 extraFlags, 299 options, 300 ) 301 } 302 303 override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { 304 activityController.onWaitingForResult(false) 305 super.onActivityResult(requestCode, resultCode, data) 306 307 when (requestCode) { 308 WidgetConfigurationController.REQUEST_CODE -> 309 widgetConfigurator.setConfigurationResult(resultCode) 310 REQUEST_CODE_WIDGET_PICKER -> { 311 if (resultCode != RESULT_OK) { 312 Log.w(TAG, "Failed to receive result from widget picker, code=$resultCode") 313 return 314 } 315 316 uiEventLogger.log(CommunalUiEvent.COMMUNAL_HUB_WIDGET_PICKER_SHOWN) 317 318 data?.let { intent -> 319 val isPendingWidgetDrag = 320 intent.getBooleanExtra(EXTRA_IS_PENDING_WIDGET_DRAG, false) 321 // Nothing to do when a widget is being dragged & dropped. The drop 322 // target in the communal grid will receive the widget to be added (if 323 // the user drops it over). 324 if (!isPendingWidgetDrag) { 325 val (componentName, user) = getWidgetExtraFromIntent(intent) 326 if (componentName != null && user != null) { 327 // Add widget at the end. 328 communalViewModel.onAddWidget( 329 componentName, 330 user, 331 configurator = widgetConfigurator, 332 ) 333 } else { 334 run { Log.w(TAG, "No AppWidgetProviderInfo found in result.") } 335 } 336 } 337 } ?: run { Log.w(TAG, "No data in result.") } 338 } 339 } 340 } 341 342 override fun onStart() { 343 super.onStart() 344 345 communalViewModel.setEditActivityShowing(true) 346 347 logger.i("Starting the communal widget editor activity") 348 uiEventLogger.log(CommunalUiEvent.COMMUNAL_HUB_EDIT_MODE_SHOWN) 349 } 350 351 override fun onStop() { 352 super.onStop() 353 communalViewModel.setEditActivityShowing(false) 354 355 logger.i("Stopping the communal widget editor activity") 356 uiEventLogger.log(CommunalUiEvent.COMMUNAL_HUB_EDIT_MODE_GONE) 357 } 358 359 override fun onDestroy() { 360 super.onDestroy() 361 communalViewModel.cleanupEditModeState() 362 communalViewModel.setEditModeOpen(false) 363 } 364 365 private fun lockNow() { 366 try { 367 checkNotNull(windowManagerService).lockNow(/* options */ null) 368 } catch (e: RemoteException) { 369 Log.e(TAG, "Couldn't lock the device as WindowManager is dead.") 370 } 371 } 372 } 373