/* * Copyright (C) 2019 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.car.assist; import android.content.Context; import android.os.Bundle; import android.os.Handler; import android.service.notification.StatusBarNotification; import android.service.voice.VoiceInteractionService; import android.service.voice.VoiceInteractionSession; import androidx.annotation.StringDef; import com.android.car.assist.payloadhandlers.NotificationPayloadHandler; /** * An active voice interaction session on the car, providing additional actions which assistant * should act on. Override the {@link #onShow(String, Bundle, int)} to received the action specified * by the voice session initiator. */ public abstract class CarVoiceInteractionSession extends VoiceInteractionSession { /** The key used for the action {@link String} in the payload {@link Bundle}. */ public static final String KEY_ACTION = "KEY_ACTION"; /** * The key used for the {@link CarVoiceInteractionSession#VOICE_ACTION_HANDLE_EXCEPTION} payload * {@link Bundle}. Must map to a {@link ExceptionValue}. */ public static final String KEY_EXCEPTION = "KEY_EXCEPTION"; /** * The key used for the {@link CarVoiceInteractionSession#VOICE_ACTION_HANDLE_EXCEPTION} payload * {@link Bundle}. Must map to a boolean. If value is true, the Fallback Assistant that can * handle the user's request has been disabled. */ public static final String KEY_FALLBACK_ASSISTANT_ENABLED = "KEY_FALLBACK_ASSISTANT_ENABLED"; /** * The key used for the payload {@link Bundle}, if a {@link StatusBarNotification} is used as * the payload. */ public static final String KEY_NOTIFICATION = "KEY_NOTIFICATION"; /** Indicates to assistant that no action was specified. */ public static final String VOICE_ACTION_NO_ACTION = "VOICE_ACTION_NO_ACTION"; /** Indicates to assistant that a read action is being requested for a given payload. */ public static final String VOICE_ACTION_READ_NOTIFICATION = "VOICE_ACTION_READ_NOTIFICATION"; /** Indicates to assistant that a reply action is being requested for a given payload. */ public static final String VOICE_ACTION_REPLY_NOTIFICATION = "VOICE_ACTION_REPLY_NOTIFICATION"; /** * Indicates to assistant that it should resolve the exception in the given payload (found in * {@link CarVoiceInteractionSession#KEY_EXCEPTION}'s value). */ public static final String VOICE_ACTION_HANDLE_EXCEPTION = "VOICE_ACTION_HANDLE_EXCEPTION"; /** * The list of exceptions the active voice service must handle. */ @StringDef({EXCEPTION_NOTIFICATION_LISTENER_PERMISSIONS_MISSING}) public @interface ExceptionValue {} /** * Indicates to assistant that it is missing the Notification Listener permission, and should * request this permission from the user. **/ public static final String EXCEPTION_NOTIFICATION_LISTENER_PERMISSIONS_MISSING = "EXCEPTION_NOTIFICATION_LISTENER_PERMISSIONS_MISSING"; private final NotificationPayloadHandler mNotificationPayloadHandler; public CarVoiceInteractionSession(Context context) { super(context); mNotificationPayloadHandler = new NotificationPayloadHandler(getContext()); } public CarVoiceInteractionSession(Context context, Handler handler) { super(context, handler); mNotificationPayloadHandler = new NotificationPayloadHandler(getContext()); } /** * Returns the notification payload handler, which can be used to handle actions related to * notification payloads. */ public NotificationPayloadHandler getNotificationPayloadHandler() { return mNotificationPayloadHandler; } @Override public final void onShow(Bundle args, int showFlags) { super.onShow(args, showFlags); if (args != null && isCarNotificationSource(showFlags)) { String action = getRequestedVoiceAction(args); if (!VOICE_ACTION_NO_ACTION.equals(action)) { onShow(action, args, showFlags); return; } } onShow(VOICE_ACTION_NO_ACTION, args, showFlags); } /** * Called when the session UI is going to be shown. This is called after * {@link #onCreateContentView} (if the session's content UI needed to be created) and * immediately prior to the window being shown. This may be called while the window * is already shown, if a show request has come in while it is shown, to allow you to * update the UI to match the new show arguments. * * @param action The action that is being requested for this session * (e.g. {@link CarVoiceInteractionSession#VOICE_ACTION_READ_NOTIFICATION}, * {@link CarVoiceInteractionSession#VOICE_ACTION_REPLY_NOTIFICATION}). * @param args The arguments that were supplied to * {@link VoiceInteractionService#showSession VoiceInteractionService.showSession}. * @param flags The show flags originally provided to * {@link VoiceInteractionService#showSession VoiceInteractionService.showSession}. */ protected abstract void onShow(String action, Bundle args, int flags); /** * Returns true if the request was initiated for a car notification. */ private static boolean isCarNotificationSource(int flags) { return (flags & SHOW_SOURCE_NOTIFICATION) != 0; } /** * Returns the action {@link String} provided in the args {@Bundle}, * or {@link CarVoiceInteractionSession#VOICE_ACTION_NO_ACTION} if no such string was provided. */ protected static String getRequestedVoiceAction(Bundle args) { return args.getString(KEY_ACTION, VOICE_ACTION_NO_ACTION); } }