• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2024 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.ActivityOptions
21 import android.content.IntentSender
22 import android.os.OutcomeReceiver
23 import android.window.SplashScreen
24 import androidx.activity.ComponentActivity
25 import com.android.systemui.communal.shared.model.GlanceableHubMultiUserHelper
26 import com.android.systemui.dagger.qualifiers.Background
27 import com.android.systemui.dagger.qualifiers.Main
28 import com.android.systemui.util.nullableAtomicReference
29 import dagger.Lazy
30 import dagger.assisted.Assisted
31 import dagger.assisted.AssistedFactory
32 import dagger.assisted.AssistedInject
33 import java.util.concurrent.Executor
34 import kotlinx.coroutines.CompletableDeferred
35 import kotlinx.coroutines.CoroutineDispatcher
36 import kotlinx.coroutines.withContext
37 
38 /**
39  * Handles starting widget configuration activities and receiving the response to determine if
40  * configuration was successful.
41  */
42 class WidgetConfigurationController
43 @AssistedInject
44 constructor(
45     @Assisted private val activity: ComponentActivity,
46     private val appWidgetHostLazy: Lazy<CommunalAppWidgetHost>,
47     @Background private val bgDispatcher: CoroutineDispatcher,
48     private val glanceableHubMultiUserHelper: GlanceableHubMultiUserHelper,
49     private val glanceableHubWidgetManagerLazy: Lazy<GlanceableHubWidgetManager>,
50     @Main private val mainExecutor: Executor,
51 ) : WidgetConfigurator, OutcomeReceiver<IntentSender?, Throwable> {
52     @AssistedFactory
interfacenull53     fun interface Factory {
54         fun create(activity: ComponentActivity): WidgetConfigurationController
55     }
56 
57     private val activityOptions: ActivityOptions
58         get() =
<lambda>null59             ActivityOptions.makeBasic().apply {
60                 pendingIntentBackgroundActivityStartMode =
61                     ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
62                 splashScreenStyle = SplashScreen.SPLASH_SCREEN_STYLE_SOLID_COLOR
63             }
64 
65     private var result: CompletableDeferred<Boolean>? by nullableAtomicReference()
66 
configureWidgetnull67     override suspend fun configureWidget(appWidgetId: Int): Boolean =
68         withContext(bgDispatcher) {
69             if (result != null) {
70                 throw IllegalStateException("There is already a pending configuration")
71             }
72             result = CompletableDeferred()
73 
74             try {
75                 if (
76                     !glanceableHubMultiUserHelper.glanceableHubHsumFlagEnabled ||
77                         !glanceableHubMultiUserHelper.isInHeadlessSystemUser()
78                 ) {
79                     // Start configuration activity directly if we're running in a foreground user
80                     with(appWidgetHostLazy.get()) {
81                         startAppWidgetConfigureActivityForResult(
82                             activity,
83                             appWidgetId,
84                             0,
85                             REQUEST_CODE,
86                             activityOptions.toBundle(),
87                         )
88                     }
89                 } else {
90                     with(glanceableHubWidgetManagerLazy.get()) {
91                         // Use service to get intent sender and start configuration activity
92                         // locally if running in a headless system user
93                         getIntentSenderForConfigureActivity(
94                             appWidgetId,
95                             outcomeReceiver = this@WidgetConfigurationController,
96                             mainExecutor,
97                         )
98                     }
99                 }
100             } catch (_: Exception) {
101                 setConfigurationResult(Activity.RESULT_CANCELED)
102             }
103             val value = result?.await() == true
104             result = null
105             return@withContext value
106         }
107 
108     // Called when an intent sender is returned, and the configuration activity should be started.
onResultnull109     override fun onResult(intentSender: IntentSender?) {
110         if (intentSender == null) {
111             setConfigurationResult(Activity.RESULT_CANCELED)
112             return
113         }
114 
115         activity.startIntentSenderForResult(
116             intentSender,
117             REQUEST_CODE,
118             /* fillInIntent = */ null,
119             /* flagsMask = */ 0,
120             /* flagsValues = */ 0,
121             /* extraFlags = */ 0,
122             activityOptions.toBundle(),
123         )
124     }
125 
126     // Called when there is an error getting the intent sender.
onErrornull127     override fun onError(e: Throwable) {
128         setConfigurationResult(Activity.RESULT_CANCELED)
129     }
130 
setConfigurationResultnull131     fun setConfigurationResult(resultCode: Int) {
132         result?.complete(resultCode == Activity.RESULT_OK)
133     }
134 
135     companion object {
136         const val REQUEST_CODE = 100
137     }
138 }
139