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