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