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.notetask 18 19 import android.app.Service 20 import android.content.Context 21 import android.content.Intent 22 import android.graphics.drawable.Icon 23 import android.os.IBinder 24 import android.os.UserHandle 25 import com.android.internal.infra.ServiceConnector 26 import com.android.systemui.dagger.SysUISingleton 27 import com.android.systemui.dagger.qualifiers.Application 28 import com.android.systemui.dagger.qualifiers.Background 29 import com.android.systemui.log.DebugLogger.debugLog 30 import com.android.wm.shell.bubbles.Bubbles 31 import java.util.Optional 32 import javax.inject.Inject 33 import kotlin.coroutines.resume 34 import kotlin.coroutines.suspendCoroutine 35 import kotlinx.coroutines.CoroutineDispatcher 36 import kotlinx.coroutines.withContext 37 38 /** 39 * A utility class to help interact with [Bubbles] as system user. The SysUI instance running as 40 * system user is the only instance that has the instance of [Bubbles] that manages the notes app 41 * bubble for all users. 42 * 43 * <p>Note: This class is made overridable so that a fake can be created for as mocking suspending 44 * functions is not supported by the Android tree's version of mockito. 45 */ 46 @SysUISingleton 47 open class NoteTaskBubblesController 48 @Inject 49 constructor( 50 @Application private val context: Context, 51 @Background private val bgDispatcher: CoroutineDispatcher 52 ) { 53 54 private val serviceConnector: ServiceConnector<INoteTaskBubblesService> = 55 ServiceConnector.Impl( 56 context, 57 Intent(context, NoteTaskBubblesService::class.java), 58 Context.BIND_AUTO_CREATE or Context.BIND_WAIVE_PRIORITY or Context.BIND_NOT_VISIBLE, 59 UserHandle.USER_SYSTEM, 60 INoteTaskBubblesService.Stub::asInterface 61 ) 62 63 /** Returns whether notes app bubble is supported. */ 64 open suspend fun areBubblesAvailable(): Boolean = 65 withContext(bgDispatcher) { 66 suspendCoroutine { continuation -> 67 serviceConnector 68 .postForResult { it.areBubblesAvailable() } 69 .whenComplete { available, error -> 70 if (error != null) { 71 debugLog(error = error) { "Failed to query Bubbles as system user." } 72 } 73 continuation.resume(available ?: false) 74 } 75 } 76 } 77 78 /** Calls the [Bubbles.showOrHideAppBubble] API as [UserHandle.USER_SYSTEM]. */ 79 open suspend fun showOrHideAppBubble( 80 intent: Intent, 81 userHandle: UserHandle, 82 icon: Icon 83 ) { 84 withContext(bgDispatcher) { 85 serviceConnector 86 .post { it.showOrHideAppBubble(intent, userHandle, icon) } 87 .whenComplete { _, error -> 88 if (error != null) { 89 debugLog(error = error) { 90 "Failed to show notes app bubble for intent $intent, " + 91 "user $userHandle, and icon $icon." 92 } 93 } else { 94 debugLog { 95 "Call to show notes app bubble for intent $intent, " + 96 "user $userHandle, and icon $icon successful." 97 } 98 } 99 } 100 } 101 } 102 103 /** 104 * A helper service to call [Bubbles] APIs that should always be called from the system user 105 * instance of SysUI. 106 * 107 * <p>Note: This service always runs in the SysUI process running on the system user 108 * irrespective of which user started the service. This is required so that the correct instance 109 * of {@link Bubbles} is injected. This is set via attribute {@code android:singleUser=”true”} 110 * in AndroidManifest. 111 */ 112 class NoteTaskBubblesService 113 @Inject 114 constructor(private val mOptionalBubbles: Optional<Bubbles>) : Service() { 115 116 override fun onBind(intent: Intent): IBinder { 117 return object : INoteTaskBubblesService.Stub() { 118 override fun areBubblesAvailable() = mOptionalBubbles.isPresent 119 120 override fun showOrHideAppBubble( 121 intent: Intent, 122 userHandle: UserHandle, 123 icon: Icon 124 ) { 125 mOptionalBubbles.ifPresentOrElse( 126 { bubbles -> bubbles.showOrHideAppBubble(intent, userHandle, icon) }, 127 { 128 debugLog { 129 "Failed to show or hide bubble for intent $intent," + 130 "user $user, and icon $icon as bubble is empty." 131 } 132 } 133 ) 134 } 135 } 136 } 137 } 138 } 139