blob: 1f9d9158fd5cc9ad745347fdb25d9f9c645bcc62 [file] [log] [blame]
Uriel Sade298f6c02018-12-19 20:34:43 -08001/*
Uriel Saded81ddc92018-12-21 15:16:56 -08002 * Copyright (C) 2019 The Android Open Source Project
Uriel Sade298f6c02018-12-19 20:34:43 -08003 *
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 */
16package com.android.car.assist.client;
17
Uriel Saded81ddc92018-12-21 15:16:56 -080018import static android.app.Notification.Action.SEMANTIC_ACTION_MARK_AS_READ;
19import static android.app.Notification.Action.SEMANTIC_ACTION_REPLY;
Uriel Sade3703dbd2019-01-16 12:55:19 -080020import static android.service.voice.VoiceInteractionSession.SHOW_SOURCE_NOTIFICATION;
Uriel Saded81ddc92018-12-21 15:16:56 -080021
Ritwika Mitra2f0ffb22019-05-23 10:10:00 -070022import static com.android.car.assist.CarVoiceInteractionSession.EXCEPTION_NOTIFICATION_LISTENER_PERMISSIONS_MISSING;
23
Ritwika Mitra146ed852019-05-01 10:33:06 -070024import android.annotation.Nullable;
Ritwika Mitra3eb68932019-04-29 16:40:04 -070025import android.app.ActivityManager;
Uriel Sade298f6c02018-12-19 20:34:43 -080026import android.app.Notification;
Uriel Saded81ddc92018-12-21 15:16:56 -080027import android.app.RemoteInput;
Uriel Sade298f6c02018-12-19 20:34:43 -080028import android.content.Context;
29import android.os.Bundle;
30import android.provider.Settings;
31import android.service.notification.StatusBarNotification;
32import android.util.Log;
33
Ritwika Mitra2f0ffb22019-05-23 10:10:00 -070034import androidx.annotation.StringDef;
Uriel Sade298f6c02018-12-19 20:34:43 -080035import androidx.core.app.NotificationCompat;
36
37import com.android.car.assist.CarVoiceInteractionSession;
38import com.android.internal.app.AssistUtils;
Uriel Sade3703dbd2019-01-16 12:55:19 -080039import com.android.internal.app.IVoiceActionCheckCallback;
Uriel Sade298f6c02018-12-19 20:34:43 -080040
Priyank Singh128ff292019-04-29 20:07:36 -070041import java.util.ArrayList;
Uriel Sade298f6c02018-12-19 20:34:43 -080042import java.util.Arrays;
43import java.util.Collections;
44import java.util.HashSet;
45import java.util.List;
46import java.util.Objects;
47import java.util.Set;
48import java.util.stream.Collectors;
49import java.util.stream.IntStream;
50
51/**
52 * Util class providing helper methods to interact with the current active voice service,
53 * while ensuring that the active voice service has the required permissions.
54 */
55public class CarAssistUtils {
56 public static final String TAG = "CarAssistUtils";
Uriel Saded81ddc92018-12-21 15:16:56 -080057 private static final List<Integer> REQUIRED_SEMANTIC_ACTIONS = Collections.unmodifiableList(
Uriel Sade298f6c02018-12-19 20:34:43 -080058 Arrays.asList(
Uriel Saded81ddc92018-12-21 15:16:56 -080059 SEMANTIC_ACTION_MARK_AS_READ
Uriel Sade298f6c02018-12-19 20:34:43 -080060 )
61 );
62
Ritwika Mitraaaf0c172019-01-30 08:25:03 -080063 private static final List<Integer> SUPPORTED_SEMANTIC_ACTIONS = Collections.unmodifiableList(
64 Arrays.asList(
65 SEMANTIC_ACTION_MARK_AS_READ,
66 SEMANTIC_ACTION_REPLY
67 )
68 );
Uriel Saded81ddc92018-12-21 15:16:56 -080069
Uriel Sade298f6c02018-12-19 20:34:43 -080070 private final Context mContext;
71 private final AssistUtils mAssistUtils;
Uriel Saded81ddc92018-12-21 15:16:56 -080072 private final FallbackAssistant mFallbackAssistant;
73 private final String mErrorMessage;
Ritwika Mitra2f0ffb22019-05-23 10:10:00 -070074 private final boolean mIsFallbackAssistantEnabled;
Uriel Sade298f6c02018-12-19 20:34:43 -080075
Uriel Sade3703dbd2019-01-16 12:55:19 -080076 /** Interface used to receive callbacks from voice action requests. */
77 public interface ActionRequestCallback {
Ritwika Mitra2f0ffb22019-05-23 10:10:00 -070078 /**
79 * The action was successfully completed either by the active or fallback assistant.
80 **/
81 String RESULT_SUCCESS = "SUCCESS";
82
83 /**
84 * The action was not successfully completed, but the active assistant has been prompted to
85 * alert the user of this error and handle it. The caller of this callback is recommended
86 * to NOT alert the user of this error again.
87 */
88 String RESULT_FAILED_WITH_ERROR_HANDLED = "FAILED_WITH_ERROR_HANDLED";
89
90 /**
91 * The action has not been successfully completed, and the error has not been handled.
92 **/
93 String RESULT_FAILED = "FAILED";
94
95 /**
96 * The list of result states.
97 */
98 @StringDef({RESULT_FAILED, RESULT_FAILED_WITH_ERROR_HANDLED, RESULT_SUCCESS})
99 @interface ResultState {
100 }
101
102 /** Callback containing the result of completing the voice action request. */
103 void onResult(@ResultState String state);
Uriel Sade3703dbd2019-01-16 12:55:19 -0800104 }
105
Uriel Sade298f6c02018-12-19 20:34:43 -0800106 public CarAssistUtils(Context context) {
Uriel Sade298f6c02018-12-19 20:34:43 -0800107 mContext = context;
Uriel Saded81ddc92018-12-21 15:16:56 -0800108 mAssistUtils = new AssistUtils(context);
Ritwika Mitra146ed852019-05-01 10:33:06 -0700109 mFallbackAssistant = new FallbackAssistant(context);
Uriel Saded81ddc92018-12-21 15:16:56 -0800110 mErrorMessage = context.getString(R.string.assist_action_failed_toast);
Ritwika Mitra2f0ffb22019-05-23 10:10:00 -0700111 mIsFallbackAssistantEnabled =
112 context.getResources().getBoolean(R.bool.config_enableFallbackAssistant);
113 }
114
115 /**
116 * @return {@code true} if there is an active assistant.
117 */
118 public boolean hasActiveAssistant() {
119 return mAssistUtils.getActiveServiceComponentName() != null;
Uriel Sade298f6c02018-12-19 20:34:43 -0800120 }
121
122 /**
123 * Returns true if the current active assistant has notification listener permissions.
124 */
125 public boolean assistantIsNotificationListener() {
Ritwika Mitra2f0ffb22019-05-23 10:10:00 -0700126 if (!hasActiveAssistant()) {
127 if (Log.isLoggable(TAG, Log.DEBUG)) {
128 Log.d(TAG, "No active assistant was found.");
129 }
130 return false;
131 }
Uriel Sade298f6c02018-12-19 20:34:43 -0800132 final String activeComponent = mAssistUtils.getActiveServiceComponentName()
133 .flattenToString();
Ritwika Mitraac5f1512019-02-04 10:21:14 -0800134 int slashIndex = activeComponent.indexOf("/");
135 final String activePackage = activeComponent.substring(0, slashIndex);
136
Ritwika Mitra3eb68932019-04-29 16:40:04 -0700137 final String listeners = Settings.Secure.getStringForUser(mContext.getContentResolver(),
138 Settings.Secure.ENABLED_NOTIFICATION_LISTENERS, ActivityManager.getCurrentUser());
Uriel Sade298f6c02018-12-19 20:34:43 -0800139
Ritwika Mitraf89855b2019-05-21 11:56:20 -0700140 if (Log.isLoggable(TAG, Log.DEBUG)) {
141 Log.d(TAG, "Current user: " + ActivityManager.getCurrentUser()
142 + " has active voice service: " + activePackage + " and enabled notification "
143 + " listeners: " + listeners);
144 }
145
Ritwika Mitraac5f1512019-02-04 10:21:14 -0800146 if (listeners != null) {
147 for (String listener : Arrays.asList(listeners.split(":"))) {
148 if (listener.contains(activePackage)) {
149 return true;
150 }
151 }
152 }
Ritwika Mitra295d91a2019-04-26 09:55:11 -0700153 Log.w(TAG, "No notification listeners found for assistant: " + activeComponent);
Ritwika Mitraac5f1512019-02-04 10:21:14 -0800154 return false;
Uriel Sade298f6c02018-12-19 20:34:43 -0800155 }
156
157 /**
158 * Checks whether the notification is a car-compatible messaging notification.
159 *
160 * @param sbn The notification being checked.
161 * @return true if the notification is a car-compatible messaging notification.
162 */
163 public static boolean isCarCompatibleMessagingNotification(StatusBarNotification sbn) {
164 return hasMessagingStyle(sbn)
165 && hasRequiredAssistantCallbacks(sbn)
Ritwika Mitrafdc114d2019-07-15 10:41:48 -0700166 && ((getReplyAction(sbn.getNotification()) == null)
167 || replyCallbackHasRemoteInput(sbn))
Uriel Sade298f6c02018-12-19 20:34:43 -0800168 && assistantCallbacksShowNoUi(sbn);
169 }
170
171 /** Returns true if the semantic action provided can be supported. */
172 public static boolean isSupportedSemanticAction(int semanticAction) {
Uriel Saded81ddc92018-12-21 15:16:56 -0800173 return SUPPORTED_SEMANTIC_ACTIONS.contains(semanticAction);
Uriel Sade298f6c02018-12-19 20:34:43 -0800174 }
175
176 /**
177 * Returns true if the notification has a messaging style.
178 * <p/>
179 * This is the case if the notification in question was provided an instance of
180 * {@link Notification.MessagingStyle} (or an instance of
181 * {@link NotificationCompat.MessagingStyle} if {@link NotificationCompat} was used).
182 */
183 private static boolean hasMessagingStyle(StatusBarNotification sbn) {
184 return NotificationCompat.MessagingStyle
185 .extractMessagingStyleFromNotification(sbn.getNotification()) != null;
186 }
187
188 /**
189 * Returns true if the notification has the required Assistant callbacks to be considered
190 * a car-compatible messaging notification. The callbacks must be unambiguous, therefore false
191 * is returned if multiple callbacks exist for any semantic action that is supported.
192 */
193 private static boolean hasRequiredAssistantCallbacks(StatusBarNotification sbn) {
Priyank Singh128ff292019-04-29 20:07:36 -0700194 List<Integer> semanticActionList = getAllActions(sbn.getNotification())
195 .stream()
196 .map(NotificationCompat.Action::getSemanticAction)
Uriel Saded81ddc92018-12-21 15:16:56 -0800197 .filter(REQUIRED_SEMANTIC_ACTIONS::contains)
Uriel Sade298f6c02018-12-19 20:34:43 -0800198 .collect(Collectors.toList());
199 Set<Integer> semanticActionSet = new HashSet<>(semanticActionList);
Uriel Sade298f6c02018-12-19 20:34:43 -0800200 return semanticActionList.size() == semanticActionSet.size()
Uriel Saded81ddc92018-12-21 15:16:56 -0800201 && semanticActionSet.containsAll(REQUIRED_SEMANTIC_ACTIONS);
Uriel Sade298f6c02018-12-19 20:34:43 -0800202 }
203
Priyank Singh128ff292019-04-29 20:07:36 -0700204 /** Retrieves all visible and invisible {@link Action}s from the {@link #notification}. */
Ritwika Mitra146ed852019-05-01 10:33:06 -0700205 public static List<NotificationCompat.Action> getAllActions(Notification notification) {
Priyank Singh128ff292019-04-29 20:07:36 -0700206 List<NotificationCompat.Action> actions = new ArrayList<>();
207 actions.addAll(NotificationCompat.getInvisibleActions(notification));
208 for (int i = 0; i < NotificationCompat.getActionCount(notification); i++) {
209 actions.add(NotificationCompat.getAction(notification, i));
210 }
211 return actions;
212 }
213
Uriel Sade298f6c02018-12-19 20:34:43 -0800214 /**
Ritwika Mitra146ed852019-05-01 10:33:06 -0700215 * Retrieves the {@link NotificationCompat.Action} containing the
216 * {@link NotificationCompat.Action#SEMANTIC_ACTION_MARK_AS_READ} semantic action.
217 */
218 @Nullable
219 public static NotificationCompat.Action getMarkAsReadAction(Notification notification) {
220 for (NotificationCompat.Action action : getAllActions(notification)) {
221 if (action.getSemanticAction()
222 == NotificationCompat.Action.SEMANTIC_ACTION_MARK_AS_READ) {
223 return action;
224 }
225 }
226 return null;
227 }
228
229 /**
Ritwika Mitrafdc114d2019-07-15 10:41:48 -0700230 * Retrieves the {@link NotificationCompat.Action} containing the
231 * {@link NotificationCompat.Action#SEMANTIC_ACTION_REPLY} semantic action.
232 */
233 @Nullable
234 private static NotificationCompat.Action getReplyAction(Notification notification) {
235 for (NotificationCompat.Action action : getAllActions(notification)) {
236 if (action.getSemanticAction()
237 == NotificationCompat.Action.SEMANTIC_ACTION_REPLY) {
238 return action;
239 }
240 }
241 return null;
242 }
243
244 /**
Uriel Saded81ddc92018-12-21 15:16:56 -0800245 * Returns true if the reply callback has at least one {@link RemoteInput}.
Uriel Sade298f6c02018-12-19 20:34:43 -0800246 * <p/>
247 * Precondition: There exists only one reply callback.
248 */
249 private static boolean replyCallbackHasRemoteInput(StatusBarNotification sbn) {
250 return Arrays.stream(sbn.getNotification().actions)
Uriel Saded81ddc92018-12-21 15:16:56 -0800251 .filter(action -> action.getSemanticAction() == SEMANTIC_ACTION_REPLY)
Uriel Sade298f6c02018-12-19 20:34:43 -0800252 .map(Notification.Action::getRemoteInputs)
Uriel Saded81ddc92018-12-21 15:16:56 -0800253 .filter(Objects::nonNull)
254 .anyMatch(remoteInputs -> remoteInputs.length > 0);
Uriel Sade298f6c02018-12-19 20:34:43 -0800255 }
256
257 /** Returns true if all Assistant callbacks indicate that they show no UI, false otherwise. */
258 private static boolean assistantCallbacksShowNoUi(StatusBarNotification sbn) {
259 final Notification notification = sbn.getNotification();
260 return IntStream.range(0, notification.actions.length)
261 .mapToObj(i -> NotificationCompat.getAction(notification, i))
262 .filter(Objects::nonNull)
Uriel Saded81ddc92018-12-21 15:16:56 -0800263 .filter(action -> SUPPORTED_SEMANTIC_ACTIONS.contains(action.getSemanticAction()))
Uriel Sade298f6c02018-12-19 20:34:43 -0800264 .noneMatch(NotificationCompat.Action::getShowsUserInterface);
265 }
266
267 /**
Uriel Saded81ddc92018-12-21 15:16:56 -0800268 * Requests a given action from the current active Assistant.
269 *
Ritwika Mitra146ed852019-05-01 10:33:06 -0700270 * @param sbn the notification payload to deliver to assistant
271 * @param voiceAction must be a valid {@link CarVoiceInteractionSession} VOICE_ACTION
Ritwika Mitraeeb910c2019-05-23 12:32:06 -0700272 * @param callback the callback to issue on success/error
Uriel Sade298f6c02018-12-19 20:34:43 -0800273 */
Ritwika Mitra146ed852019-05-01 10:33:06 -0700274 public void requestAssistantVoiceAction(StatusBarNotification sbn, String voiceAction,
Uriel Sade3703dbd2019-01-16 12:55:19 -0800275 ActionRequestCallback callback) {
Uriel Saded81ddc92018-12-21 15:16:56 -0800276 if (!isCarCompatibleMessagingNotification(sbn)) {
277 Log.w(TAG, "Assistant action requested for non-compatible notification.");
Ritwika Mitra2f0ffb22019-05-23 10:10:00 -0700278 callback.onResult(ActionRequestCallback.RESULT_FAILED);
Uriel Sade3703dbd2019-01-16 12:55:19 -0800279 return;
280 }
281
Ritwika Mitra146ed852019-05-01 10:33:06 -0700282 switch (voiceAction) {
283 case CarVoiceInteractionSession.VOICE_ACTION_READ_NOTIFICATION:
Uriel Sade3703dbd2019-01-16 12:55:19 -0800284 readMessageNotification(sbn, callback);
285 return;
Ritwika Mitra146ed852019-05-01 10:33:06 -0700286 case CarVoiceInteractionSession.VOICE_ACTION_REPLY_NOTIFICATION:
Uriel Sade3703dbd2019-01-16 12:55:19 -0800287 replyMessageNotification(sbn, callback);
288 return;
Uriel Saded81ddc92018-12-21 15:16:56 -0800289 default:
Ritwika Mitra146ed852019-05-01 10:33:06 -0700290 Log.w(TAG, "Requested Assistant action for unsupported semantic action.");
Ritwika Mitra2f0ffb22019-05-23 10:10:00 -0700291 callback.onResult(ActionRequestCallback.RESULT_FAILED);
Uriel Sade3703dbd2019-01-16 12:55:19 -0800292 return;
Uriel Saded81ddc92018-12-21 15:16:56 -0800293 }
Uriel Sade298f6c02018-12-19 20:34:43 -0800294 }
295
296 /**
297 * Requests a read action for the notification from the current active Assistant.
Ritwika Mitraeeb910c2019-05-23 12:32:06 -0700298 * If the Assistant cannot handle the request, a fallback implementation will attempt to
Uriel Saded81ddc92018-12-21 15:16:56 -0800299 * handle it.
Uriel Sade298f6c02018-12-19 20:34:43 -0800300 *
Ritwika Mitraeeb910c2019-05-23 12:32:06 -0700301 * @param sbn the notification to deliver as the payload
Uriel Sade3703dbd2019-01-16 12:55:19 -0800302 * @param callback the callback to issue on success/error
Uriel Sade298f6c02018-12-19 20:34:43 -0800303 */
Uriel Sade3703dbd2019-01-16 12:55:19 -0800304 private void readMessageNotification(StatusBarNotification sbn,
305 ActionRequestCallback callback) {
306 Bundle args = BundleBuilder.buildAssistantReadBundle(sbn);
307 String action = CarVoiceInteractionSession.VOICE_ACTION_READ_NOTIFICATION;
308
Ritwika Mitraeeb910c2019-05-23 12:32:06 -0700309 requestAction(action, sbn, args, callback);
Uriel Sade298f6c02018-12-19 20:34:43 -0800310 }
311
312 /**
313 * Requests a reply action for the notification from the current active Assistant.
Ritwika Mitraeeb910c2019-05-23 12:32:06 -0700314 * If the Assistant cannot handle the request, a fallback implementation will attempt to
Uriel Saded81ddc92018-12-21 15:16:56 -0800315 * handle it.
Uriel Sade298f6c02018-12-19 20:34:43 -0800316 *
Ritwika Mitraeeb910c2019-05-23 12:32:06 -0700317 * @param sbn the notification to deliver as the payload
Uriel Sade3703dbd2019-01-16 12:55:19 -0800318 * @param callback the callback to issue on success/error
Uriel Sade298f6c02018-12-19 20:34:43 -0800319 */
Uriel Sade3703dbd2019-01-16 12:55:19 -0800320 private void replyMessageNotification(StatusBarNotification sbn,
321 ActionRequestCallback callback) {
322 Bundle args = BundleBuilder.buildAssistantReplyBundle(sbn);
323 String action = CarVoiceInteractionSession.VOICE_ACTION_REPLY_NOTIFICATION;
324
Ritwika Mitraeeb910c2019-05-23 12:32:06 -0700325 requestAction(action, sbn, args, callback);
Uriel Sade298f6c02018-12-19 20:34:43 -0800326 }
327
Ritwika Mitraeeb910c2019-05-23 12:32:06 -0700328 private void requestAction(String action, StatusBarNotification sbn, Bundle payloadArguments,
Uriel Sade3703dbd2019-01-16 12:55:19 -0800329 ActionRequestCallback callback) {
330
Ritwika Mitra2f0ffb22019-05-23 10:10:00 -0700331 if (!hasActiveAssistant()) {
332 if (mIsFallbackAssistantEnabled) {
333 handleFallback(sbn, action, callback);
334 } else {
335 // If there is no active assistant, and fallback assistant is not enabled, then
336 // there is nothing for us to do.
337 callback.onResult(ActionRequestCallback.RESULT_FAILED);
338 }
339 return;
340 }
341
Uriel Sade3703dbd2019-01-16 12:55:19 -0800342 if (!assistantIsNotificationListener()) {
Ritwika Mitra2f0ffb22019-05-23 10:10:00 -0700343 if (mIsFallbackAssistantEnabled) {
344 handleFallback(sbn, action, callback);
345 } else {
346 // If there is an active assistant, alert them to request permissions.
Ritwika Mitra22d50c92019-05-31 10:24:48 -0700347 Bundle handleExceptionBundle = BundleBuilder
348 .buildAssistantHandleExceptionBundle(
Ritwika Mitra82be9bd2019-10-07 07:59:15 -0700349 EXCEPTION_NOTIFICATION_LISTENER_PERMISSIONS_MISSING,
350 /* fallbackAssistantEnabled */ false);
Ritwika Mitra22d50c92019-05-31 10:24:48 -0700351 fireAssistantAction(CarVoiceInteractionSession.VOICE_ACTION_HANDLE_EXCEPTION,
352 handleExceptionBundle, callback);
Ritwika Mitra2f0ffb22019-05-23 10:10:00 -0700353 }
Uriel Sade3703dbd2019-01-16 12:55:19 -0800354 return;
355 }
356
Ritwika Mitra22d50c92019-05-31 10:24:48 -0700357 fireAssistantAction(action, payloadArguments, callback);
358 }
359
360 private void fireAssistantAction(String action, Bundle payloadArguments,
361 ActionRequestCallback callback) {
Uriel Sade3703dbd2019-01-16 12:55:19 -0800362 IVoiceActionCheckCallback actionCheckCallback = new IVoiceActionCheckCallback.Stub() {
363 @Override
364 public void onComplete(List<String> supportedActions) {
Ritwika Mitra2f0ffb22019-05-23 10:10:00 -0700365 String resultState = ActionRequestCallback.RESULT_FAILED;
Uriel Sade3703dbd2019-01-16 12:55:19 -0800366 if (supportedActions != null && supportedActions.contains(action)) {
367 if (Log.isLoggable(TAG, Log.DEBUG)) {
368 Log.d(TAG, "Launching active Assistant for action: " + action);
369 }
Ritwika Mitra2f0ffb22019-05-23 10:10:00 -0700370 if (mAssistUtils.showSessionForActiveService(payloadArguments,
371 SHOW_SOURCE_NOTIFICATION, null, null)) {
372 resultState = ActionRequestCallback.RESULT_SUCCESS;
373 }
Uriel Sade3703dbd2019-01-16 12:55:19 -0800374 } else {
375 Log.w(TAG, "Active Assistant does not support voice action: " + action);
Uriel Sade3703dbd2019-01-16 12:55:19 -0800376 }
Ritwika Mitra2f0ffb22019-05-23 10:10:00 -0700377 callback.onResult(resultState);
Uriel Sade3703dbd2019-01-16 12:55:19 -0800378 }
379 };
380
381 Set<String> actionSet = new HashSet<>(Collections.singletonList(action));
382 mAssistUtils.getActiveServiceSupportedActions(actionSet, actionCheckCallback);
383 }
384
Ritwika Mitraeeb910c2019-05-23 12:32:06 -0700385 private void handleFallback(StatusBarNotification sbn, String action,
386 ActionRequestCallback callback) {
387 FallbackAssistant.Listener listener = new FallbackAssistant.Listener() {
388 @Override
Ritwika Mitra2f0ffb22019-05-23 10:10:00 -0700389 public void onMessageRead(boolean hasError) {
Ritwika Mitra22d50c92019-05-31 10:24:48 -0700390 // Tracks if the FallbackAssistant successfully handled the action.
391 final String fallbackActionResult = hasError ? ActionRequestCallback.RESULT_FAILED
Ritwika Mitra2f0ffb22019-05-23 10:10:00 -0700392 : ActionRequestCallback.RESULT_SUCCESS;
Ritwika Mitra22d50c92019-05-31 10:24:48 -0700393 if (hasActiveAssistant()) {
Ritwika Mitra82be9bd2019-10-07 07:59:15 -0700394 // If there is an active assistant, alert them to request permissions.
395 Bundle handleExceptionBundle = BundleBuilder
396 .buildAssistantHandleExceptionBundle(
397 EXCEPTION_NOTIFICATION_LISTENER_PERMISSIONS_MISSING,
398 /* fallbackAssistantEnabled */ true);
Ritwika Mitra22d50c92019-05-31 10:24:48 -0700399 fireAssistantAction(CarVoiceInteractionSession.VOICE_ACTION_HANDLE_EXCEPTION,
Ritwika Mitra82be9bd2019-10-07 07:59:15 -0700400 handleExceptionBundle, new ActionRequestCallback() {
Ritwika Mitra22d50c92019-05-31 10:24:48 -0700401 @Override
402 public void onResult(String requestActionFromAssistantResult) {
403 if (fallbackActionResult.equals(
404 ActionRequestCallback.RESULT_FAILED)
405 && requestActionFromAssistantResult
406 == ActionRequestCallback.RESULT_SUCCESS) {
407 // Only change the callback.ResultState if fallback failed,
408 // and assistant session is shown.
409 callback.onResult(
410 ActionRequestCallback
411 .RESULT_FAILED_WITH_ERROR_HANDLED);
412 } else {
413 callback.onResult(fallbackActionResult);
414 }
415 }
416 });
417 } else {
418 callback.onResult(fallbackActionResult);
Ritwika Mitra2f0ffb22019-05-23 10:10:00 -0700419 }
Ritwika Mitraeeb910c2019-05-23 12:32:06 -0700420 }
421 };
422
Uriel Sade3703dbd2019-01-16 12:55:19 -0800423 switch (action) {
424 case CarVoiceInteractionSession.VOICE_ACTION_READ_NOTIFICATION:
Ritwika Mitraeeb910c2019-05-23 12:32:06 -0700425 mFallbackAssistant.handleReadAction(sbn, listener);
426 break;
Uriel Sade3703dbd2019-01-16 12:55:19 -0800427 case CarVoiceInteractionSession.VOICE_ACTION_REPLY_NOTIFICATION:
Ritwika Mitraeeb910c2019-05-23 12:32:06 -0700428 mFallbackAssistant.handleErrorMessage(mErrorMessage, listener);
429 break;
Uriel Sade3703dbd2019-01-16 12:55:19 -0800430 default:
Ritwika Mitraeeb910c2019-05-23 12:32:06 -0700431 Log.w(TAG, "Requested unsupported FallbackAssistant action.");
Ritwika Mitra2f0ffb22019-05-23 10:10:00 -0700432 callback.onResult(ActionRequestCallback.RESULT_FAILED);
Ritwika Mitraeeb910c2019-05-23 12:32:06 -0700433 return;
Uriel Sade3703dbd2019-01-16 12:55:19 -0800434 }
Uriel Sade298f6c02018-12-19 20:34:43 -0800435 }
Uriel Sade298f6c02018-12-19 20:34:43 -0800436}