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 
27 import com.android.car.assist.payloadhandlers.NotificationPayloadHandler;
28 
29 /**
30  * An active voice interaction session on the car, providing additional actions which assistant
31  * should act on. Override the {@link #onShow(String, Bundle, int)} to received the action specified
32  * by the voice session initiator.
33  */
34 public abstract class CarVoiceInteractionSession extends VoiceInteractionSession {
35     /** The key used for the action {@link String} in the payload {@link Bundle}. */
36     public static final String KEY_ACTION = "KEY_ACTION";
37 
38     /**
39      * The key used for the {@link CarVoiceInteractionSession#VOICE_ACTION_HANDLE_EXCEPTION} payload
40      * {@link Bundle}. Must map to a {@link ExceptionValue}.
41      */
42     public static final String KEY_EXCEPTION = "KEY_EXCEPTION";
43 
44     /**
45      * The key used for the {@link CarVoiceInteractionSession#VOICE_ACTION_HANDLE_EXCEPTION} payload
46      * {@link Bundle}. Must map to a boolean. If value is true, the Fallback Assistant that can
47      * handle the user's request has been disabled.
48      */
49     public static final String KEY_FALLBACK_ASSISTANT_ENABLED = "KEY_FALLBACK_ASSISTANT_ENABLED";
50 
51     /**
52      * The key used for the payload {@link Bundle}, if a {@link StatusBarNotification} is used as
53      * the payload.
54      */
55     public static final String KEY_NOTIFICATION = "KEY_NOTIFICATION";
56 
57     /** Indicates to assistant that no action was specified. */
58     public static final String VOICE_ACTION_NO_ACTION = "VOICE_ACTION_NO_ACTION";
59 
60     /** Indicates to assistant that a read action is being requested for a given payload. */
61     public static final String VOICE_ACTION_READ_NOTIFICATION = "VOICE_ACTION_READ_NOTIFICATION";
62 
63     /** Indicates to assistant that a reply action is being requested for a given payload. */
64     public static final String VOICE_ACTION_REPLY_NOTIFICATION = "VOICE_ACTION_REPLY_NOTIFICATION";
65 
66     /**
67      * Indicates to assistant that it should resolve the exception in the given payload (found in
68      * {@link CarVoiceInteractionSession#KEY_EXCEPTION}'s value).
69      */
70     public static final String VOICE_ACTION_HANDLE_EXCEPTION = "VOICE_ACTION_HANDLE_EXCEPTION";
71 
72     /**
73      * The list of exceptions the active voice service must handle.
74      */
75     @StringDef({EXCEPTION_NOTIFICATION_LISTENER_PERMISSIONS_MISSING})
76     public @interface ExceptionValue {}
77 
78     /**
79      * Indicates to assistant that it is missing the Notification Listener permission, and should
80      * request this permission from the user.
81      **/
82     public static final String EXCEPTION_NOTIFICATION_LISTENER_PERMISSIONS_MISSING =
83             "EXCEPTION_NOTIFICATION_LISTENER_PERMISSIONS_MISSING";
84 
85 
86     private final NotificationPayloadHandler mNotificationPayloadHandler;
87 
CarVoiceInteractionSession(Context context)88     public CarVoiceInteractionSession(Context context) {
89         super(context);
90         mNotificationPayloadHandler = new NotificationPayloadHandler(getContext());
91     }
92 
CarVoiceInteractionSession(Context context, Handler handler)93     public CarVoiceInteractionSession(Context context, Handler handler) {
94         super(context, handler);
95         mNotificationPayloadHandler = new NotificationPayloadHandler(getContext());
96     }
97 
98     /**
99      * Returns the notification payload handler, which can be used to handle actions related to
100      * notification payloads.
101      */
getNotificationPayloadHandler()102     public NotificationPayloadHandler getNotificationPayloadHandler() {
103         return mNotificationPayloadHandler;
104     }
105 
106     @Override
onShow(Bundle args, int showFlags)107     public final void onShow(Bundle args, int showFlags) {
108         super.onShow(args, showFlags);
109         if (args != null && isCarNotificationSource(showFlags)) {
110             String action = getRequestedVoiceAction(args);
111             if (!VOICE_ACTION_NO_ACTION.equals(action)) {
112                 onShow(action, args, showFlags);
113                 return;
114             }
115         }
116         onShow(VOICE_ACTION_NO_ACTION, args, showFlags);
117     }
118 
119     /**
120      * Called when the session UI is going to be shown.  This is called after
121      * {@link #onCreateContentView} (if the session's content UI needed to be created) and
122      * immediately prior to the window being shown.  This may be called while the window
123      * is already shown, if a show request has come in while it is shown, to allow you to
124      * update the UI to match the new show arguments.
125      *
126      * @param action The action that is being requested for this session
127      *               (e.g. {@link CarVoiceInteractionSession#VOICE_ACTION_READ_NOTIFICATION},
128      *               {@link CarVoiceInteractionSession#VOICE_ACTION_REPLY_NOTIFICATION}).
129      * @param args The arguments that were supplied to
130      * {@link VoiceInteractionService#showSession VoiceInteractionService.showSession}.
131      * @param flags The show flags originally provided to
132      * {@link VoiceInteractionService#showSession VoiceInteractionService.showSession}.
133      */
onShow(String action, Bundle args, int flags)134     protected abstract void onShow(String action, Bundle args, int flags);
135 
136     /**
137      * Returns true if the request was initiated for a car notification.
138      */
isCarNotificationSource(int flags)139     private static boolean isCarNotificationSource(int flags) {
140         return (flags & SHOW_SOURCE_NOTIFICATION) != 0;
141     }
142 
143     /**
144      * Returns the action {@link String} provided in the args {@Bundle},
145      * or {@link CarVoiceInteractionSession#VOICE_ACTION_NO_ACTION} if no such string was provided.
146      */
getRequestedVoiceAction(Bundle args)147     protected static String getRequestedVoiceAction(Bundle args) {
148         return args.getString(KEY_ACTION, VOICE_ACTION_NO_ACTION);
149     }
150 }
151