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