• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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