blob: 1ef55ccf132013837f38ca9cdfc342f9ace388b1 [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;
20
Ritwika Mitra60ab82a2019-05-23 10:10:00 -070021import static com.android.car.assist.CarVoiceInteractionSession.EXCEPTION_NOTIFICATION_LISTENER_PERMISSIONS_MISSING;
22
Ritwika Mitraadb2bfe2019-05-01 10:33:06 -070023import android.annotation.Nullable;
Ritwika Mitra95cf93c2019-04-29 16:40:04 -070024import android.app.ActivityManager;
Uriel Sade298f6c02018-12-19 20:34:43 -080025import android.app.Notification;
Uriel Saded81ddc92018-12-21 15:16:56 -080026import android.app.RemoteInput;
Uriel Sade298f6c02018-12-19 20:34:43 -080027import android.content.Context;
28import android.os.Bundle;
29import android.provider.Settings;
30import android.service.notification.StatusBarNotification;
31import android.util.Log;
32
Ritwika Mitra60ab82a2019-05-23 10:10:00 -070033import androidx.annotation.StringDef;
Uriel Sade298f6c02018-12-19 20:34:43 -080034import androidx.core.app.NotificationCompat;
35
36import com.android.car.assist.CarVoiceInteractionSession;
37import com.android.internal.app.AssistUtils;
38
Priyank Singh128ff292019-04-29 20:07:36 -070039import java.util.ArrayList;
Uriel Sade298f6c02018-12-19 20:34:43 -080040import java.util.Arrays;
41import java.util.Collections;
42import java.util.HashSet;
43import java.util.List;
44import java.util.Objects;
45import java.util.Set;
46import java.util.stream.Collectors;
47import java.util.stream.IntStream;
48
49/**
50 * Util class providing helper methods to interact with the current active voice service,
51 * while ensuring that the active voice service has the required permissions.
52 */
53public class CarAssistUtils {
54 public static final String TAG = "CarAssistUtils";
Uriel Saded81ddc92018-12-21 15:16:56 -080055 private static final List<Integer> REQUIRED_SEMANTIC_ACTIONS = Collections.unmodifiableList(
Uriel Sade298f6c02018-12-19 20:34:43 -080056 Arrays.asList(
Uriel Saded81ddc92018-12-21 15:16:56 -080057 SEMANTIC_ACTION_MARK_AS_READ
Uriel Sade298f6c02018-12-19 20:34:43 -080058 )
59 );
60
Ritwika Mitraaaf0c172019-01-30 08:25:03 -080061 private static final List<Integer> SUPPORTED_SEMANTIC_ACTIONS = Collections.unmodifiableList(
62 Arrays.asList(
63 SEMANTIC_ACTION_MARK_AS_READ,
64 SEMANTIC_ACTION_REPLY
65 )
66 );
Uriel Saded81ddc92018-12-21 15:16:56 -080067
Uriel Sade298f6c02018-12-19 20:34:43 -080068 private final Context mContext;
69 private final AssistUtils mAssistUtils;
Uriel Saded81ddc92018-12-21 15:16:56 -080070 private final FallbackAssistant mFallbackAssistant;
71 private final String mErrorMessage;
Ritwika Mitra60ab82a2019-05-23 10:10:00 -070072 private final boolean mIsFallbackAssistantEnabled;
Uriel Sade298f6c02018-12-19 20:34:43 -080073
Ritwika Mitra35968f82019-05-22 16:05:28 -070074 /** Interface used to receive callbacks from voice action requests. */
75 public interface ActionRequestCallback {
Ritwika Mitra60ab82a2019-05-23 10:10:00 -070076 /**
77 * The action was successfully completed either by the active or fallback assistant.
78 **/
79 String RESULT_SUCCESS = "SUCCESS";
80
81 /**
82 * The action was not successfully completed, but the active assistant has been prompted to
83 * alert the user of this error and handle it. The caller of this callback is recommended
84 * to NOT alert the user of this error again.
85 */
86 String RESULT_FAILED_WITH_ERROR_HANDLED = "FAILED_WITH_ERROR_HANDLED";
87
88 /**
89 * The action has not been successfully completed, and the error has not been handled.
90 **/
91 String RESULT_FAILED = "FAILED";
92
93 /**
94 * The list of result states.
95 */
96 @StringDef({RESULT_FAILED, RESULT_FAILED_WITH_ERROR_HANDLED, RESULT_SUCCESS})
97 @interface ResultState {
98 }
99
100 /** Callback containing the result of completing the voice action request. */
101 void onResult(@ResultState String state);
Ritwika Mitra35968f82019-05-22 16:05:28 -0700102 }
103
Uriel Sade298f6c02018-12-19 20:34:43 -0800104 public CarAssistUtils(Context context) {
Uriel Sade298f6c02018-12-19 20:34:43 -0800105 mContext = context;
Uriel Saded81ddc92018-12-21 15:16:56 -0800106 mAssistUtils = new AssistUtils(context);
Ritwika Mitraadb2bfe2019-05-01 10:33:06 -0700107 mFallbackAssistant = new FallbackAssistant(context);
Uriel Saded81ddc92018-12-21 15:16:56 -0800108 mErrorMessage = context.getString(R.string.assist_action_failed_toast);
Ritwika Mitra60ab82a2019-05-23 10:10:00 -0700109 mIsFallbackAssistantEnabled =
110 context.getResources().getBoolean(R.bool.config_enableFallbackAssistant);
111 }
112
113 /**
114 * @return {@code true} if there is an active assistant.
115 */
116 public boolean hasActiveAssistant() {
117 return mAssistUtils.getActiveServiceComponentName() != null;
Uriel Sade298f6c02018-12-19 20:34:43 -0800118 }
119
120 /**
121 * Returns true if the current active assistant has notification listener permissions.
122 */
123 public boolean assistantIsNotificationListener() {
Ritwika Mitra3ebfef52019-06-03 09:56:02 -0700124 if (!hasActiveAssistant()) {
Ritwika Mitra60ab82a2019-05-23 10:10:00 -0700125 if (Log.isLoggable(TAG, Log.DEBUG)) {
126 Log.d(TAG, "No active assistant was found.");
127 }
128 return false;
129 }
Ritwika Mitra3ebfef52019-06-03 09:56:02 -0700130 final String activeComponent = mAssistUtils.getActiveServiceComponentName()
131 .flattenToString();
Ritwika Mitraeb040682019-02-01 15:36:23 -0800132 int slashIndex = activeComponent.indexOf("/");
133 final String activePackage = activeComponent.substring(0, slashIndex);
134
Ritwika Mitra95cf93c2019-04-29 16:40:04 -0700135 final String listeners = Settings.Secure.getStringForUser(mContext.getContentResolver(),
136 Settings.Secure.ENABLED_NOTIFICATION_LISTENERS, ActivityManager.getCurrentUser());
Uriel Sade298f6c02018-12-19 20:34:43 -0800137
Ritwika Mitra73da8582019-05-21 11:56:20 -0700138 if (Log.isLoggable(TAG, Log.DEBUG)) {
139 Log.d(TAG, "Current user: " + ActivityManager.getCurrentUser()
140 + " has active voice service: " + activePackage + " and enabled notification "
141 + " listeners: " + listeners);
142 }
143
Ritwika Mitraeb040682019-02-01 15:36:23 -0800144 if (listeners != null) {
145 for (String listener : Arrays.asList(listeners.split(":"))) {
146 if (listener.contains(activePackage)) {
147 return true;
148 }
149 }
150 }
Ritwika Mitracdc99442019-04-26 09:55:11 -0700151 Log.w(TAG, "No notification listeners found for assistant: " + activeComponent);
Ritwika Mitraeb040682019-02-01 15:36:23 -0800152 return false;
Uriel Sade298f6c02018-12-19 20:34:43 -0800153 }
154
155 /**
156 * Checks whether the notification is a car-compatible messaging notification.
157 *
158 * @param sbn The notification being checked.
159 * @return true if the notification is a car-compatible messaging notification.
160 */
161 public static boolean isCarCompatibleMessagingNotification(StatusBarNotification sbn) {
162 return hasMessagingStyle(sbn)
163 && hasRequiredAssistantCallbacks(sbn)
Ritwika Mitrafdc114d2019-07-15 10:41:48 -0700164 && ((getReplyAction(sbn.getNotification()) == null)
165 || replyCallbackHasRemoteInput(sbn))
Uriel Sade298f6c02018-12-19 20:34:43 -0800166 && assistantCallbacksShowNoUi(sbn);
167 }
168
169 /** Returns true if the semantic action provided can be supported. */
170 public static boolean isSupportedSemanticAction(int semanticAction) {
Uriel Saded81ddc92018-12-21 15:16:56 -0800171 return SUPPORTED_SEMANTIC_ACTIONS.contains(semanticAction);
Uriel Sade298f6c02018-12-19 20:34:43 -0800172 }
173
174 /**
175 * Returns true if the notification has a messaging style.
176 * <p/>
177 * This is the case if the notification in question was provided an instance of
178 * {@link Notification.MessagingStyle} (or an instance of
179 * {@link NotificationCompat.MessagingStyle} if {@link NotificationCompat} was used).
180 */
181 private static boolean hasMessagingStyle(StatusBarNotification sbn) {
182 return NotificationCompat.MessagingStyle
183 .extractMessagingStyleFromNotification(sbn.getNotification()) != null;
184 }
185
186 /**
187 * Returns true if the notification has the required Assistant callbacks to be considered
188 * a car-compatible messaging notification. The callbacks must be unambiguous, therefore false
189 * is returned if multiple callbacks exist for any semantic action that is supported.
190 */
191 private static boolean hasRequiredAssistantCallbacks(StatusBarNotification sbn) {
Priyank Singh128ff292019-04-29 20:07:36 -0700192 List<Integer> semanticActionList = getAllActions(sbn.getNotification())
193 .stream()
194 .map(NotificationCompat.Action::getSemanticAction)
Uriel Saded81ddc92018-12-21 15:16:56 -0800195 .filter(REQUIRED_SEMANTIC_ACTIONS::contains)
Uriel Sade298f6c02018-12-19 20:34:43 -0800196 .collect(Collectors.toList());
197 Set<Integer> semanticActionSet = new HashSet<>(semanticActionList);
Uriel Sade298f6c02018-12-19 20:34:43 -0800198 return semanticActionList.size() == semanticActionSet.size()
Uriel Saded81ddc92018-12-21 15:16:56 -0800199 && semanticActionSet.containsAll(REQUIRED_SEMANTIC_ACTIONS);
Uriel Sade298f6c02018-12-19 20:34:43 -0800200 }
201
Priyank Singh128ff292019-04-29 20:07:36 -0700202 /** Retrieves all visible and invisible {@link Action}s from the {@link #notification}. */
Ritwika Mitraadb2bfe2019-05-01 10:33:06 -0700203 public static List<NotificationCompat.Action> getAllActions(Notification notification) {
Priyank Singh128ff292019-04-29 20:07:36 -0700204 List<NotificationCompat.Action> actions = new ArrayList<>();
205 actions.addAll(NotificationCompat.getInvisibleActions(notification));
206 for (int i = 0; i < NotificationCompat.getActionCount(notification); i++) {
207 actions.add(NotificationCompat.getAction(notification, i));
208 }
209 return actions;
210 }
211
Uriel Sade298f6c02018-12-19 20:34:43 -0800212 /**
Ritwika Mitraadb2bfe2019-05-01 10:33:06 -0700213 * Retrieves the {@link NotificationCompat.Action} containing the
214 * {@link NotificationCompat.Action#SEMANTIC_ACTION_MARK_AS_READ} semantic action.
215 */
216 @Nullable
217 public static NotificationCompat.Action getMarkAsReadAction(Notification notification) {
218 for (NotificationCompat.Action action : getAllActions(notification)) {
219 if (action.getSemanticAction()
220 == NotificationCompat.Action.SEMANTIC_ACTION_MARK_AS_READ) {
221 return action;
222 }
223 }
224 return null;
225 }
226
227 /**
Ritwika Mitrafdc114d2019-07-15 10:41:48 -0700228 * Retrieves the {@link NotificationCompat.Action} containing the
229 * {@link NotificationCompat.Action#SEMANTIC_ACTION_REPLY} semantic action.
230 */
231 @Nullable
232 private static NotificationCompat.Action getReplyAction(Notification notification) {
233 for (NotificationCompat.Action action : getAllActions(notification)) {
234 if (action.getSemanticAction()
235 == NotificationCompat.Action.SEMANTIC_ACTION_REPLY) {
236 return action;
237 }
238 }
239 return null;
240 }
241
242 /**
Uriel Saded81ddc92018-12-21 15:16:56 -0800243 * Returns true if the reply callback has at least one {@link RemoteInput}.
Uriel Sade298f6c02018-12-19 20:34:43 -0800244 * <p/>
245 * Precondition: There exists only one reply callback.
246 */
247 private static boolean replyCallbackHasRemoteInput(StatusBarNotification sbn) {
248 return Arrays.stream(sbn.getNotification().actions)
Uriel Saded81ddc92018-12-21 15:16:56 -0800249 .filter(action -> action.getSemanticAction() == SEMANTIC_ACTION_REPLY)
Uriel Sade298f6c02018-12-19 20:34:43 -0800250 .map(Notification.Action::getRemoteInputs)
Uriel Saded81ddc92018-12-21 15:16:56 -0800251 .filter(Objects::nonNull)
252 .anyMatch(remoteInputs -> remoteInputs.length > 0);
Uriel Sade298f6c02018-12-19 20:34:43 -0800253 }
254
255 /** Returns true if all Assistant callbacks indicate that they show no UI, false otherwise. */
256 private static boolean assistantCallbacksShowNoUi(StatusBarNotification sbn) {
257 final Notification notification = sbn.getNotification();
258 return IntStream.range(0, notification.actions.length)
259 .mapToObj(i -> NotificationCompat.getAction(notification, i))
260 .filter(Objects::nonNull)
Uriel Saded81ddc92018-12-21 15:16:56 -0800261 .filter(action -> SUPPORTED_SEMANTIC_ACTIONS.contains(action.getSemanticAction()))
Uriel Sade298f6c02018-12-19 20:34:43 -0800262 .noneMatch(NotificationCompat.Action::getShowsUserInterface);
263 }
264
265 /**
Uriel Saded81ddc92018-12-21 15:16:56 -0800266 * Requests a given action from the current active Assistant.
267 *
Ritwika Mitraadb2bfe2019-05-01 10:33:06 -0700268 * @param sbn the notification payload to deliver to assistant
269 * @param voiceAction must be a valid {@link CarVoiceInteractionSession} VOICE_ACTION
Ritwika Mitra35968f82019-05-22 16:05:28 -0700270 * @param callback the callback to issue on success/error
Uriel Sade298f6c02018-12-19 20:34:43 -0800271 */
Ritwika Mitra35968f82019-05-22 16:05:28 -0700272 public void requestAssistantVoiceAction(StatusBarNotification sbn, String voiceAction,
273 ActionRequestCallback callback) {
Uriel Saded81ddc92018-12-21 15:16:56 -0800274 if (!isCarCompatibleMessagingNotification(sbn)) {
275 Log.w(TAG, "Assistant action requested for non-compatible notification.");
Ritwika Mitra60ab82a2019-05-23 10:10:00 -0700276 callback.onResult(ActionRequestCallback.RESULT_FAILED);
Ritwika Mitra35968f82019-05-22 16:05:28 -0700277 return;
Uriel Sade298f6c02018-12-19 20:34:43 -0800278 }
279
Ritwika Mitraadb2bfe2019-05-01 10:33:06 -0700280 switch (voiceAction) {
281 case CarVoiceInteractionSession.VOICE_ACTION_READ_NOTIFICATION:
Ritwika Mitra35968f82019-05-22 16:05:28 -0700282 readMessageNotification(sbn, callback);
283 return;
Ritwika Mitraadb2bfe2019-05-01 10:33:06 -0700284 case CarVoiceInteractionSession.VOICE_ACTION_REPLY_NOTIFICATION:
Ritwika Mitra35968f82019-05-22 16:05:28 -0700285 replyMessageNotification(sbn, callback);
286 return;
Uriel Saded81ddc92018-12-21 15:16:56 -0800287 default:
Ritwika Mitra35968f82019-05-22 16:05:28 -0700288 Log.w(TAG, "Requested Assistant action for unsupported semantic action.");
Ritwika Mitra60ab82a2019-05-23 10:10:00 -0700289 callback.onResult(ActionRequestCallback.RESULT_FAILED);
Ritwika Mitra35968f82019-05-22 16:05:28 -0700290 return;
Uriel Saded81ddc92018-12-21 15:16:56 -0800291 }
Uriel Sade298f6c02018-12-19 20:34:43 -0800292 }
293
294 /**
295 * Requests a read action for the notification from the current active Assistant.
Ritwika Mitra35968f82019-05-22 16:05:28 -0700296 * If the Assistant cannot handle the request, a fallback implementation will attempt to
Uriel Saded81ddc92018-12-21 15:16:56 -0800297 * handle it.
Uriel Sade298f6c02018-12-19 20:34:43 -0800298 *
Ritwika Mitra35968f82019-05-22 16:05:28 -0700299 * @param sbn the notification to deliver as the payload
300 * @param callback the callback to issue on success/error
Uriel Sade298f6c02018-12-19 20:34:43 -0800301 */
Ritwika Mitra35968f82019-05-22 16:05:28 -0700302 private void readMessageNotification(StatusBarNotification sbn,
303 ActionRequestCallback callback) {
304 Bundle args = BundleBuilder.buildAssistantReadBundle(sbn);
305 String action = CarVoiceInteractionSession.VOICE_ACTION_READ_NOTIFICATION;
306
307 requestAction(action, sbn, args, callback);
Uriel Sade298f6c02018-12-19 20:34:43 -0800308 }
309
310 /**
311 * Requests a reply action for the notification from the current active Assistant.
Ritwika Mitra35968f82019-05-22 16:05:28 -0700312 * If the Assistant cannot handle the request, a fallback implementation will attempt to
Uriel Saded81ddc92018-12-21 15:16:56 -0800313 * handle it.
Uriel Sade298f6c02018-12-19 20:34:43 -0800314 *
Ritwika Mitra35968f82019-05-22 16:05:28 -0700315 * @param sbn the notification to deliver as the payload
316 * @param callback the callback to issue on success/error
Uriel Sade298f6c02018-12-19 20:34:43 -0800317 */
Ritwika Mitra35968f82019-05-22 16:05:28 -0700318 private void replyMessageNotification(StatusBarNotification sbn,
319 ActionRequestCallback callback) {
320 Bundle args = BundleBuilder.buildAssistantReplyBundle(sbn);
321 String action = CarVoiceInteractionSession.VOICE_ACTION_REPLY_NOTIFICATION;
322
323 requestAction(action, sbn, args, callback);
Uriel Sade298f6c02018-12-19 20:34:43 -0800324 }
325
Ritwika Mitra35968f82019-05-22 16:05:28 -0700326 private void requestAction(String action, StatusBarNotification sbn, Bundle payloadArguments,
327 ActionRequestCallback callback) {
Ritwika Mitra3ebfef52019-06-03 09:56:02 -0700328
329 if (!hasActiveAssistant()) {
Ritwika Mitra60ab82a2019-05-23 10:10:00 -0700330 if (mIsFallbackAssistantEnabled) {
331 handleFallback(sbn, action, callback);
332 } else {
333 // If there is no active assistant, and fallback assistant is not enabled, then
Ritwika Mitra3ebfef52019-06-03 09:56:02 -0700334 // there is nothing for us to do.
335 callback.onResult(ActionRequestCallback.RESULT_FAILED);
336 }
337 return;
338 }
339
340 if (!assistantIsNotificationListener()) {
341 if (mIsFallbackAssistantEnabled) {
342 handleFallback(sbn, action, callback);
343 } else {
344 // If there is an active assistant, alert them to request permissions.
345 String resultState = requestHandleMissingPermission()
346 ? ActionRequestCallback.RESULT_FAILED_WITH_ERROR_HANDLED
347 : ActionRequestCallback.RESULT_FAILED;
Ritwika Mitra60ab82a2019-05-23 10:10:00 -0700348 callback.onResult(resultState);
349 }
Ritwika Mitra35968f82019-05-22 16:05:28 -0700350 return;
Ritwika Mitracdc99442019-04-26 09:55:11 -0700351 }
Ritwika Mitra35968f82019-05-22 16:05:28 -0700352
353 boolean success = mAssistUtils.showSessionForActiveService(payloadArguments,
354 CarVoiceInteractionSession.SHOW_SOURCE_NOTIFICATION, null, null);
Ritwika Mitra60ab82a2019-05-23 10:10:00 -0700355 String resultState = ActionRequestCallback.RESULT_FAILED;
Ritwika Mitra35968f82019-05-22 16:05:28 -0700356 if (success) {
357 if (Log.isLoggable(TAG, Log.DEBUG)) {
358 Log.d(TAG, "Launching active assistant for action: " + action);
359 }
Ritwika Mitra60ab82a2019-05-23 10:10:00 -0700360 resultState = ActionRequestCallback.RESULT_SUCCESS;
Ritwika Mitra35968f82019-05-22 16:05:28 -0700361 } else {
362 Log.w(TAG, "Failed to launch active assistant.");
363 }
Ritwika Mitra60ab82a2019-05-23 10:10:00 -0700364 callback.onResult(resultState);
Ritwika Mitra35968f82019-05-22 16:05:28 -0700365 }
366
367 private void handleFallback(StatusBarNotification sbn, String action,
368 ActionRequestCallback callback) {
369 FallbackAssistant.Listener listener = new FallbackAssistant.Listener() {
370 @Override
Ritwika Mitra60ab82a2019-05-23 10:10:00 -0700371 public void onMessageRead(boolean hasError) {
372 String resultState = hasError ? ActionRequestCallback.RESULT_FAILED
373 : ActionRequestCallback.RESULT_SUCCESS;
374 // Only change the resultState if fallback failed, and assistant successfully
375 // alerted to prompt user for permissions.
Ritwika Mitra3ebfef52019-06-03 09:56:02 -0700376 if (hasActiveAssistant() && requestHandleMissingPermission()
Ritwika Mitra60ab82a2019-05-23 10:10:00 -0700377 && resultState.equals(ActionRequestCallback.RESULT_FAILED)) {
378 resultState = ActionRequestCallback.RESULT_FAILED_WITH_ERROR_HANDLED;
379 }
380 callback.onResult(resultState);
Ritwika Mitra35968f82019-05-22 16:05:28 -0700381 }
382 };
383
384 switch (action) {
385 case CarVoiceInteractionSession.VOICE_ACTION_READ_NOTIFICATION:
386 mFallbackAssistant.handleReadAction(sbn, listener);
387 break;
388 case CarVoiceInteractionSession.VOICE_ACTION_REPLY_NOTIFICATION:
389 mFallbackAssistant.handleErrorMessage(mErrorMessage, listener);
390 break;
391 default:
392 Log.w(TAG, "Requested unsupported FallbackAssistant action.");
Ritwika Mitra60ab82a2019-05-23 10:10:00 -0700393 callback.onResult(ActionRequestCallback.RESULT_FAILED);
Ritwika Mitra35968f82019-05-22 16:05:28 -0700394 return;
395 }
Uriel Sade298f6c02018-12-19 20:34:43 -0800396 }
Ritwika Mitra60ab82a2019-05-23 10:10:00 -0700397
398 /**
399 * Requests the active voice service to handle the permissions missing error.
400 *
401 * @return {@code true} if active assistant was successfully alerted.
402 **/
Ritwika Mitra3ebfef52019-06-03 09:56:02 -0700403 private boolean requestHandleMissingPermission() {
Ritwika Mitra60ab82a2019-05-23 10:10:00 -0700404 Bundle payloadArguments = BundleBuilder
405 .buildAssistantHandleExceptionBundle(
406 EXCEPTION_NOTIFICATION_LISTENER_PERMISSIONS_MISSING);
407 boolean requestedSuccessfully = mAssistUtils.showSessionForActiveService(payloadArguments,
408 CarVoiceInteractionSession.SHOW_SOURCE_NOTIFICATION, null, null);
409 if (!requestedSuccessfully) {
410 Log.w(TAG, "Failed to alert assistant to request permissions from user");
411 }
412 return requestedSuccessfully;
413 }
Uriel Sade298f6c02018-12-19 20:34:43 -0800414}