• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2019 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 package com.android.car.assist;
17 
18 import android.content.Context;
19 import android.os.Bundle;
20 import android.os.Handler;
21 import android.service.notification.StatusBarNotification;
22 import android.service.voice.VoiceInteractionService;
23 import android.service.voice.VoiceInteractionSession;
24 
25 import androidx.annotation.StringDef;
26 import androidx.core.app.NotificationManagerCompat;
27 
28 import com.android.car.assist.payloadhandlers.NotificationPayloadHandler;
29 import com.android.car.messenger.common.Conversation;
30 
31 /**
32  * An active voice interaction session on the car, providing additional actions which assistant
33  * should act on. Override the {@link #onShow(String, Bundle, int)} to received the action specified
34  * by the voice session initiator.
35  */
36 public abstract class CarVoiceInteractionSession extends VoiceInteractionSession {
37     /** The key used for the action {@link String} in the payload {@link Bundle}. */
38     public static final String KEY_ACTION = "KEY_ACTION";
39 
40     /**
41      * The key used for the {@link CarVoiceInteractionSession#VOICE_ACTION_HANDLE_EXCEPTION} payload
42      * {@link Bundle}. Must map to a {@link ExceptionValue}.
43      */
44     public static final String KEY_EXCEPTION = "KEY_EXCEPTION";
45 
46     /**
47      * The key used for the {@link CarVoiceInteractionSession#VOICE_ACTION_HANDLE_EXCEPTION} payload
48      * {@link Bundle}. Must map to a boolean. If value is true, the Fallback Assistant that can
49      * handle the user's request has been disabled.
50      */
51     public static final String KEY_FALLBACK_ASSISTANT_ENABLED = "KEY_FALLBACK_ASSISTANT_ENABLED";
52 
53     /**
54      * The key used for a substitute package name the Digital Assistant should read out in lieu of
55      * package name associated with the {@link StatusBarNotification}.
56      *
57      * <p>Only system packages which lump together a bunch of unrelated stuff may substitute a
58      * different name to make the purpose of the notification more clear. The correct package label
59      * should always be accessible via SystemUI.
60      */
61     public static final String EXTRA_SUBSTITUTE_APP_NAME = "android.substName";
62 
63     /**
64      * The key used for the payload {@link Bundle}, if a {@link StatusBarNotification} is used as
65      * the payload.
66      */
67     public static final String KEY_NOTIFICATION = "KEY_NOTIFICATION";
68 
69     /**
70      * The key used for the payload {@link Bundle}, if a {@link Conversation} is used as the
71      * payload.
72      */
73     public static final String KEY_CONVERSATION = "KEY_CONVERSATION";
74 
75     /** Indicates to assistant that no action was specified. */
76     public static final String VOICE_ACTION_NO_ACTION = "VOICE_ACTION_NO_ACTION";
77 
78     /**
79      * Indicates to assistant that a read action is being requested for a given payload. A {@link
80      * StatusBarNotification} object will be provided in the payload
81      */
82     public static final String VOICE_ACTION_READ_NOTIFICATION = "VOICE_ACTION_READ_NOTIFICATION";
83 
84     /**
85      * Indicates to assistant that a reply action is being requested for a given payload. A {@link
86      * StatusBarNotification} object will be provided in the payload
87      */
88     public static final String VOICE_ACTION_REPLY_NOTIFICATION = "VOICE_ACTION_REPLY_NOTIFICATION";
89 
90     /**
91      * Indicates to assistant that a read conversation action is being requested. A {@link
92      * Conversation} object will be provided in the payload.
93      */
94     public static final String VOICE_ACTION_READ_CONVERSATION = "VOICE_ACTION_READ_CONVERSATION";
95 
96     /**
97      * Indicates to assistant that a reply conversation action is being requested. A {@link
98      * Conversation} object will be provided in the payload.
99      */
100     public static final String VOICE_ACTION_REPLY_CONVERSATION = "VOICE_ACTION_REPLY_CONVERSATION";
101 
102     /**
103      * Indicates to digital assistant that it should capture a SMS message from the user,
104      * potentially finding which contact to send the message to and which device to send the message
105      * from (only if the application does not send the digital assistant this information in the
106      * bundle). Once the digital assistant has gathered the information from the user, it should
107      * send back the PendingIntent (provided in the bundle) with the information so the application
108      * can actually send the SMS.
109      */
110     public static final String VOICE_ACTION_SEND_SMS = "VOICE_ACTION_SEND_SMS";
111 
112     /* Recipient's phone number. If this and the recipient name are not provided,
113      * by the application, digital assistant must do contact disambiguation
114      * and add the phone number to the pending intent
115      */
116     public static final String KEY_PHONE_NUMBER = "KEY_PHONE_NUMBER";
117 
118     /**
119      * Recipient's name. If this and the recipient phone number are not provided by the application,
120      * digital assistant must do contact disambiguation but is not required to add the name to the
121      * PendingIntent.
122      */
123     public static final String KEY_RECIPIENT_NAME = "KEY RECIPIENT NAME";
124 
125     /* Recipient's UID in the Contact Provider database. Optionally provided by the application.
126      * Not required to be sent back by the digital assistant.
127      */
128     public static final String KEY_RECIPIENT_UID = "KEY_RECIPIENT_UID";
129 
130     /* Friendly name of the device in which to send the message from. If not
131      * provided by the application, digital assistant must do device disambiguation
132      * but is not required to add it to the Pending Intent.
133      */
134     public static final String KEY_DEVICE_NAME = "KEY DEVICE_NAME";
135 
136     /* Bluetooth device address of the device of which to send the message from.
137      * If not provided by the application, assistant must do device disambiguation
138      * and add this to the Pendingintent.
139      */
140     public static final String KEY_DEVICE_ADDRESS = "KEY_DEVICE_ADDRESS";
141 
142     /* KEY_SEND_PENDING_INTENT is not null and always be provided by the application.
143      * The application must preload the pending intent with any KEYs, it provides the assistant
144      * that is also needed to send the message.
145      * (i.e. if the application passes in the KEY_PHONE_NUMBER in the Bundle,
146      * the assistant can assume the application has already put this in the
147      * PendingIntent and may not re-add it to the PendingIntent).
148      */
149     public static final String KEY_SEND_PENDING_INTENT = "KEY_SEND_PENDING INTENT";
150 
151     /**
152      * Indicates to assistant that it should resolve the exception in the given payload (found in
153      * {@link CarVoiceInteractionSession#KEY_EXCEPTION}'s value).
154      */
155     public static final String VOICE_ACTION_HANDLE_EXCEPTION = "VOICE_ACTION_HANDLE_EXCEPTION";
156 
157     /** The list of exceptions the active voice service must handle. */
158     @StringDef({EXCEPTION_NOTIFICATION_LISTENER_PERMISSIONS_MISSING})
159     public @interface ExceptionValue {}
160 
161     /**
162      * Indicates to assistant that it is missing the Notification Listener permission, and should
163      * request this permission from the user.
164      */
165     public static final String EXCEPTION_NOTIFICATION_LISTENER_PERMISSIONS_MISSING =
166             "EXCEPTION_NOTIFICATION_LISTENER_PERMISSIONS_MISSING";
167 
168     private final NotificationPayloadHandler mNotificationPayloadHandler;
169 
CarVoiceInteractionSession(Context context)170     public CarVoiceInteractionSession(Context context) {
171         super(context);
172         mNotificationPayloadHandler = new NotificationPayloadHandler(getContext());
173     }
174 
CarVoiceInteractionSession(Context context, Handler handler)175     public CarVoiceInteractionSession(Context context, Handler handler) {
176         super(context, handler);
177         mNotificationPayloadHandler = new NotificationPayloadHandler(getContext());
178     }
179 
180     /**
181      * Returns the notification payload handler, which can be used to handle actions related to
182      * notification payloads.
183      */
getNotificationPayloadHandler()184     public NotificationPayloadHandler getNotificationPayloadHandler() {
185         return mNotificationPayloadHandler;
186     }
187 
188     @Override
onShow(Bundle args, int showFlags)189     public final void onShow(Bundle args, int showFlags) {
190         super.onShow(args, showFlags);
191         addNotificationAccessExceptionIfNeeded(args);
192         if (args != null) {
193             String action = getRequestedVoiceAction(args);
194             if (!VOICE_ACTION_NO_ACTION.equals(action)) {
195                 onShow(action, args, showFlags);
196                 return;
197             }
198         }
199         onShow(VOICE_ACTION_NO_ACTION, args, showFlags);
200     }
201 
202     /**
203      * Called when the session UI is going to be shown. This is called after {@link
204      * #onCreateContentView} (if the session's content UI needed to be created) and immediately
205      * prior to the window being shown. This may be called while the window is already shown, if a
206      * show request has come in while it is shown, to allow you to update the UI to match the new
207      * show arguments.
208      *
209      * @param action The action that is being requested for this session (e.g. {@link
210      *     CarVoiceInteractionSession#VOICE_ACTION_READ_NOTIFICATION}, {@link
211      *     CarVoiceInteractionSession#VOICE_ACTION_REPLY_NOTIFICATION}).
212      * @param args The arguments that were supplied to {@link VoiceInteractionService#showSession
213      *     VoiceInteractionService.showSession}.
214      * @param flags The show flags originally provided to {@link VoiceInteractionService#showSession
215      *     VoiceInteractionService.showSession}.
216      */
onShow(String action, Bundle args, int flags)217     protected abstract void onShow(String action, Bundle args, int flags);
218 
219     /**
220      * Transforms bundle to {@link KEY_EXCEPTION} with
221      * value {@link EXCEPTION_NOTIFICATION_LISTENER_PERMISSIONS_MISSING} if
222      * Notification Listener permissions are missing.
223      */
addNotificationAccessExceptionIfNeeded(Bundle args)224     private void addNotificationAccessExceptionIfNeeded(Bundle args) {
225       if (needsNotificationAccess(args)) {
226               args.putString(KEY_ACTION, VOICE_ACTION_HANDLE_EXCEPTION);
227               args.putString(KEY_EXCEPTION, EXCEPTION_NOTIFICATION_LISTENER_PERMISSIONS_MISSING);
228               args.putBoolean(KEY_FALLBACK_ASSISTANT_ENABLED, false);
229       }
230     }
231 
needsNotificationAccess(Bundle args)232     private boolean needsNotificationAccess(Bundle args) {
233       return isNotificationAction(args) && !hasNotificationAccess(getContext());
234     }
235 
236     /**
237      * Returns {@code true} if the given {@code args} is a notification action, {@code false}
238      * otherwise
239      */
isNotificationAction(Bundle args)240     private static boolean isNotificationAction(Bundle args) {
241         if (args == null) {
242           return false;
243         }
244         String action = args.getString(KEY_ACTION);
245         return VOICE_ACTION_REPLY_NOTIFICATION.equals(action)
246             || VOICE_ACTION_READ_NOTIFICATION.equals(action);
247     }
248 
hasNotificationAccess(Context context)249     private boolean hasNotificationAccess(Context context) {
250         return NotificationManagerCompat
251             .getEnabledListenerPackages(context).contains(context.getPackageName());
252     }
253 
254     /**
255      * Returns the action {@link String} provided in the args {@link Bundle}, or {@link
256      * CarVoiceInteractionSession#VOICE_ACTION_NO_ACTION} if no such string was provided.
257      */
getRequestedVoiceAction(Bundle args)258     protected static String getRequestedVoiceAction(Bundle args) {
259         return args.getString(KEY_ACTION, VOICE_ACTION_NO_ACTION);
260     }
261 }