1 /*
2  * Copyright (C) 2021 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 android.car.builtin.util;
18 
19 import static android.service.voice.VoiceInteractionSession.SHOW_SOURCE_PUSH_TO_TALK;
20 
21 import android.annotation.NonNull;
22 import android.annotation.SystemApi;
23 import android.app.ActivityManager;
24 import android.content.Context;
25 import android.os.Bundle;
26 import android.os.RemoteException;
27 import android.os.SystemClock;
28 
29 import com.android.internal.annotations.VisibleForTesting;
30 import com.android.internal.app.AssistUtils;
31 import com.android.internal.app.IVoiceInteractionSessionListener;
32 import com.android.internal.app.IVoiceInteractionSessionShowCallback;
33 
34 import java.util.Objects;
35 
36 /**
37  * Class to wrap {@link AssistUtils}.
38  * @hide
39  */
40 @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
41 public final class AssistUtilsHelper {
42 
43     private static final String TAG = AssistUtilsHelper.class.getSimpleName();
44 
45     /**
46      * Used as a boolean extra field on show the session for the currently active voice interaction
47      * service, {@code true} indicates that the service was launch from a key event,
48      * {@code false} otherwise.
49      */
50     @VisibleForTesting
51     static final String EXTRA_CAR_PUSH_TO_TALK =
52             "com.android.car.input.EXTRA_CAR_PUSH_TO_TALK";
53 
54     /**
55      * Used as a long extra field on show the session for the currently active voice interaction
56      * service, the value indicates the button press time measured in milliseconds since the last
57      * boot up.
58      */
59     @VisibleForTesting
60     static final String EXTRA_TRIGGER_TIMESTAMP_PUSH_TO_TALK_MS =
61             "com.android.car.input.EXTRA_TRIGGER_TIMESTAMP_PUSH_TO_TALK_MS";
62 
63     /**
64      * Determines if there is a voice interaction session running.
65      *
66      * @param context used to build the assist utils.
67      * @return {@code true} if a session is running, {@code false} otherwise.
68      */
isSessionRunning(@onNull Context context)69     public static boolean isSessionRunning(@NonNull Context context) {
70         AssistUtils assistUtils = getAssistUtils(context);
71 
72         return assistUtils.isSessionRunning();
73     }
74 
75     /**
76      * Hides the current voice interaction session running
77      *
78      * @param context used to build the assist utils.
79      */
hideCurrentSession(@onNull Context context)80     public static void hideCurrentSession(@NonNull Context context) {
81         AssistUtils assistUtils = getAssistUtils(context);
82 
83         assistUtils.hideCurrentSession();
84     }
85 
86     /**
87      * Registers a listener to monitor when the voice sessions are shown or hidden.
88      *
89      * @param context used to build the assist utils.
90      * @param sessionListener listener that will receive shown or hidden voice sessions callback.
91      */
92     // TODO(b/221604866) : Add unregister method
registerVoiceInteractionSessionListenerHelper(@onNull Context context, @NonNull VoiceInteractionSessionListenerHelper sessionListener)93     public static void registerVoiceInteractionSessionListenerHelper(@NonNull Context context,
94             @NonNull VoiceInteractionSessionListenerHelper sessionListener) {
95         Objects.requireNonNull(sessionListener, "Session listener must not be null.");
96 
97         AssistUtils assistUtils = getAssistUtils(context);
98 
99         assistUtils.registerVoiceInteractionSessionListener(
100                 new InternalVoiceInteractionSessionListener(sessionListener));
101     }
102 
103     /**
104      * Shows the {@link android.service.voice.VoiceInteractionSession.SHOW_SOURCE_PUSH_TO_TALK}
105      * session for active service, if the assistant component is active for the current user.
106      *
107      * @return whether the assistant component is active for the current user.
108      */
showPushToTalkSessionForActiveService(@onNull Context context, @NonNull VoiceInteractionSessionShowCallbackHelper callback)109     public static boolean showPushToTalkSessionForActiveService(@NonNull Context context,
110             @NonNull VoiceInteractionSessionShowCallbackHelper callback) {
111         Objects.requireNonNull(callback, "On shown callback must not be null.");
112 
113         AssistUtils assistUtils = getAssistUtils(context);
114         int currentUserId = ActivityManager.getCurrentUser();
115 
116 
117         if (assistUtils.getAssistComponentForUser(currentUserId) == null) {
118             Slogf.d(TAG, "showPushToTalkSessionForActiveService(): no component for user %d",
119                     currentUserId);
120             return false;
121         }
122 
123         Bundle args = new Bundle();
124         args.putBoolean(EXTRA_CAR_PUSH_TO_TALK, true);
125         args.putLong(EXTRA_TRIGGER_TIMESTAMP_PUSH_TO_TALK_MS, SystemClock.elapsedRealtime());
126 
127         IVoiceInteractionSessionShowCallback callbackWrapper =
128                 new InternalVoiceInteractionSessionShowCallback(callback);
129 
130         return assistUtils.showSessionForActiveService(args, SHOW_SOURCE_PUSH_TO_TALK,
131                 callbackWrapper, /* activityToken= */ null);
132     }
133 
getAssistUtils(@onNull Context context)134     private static AssistUtils getAssistUtils(@NonNull Context context) {
135         Objects.requireNonNull(context, "Context must not be null.");
136         return new AssistUtils(context);
137     }
138 
139     /**
140      * See {@link IVoiceInteractionSessionShowCallback}
141      */
142     public interface VoiceInteractionSessionShowCallbackHelper {
143         /**
144          * See {@link IVoiceInteractionSessionShowCallback#onFailed()}
145          */
onFailed()146         void onFailed();
147 
148         /**
149          * See {@link IVoiceInteractionSessionShowCallback#onShow()}
150          */
onShown()151         void onShown();
152     }
153 
154     /**
155      * See {@link IVoiceInteractionSessionListener}
156      */
157     public interface VoiceInteractionSessionListenerHelper {
158 
159         /**
160          * See {@link IVoiceInteractionSessionListener#onVoiceSessionShown()}
161          */
onVoiceSessionShown()162         void onVoiceSessionShown();
163 
164         /**
165          * See {@link IVoiceInteractionSessionListener#onVoiceSessionHidden()}
166          */
onVoiceSessionHidden()167         void onVoiceSessionHidden();
168     }
169 
170     private static final class InternalVoiceInteractionSessionShowCallback extends
171             IVoiceInteractionSessionShowCallback.Stub {
172         private final VoiceInteractionSessionShowCallbackHelper mCallbackHelper;
173 
InternalVoiceInteractionSessionShowCallback( VoiceInteractionSessionShowCallbackHelper callbackHelper)174         InternalVoiceInteractionSessionShowCallback(
175                 VoiceInteractionSessionShowCallbackHelper callbackHelper) {
176             mCallbackHelper = callbackHelper;
177         }
178 
179         @Override
onFailed()180         public void onFailed() {
181             mCallbackHelper.onFailed();
182         }
183 
184         @Override
onShown()185         public void onShown() {
186             mCallbackHelper.onShown();
187         }
188     }
189 
190     private static final class InternalVoiceInteractionSessionListener extends
191             IVoiceInteractionSessionListener.Stub {
192 
193         private final VoiceInteractionSessionListenerHelper mListenerHelper;
194 
InternalVoiceInteractionSessionListener( VoiceInteractionSessionListenerHelper listenerHelper)195         InternalVoiceInteractionSessionListener(
196                 VoiceInteractionSessionListenerHelper listenerHelper) {
197             mListenerHelper = listenerHelper;
198         }
199 
200         @Override
onVoiceSessionShown()201         public void onVoiceSessionShown() throws RemoteException {
202             mListenerHelper.onVoiceSessionShown();
203         }
204 
205         @Override
onVoiceSessionHidden()206         public void onVoiceSessionHidden() throws RemoteException {
207             mListenerHelper.onVoiceSessionHidden();
208         }
209 
210         @Override
onSetUiHints(Bundle args)211         public void onSetUiHints(Bundle args) throws RemoteException {
212             Slogf.d(TAG, "onSetUiHints() not used");
213         }
214 
215         @Override
onVoiceSessionWindowVisibilityChanged(boolean visible)216         public void onVoiceSessionWindowVisibilityChanged(boolean visible)
217                 throws RemoteException {
218             Slogf.d(TAG, "onVoiceSessionWindowVisibilityChanged() not used");
219         }
220     }
221 
AssistUtilsHelper()222     private AssistUtilsHelper() {
223         throw new UnsupportedOperationException("contains only static members");
224     }
225 }
226