1 /*
2  * Copyright (C) 2015 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.internal.app;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.compat.annotation.UnsupportedAppUsage;
22 import android.content.ComponentName;
23 import android.content.Context;
24 import android.content.pm.ApplicationInfo;
25 import android.content.pm.PackageManager;
26 import android.os.Bundle;
27 import android.os.IBinder;
28 import android.os.RemoteException;
29 import android.os.ServiceManager;
30 import android.provider.Settings;
31 import android.util.Log;
32 
33 import java.util.ArrayList;
34 import java.util.Set;
35 
36 /**
37  * Utility method for dealing with the assistant aspects of
38  * {@link com.android.internal.app.IVoiceInteractionManagerService IVoiceInteractionManagerService}.
39  */
40 public class AssistUtils {
41 
42     private static final String TAG = "AssistUtils";
43 
44     /** bundle key: how was the assistant invoked? */
45     public static final String INVOCATION_TYPE_KEY = "invocation_type";
46     /** value for INVOCATION_TYPE_KEY: no data */
47     public static final int INVOCATION_TYPE_UNKNOWN = 0;
48     /** value for INVOCATION_TYPE_KEY: on-screen swipe gesture */
49     public static final int INVOCATION_TYPE_GESTURE = 1;
50     /** value for INVOCATION_TYPE_KEY: device-specific physical gesture */
51     public static final int INVOCATION_TYPE_PHYSICAL_GESTURE = 2;
52     /** value for INVOCATION_TYPE_KEY: voice hotword */
53     public static final int INVOCATION_TYPE_VOICE = 3;
54     /** value for INVOCATION_TYPE_KEY: search bar affordance */
55     public static final int INVOCATION_TYPE_QUICK_SEARCH_BAR = 4;
56     /** value for INVOCATION_TYPE_KEY: long press on home navigation button */
57     public static final int INVOCATION_TYPE_HOME_BUTTON_LONG_PRESS = 5;
58     /** value for INVOCATION_TYPE_KEY: long press on physical power button */
59     public static final int INVOCATION_TYPE_POWER_BUTTON_LONG_PRESS = 6;
60     /** value for INVOCATION_TYPE_KEY: press on physcial assistant button */
61     public static final int INVOCATION_TYPE_ASSIST_BUTTON = 7;
62     /** value for INVOCATION_TYPE_KEY: long press on nav handle */
63     public static final int INVOCATION_TYPE_NAV_HANDLE_LONG_PRESS = 8;
64 
65     private final Context mContext;
66     private final IVoiceInteractionManagerService mVoiceInteractionManagerService;
67 
68     @UnsupportedAppUsage
AssistUtils(Context context)69     public AssistUtils(Context context) {
70         mContext = context;
71         mVoiceInteractionManagerService = IVoiceInteractionManagerService.Stub.asInterface(
72                 ServiceManager.getService(Context.VOICE_INTERACTION_MANAGER_SERVICE));
73     }
74 
75     /**
76      * Shows the session for the currently active service. Used to start a new session from system
77      * affordances.
78      *
79      * @param args the bundle to pass as arguments to the voice interaction session
80      * @param sourceFlags flags indicating the source of this show
81      * @param showCallback optional callback to be notified when the session was shown
82      * @param activityToken optional token of activity that needs to be on top
83      *
84      * @deprecated Use {@link #showSessionForActiveService(Bundle, int, String,
85      *             IVoiceInteractionSessionShowCallback, IBinder)} instead
86      */
87     @Deprecated
showSessionForActiveService(@ullable Bundle args, int sourceFlags, @Nullable IVoiceInteractionSessionShowCallback showCallback, @Nullable IBinder activityToken)88     public boolean showSessionForActiveService(@Nullable Bundle args, int sourceFlags,
89             @Nullable IVoiceInteractionSessionShowCallback showCallback,
90             @Nullable IBinder activityToken) {
91         return showSessionForActiveServiceInternal(args, sourceFlags, /* attributionTag */ null,
92                 showCallback, activityToken);
93     }
94 
95     /**
96      * Shows the session for the currently active service. Used to start a new session from system
97      * affordances.
98      *
99      * @param args the bundle to pass as arguments to the voice interaction session
100      * @param sourceFlags flags indicating the source of this show
101      * @param attributionTag the attribution tag of the calling context or {@code null} for default
102      *                       attribution
103      * @param showCallback optional callback to be notified when the session was shown
104      * @param activityToken optional token of activity that needs to be on top
105      */
showSessionForActiveService(@ullable Bundle args, int sourceFlags, @Nullable String attributionTag, @Nullable IVoiceInteractionSessionShowCallback showCallback, @Nullable IBinder activityToken)106     public boolean showSessionForActiveService(@Nullable Bundle args, int sourceFlags,
107             @Nullable String attributionTag,
108             @Nullable IVoiceInteractionSessionShowCallback showCallback,
109             @Nullable IBinder activityToken) {
110         return showSessionForActiveServiceInternal(args, sourceFlags, attributionTag, showCallback,
111                 activityToken);
112     }
113 
showSessionForActiveServiceInternal(@ullable Bundle args, int sourceFlags, @Nullable String attributionTag, @Nullable IVoiceInteractionSessionShowCallback showCallback, @Nullable IBinder activityToken)114     private boolean showSessionForActiveServiceInternal(@Nullable Bundle args, int sourceFlags,
115             @Nullable String attributionTag,
116             @Nullable IVoiceInteractionSessionShowCallback showCallback,
117             @Nullable IBinder activityToken) {
118         try {
119             if (mVoiceInteractionManagerService != null) {
120                 return mVoiceInteractionManagerService.showSessionForActiveService(args,
121                         sourceFlags, attributionTag, showCallback, activityToken);
122             }
123         } catch (RemoteException e) {
124             Log.w(TAG, "Failed to call showSessionForActiveService", e);
125         }
126         return false;
127     }
128 
129     /**
130      * Checks the availability of a set of voice actions for the current active voice service.
131      *
132      * @param voiceActions A set of supported voice actions to be checked.
133      * @param callback     The callback which will deliver a set of supported voice actions. If
134      *                     no voice actions are supported for the given voice action set, then null
135      *                     or empty set is provided.
136      */
getActiveServiceSupportedActions(@onNull Set<String> voiceActions, @NonNull IVoiceActionCheckCallback callback)137     public void getActiveServiceSupportedActions(@NonNull Set<String> voiceActions,
138             @NonNull IVoiceActionCheckCallback callback) {
139         try {
140             if (mVoiceInteractionManagerService != null) {
141                 mVoiceInteractionManagerService
142                         .getActiveServiceSupportedActions(new ArrayList<>(voiceActions), callback);
143             }
144         } catch (RemoteException e) {
145             Log.w(TAG, "Failed to call activeServiceSupportedActions", e);
146             try {
147                 callback.onComplete(null);
148             } catch (RemoteException re) {
149             }
150         }
151     }
152 
launchVoiceAssistFromKeyguard()153     public void launchVoiceAssistFromKeyguard() {
154         try {
155             if (mVoiceInteractionManagerService != null) {
156                 mVoiceInteractionManagerService.launchVoiceAssistFromKeyguard();
157             }
158         } catch (RemoteException e) {
159             Log.w(TAG, "Failed to call launchVoiceAssistFromKeyguard", e);
160         }
161     }
162 
activeServiceSupportsAssistGesture()163     public boolean activeServiceSupportsAssistGesture() {
164         try {
165             return mVoiceInteractionManagerService != null
166                     && mVoiceInteractionManagerService.activeServiceSupportsAssist();
167         } catch (RemoteException e) {
168             Log.w(TAG, "Failed to call activeServiceSupportsAssistGesture", e);
169             return false;
170         }
171     }
172 
activeServiceSupportsLaunchFromKeyguard()173     public boolean activeServiceSupportsLaunchFromKeyguard() {
174         try {
175             return mVoiceInteractionManagerService != null
176                     && mVoiceInteractionManagerService.activeServiceSupportsLaunchFromKeyguard();
177         } catch (RemoteException e) {
178             Log.w(TAG, "Failed to call activeServiceSupportsLaunchFromKeyguard", e);
179             return false;
180         }
181     }
182 
getActiveServiceComponentName()183     public ComponentName getActiveServiceComponentName() {
184         try {
185             if (mVoiceInteractionManagerService != null) {
186                 return mVoiceInteractionManagerService.getActiveServiceComponentName();
187             } else {
188                 return null;
189             }
190         } catch (RemoteException e) {
191             Log.w(TAG, "Failed to call getActiveServiceComponentName", e);
192             return null;
193         }
194     }
195 
isSessionRunning()196     public boolean isSessionRunning() {
197         try {
198             return mVoiceInteractionManagerService != null
199                     && mVoiceInteractionManagerService.isSessionRunning();
200         } catch (RemoteException e) {
201             Log.w(TAG, "Failed to call isSessionRunning", e);
202             return false;
203         }
204     }
205 
hideCurrentSession()206     public void hideCurrentSession() {
207         try {
208             if (mVoiceInteractionManagerService != null) {
209                 mVoiceInteractionManagerService.hideCurrentSession();
210             }
211         } catch (RemoteException e) {
212             Log.w(TAG, "Failed to call hideCurrentSession", e);
213         }
214     }
215 
onLockscreenShown()216     public void onLockscreenShown() {
217         try {
218             if (mVoiceInteractionManagerService != null) {
219                 mVoiceInteractionManagerService.onLockscreenShown();
220             }
221         } catch (RemoteException e) {
222             Log.w(TAG, "Failed to call onLockscreenShown", e);
223         }
224     }
225 
registerVoiceInteractionSessionListener(IVoiceInteractionSessionListener listener)226     public void registerVoiceInteractionSessionListener(IVoiceInteractionSessionListener listener) {
227         try {
228             if (mVoiceInteractionManagerService != null) {
229                 mVoiceInteractionManagerService.registerVoiceInteractionSessionListener(listener);
230             }
231         } catch (RemoteException e) {
232             Log.w(TAG, "Failed to register voice interaction listener", e);
233         }
234     }
235 
236     /**
237      * Allows subscription to {@link android.service.voice.VisualQueryDetectionService} service
238      * status.
239      *
240      * @param listener to receive visual service start/stop events.
241      */
subscribeVisualQueryRecognitionStatus(IVisualQueryRecognitionStatusListener listener)242     public void subscribeVisualQueryRecognitionStatus(IVisualQueryRecognitionStatusListener
243             listener) {
244         try {
245             if (mVoiceInteractionManagerService != null) {
246                 mVoiceInteractionManagerService.subscribeVisualQueryRecognitionStatus(listener);
247             }
248         } catch (RemoteException e) {
249             Log.w(TAG, "Failed to register visual query detection start listener", e);
250         }
251     }
252 
253     /**
254      * Enables visual detection service.
255      *
256      * @param listener to receive visual attention gained/lost events.
257      */
enableVisualQueryDetection( IVisualQueryDetectionAttentionListener listener)258     public void enableVisualQueryDetection(
259             IVisualQueryDetectionAttentionListener listener) {
260         try {
261             if (mVoiceInteractionManagerService != null) {
262                 mVoiceInteractionManagerService.enableVisualQueryDetection(listener);
263             }
264         } catch (RemoteException e) {
265             Log.w(TAG, "Failed to register visual query detection attention listener", e);
266         }
267     }
268 
269     /**
270      * Disables visual query detection.
271      */
disableVisualQueryDetection()272     public void disableVisualQueryDetection() {
273         try {
274             if (mVoiceInteractionManagerService != null) {
275                 mVoiceInteractionManagerService.disableVisualQueryDetection();
276             }
277         } catch (RemoteException e) {
278             Log.w(TAG, "Failed to register visual query detection attention listener", e);
279         }
280     }
281 
282     @UnsupportedAppUsage
getAssistComponentForUser(int userId)283     public ComponentName getAssistComponentForUser(int userId) {
284         final String setting = Settings.Secure.getStringForUser(mContext.getContentResolver(),
285                 Settings.Secure.ASSISTANT, userId);
286         if (setting != null) {
287             return ComponentName.unflattenFromString(setting);
288         } else {
289             return null;
290         }
291     }
292 
isPreinstalledAssistant(Context context, ComponentName assistant)293     public static boolean isPreinstalledAssistant(Context context, ComponentName assistant) {
294         if (assistant == null) {
295             return false;
296         }
297         ApplicationInfo applicationInfo;
298         try {
299             applicationInfo = context.getPackageManager().getApplicationInfo(
300                     assistant.getPackageName(), 0);
301         } catch (PackageManager.NameNotFoundException e) {
302             return false;
303         }
304         return applicationInfo.isSystemApp() || applicationInfo.isUpdatedSystemApp();
305     }
306 
isDisclosureEnabled(Context context)307     public static boolean isDisclosureEnabled(Context context) {
308         return Settings.Secure.getInt(context.getContentResolver(),
309                 Settings.Secure.ASSIST_DISCLOSURE_ENABLED, 0) != 0;
310     }
311 
312     /**
313      * @return if the disclosure animation should trigger for the given assistant.
314      *
315      * Third-party assistants will always need to disclose, while the user can configure this for
316      * pre-installed assistants.
317      */
shouldDisclose(Context context, ComponentName assistant)318     public static boolean shouldDisclose(Context context, ComponentName assistant) {
319         if (!allowDisablingAssistDisclosure(context)) {
320             return true;
321         }
322 
323         return isDisclosureEnabled(context) || !isPreinstalledAssistant(context, assistant);
324     }
325 
allowDisablingAssistDisclosure(Context context)326     public static boolean allowDisablingAssistDisclosure(Context context) {
327         return context.getResources().getBoolean(
328                 com.android.internal.R.bool.config_allowDisablingAssistDisclosure);
329     }
330 }
331