Add FallbackAssistant config flag, alert assistant to request perms
Created a config flag to enable or disable the FallbackAssistant.
If there is an active voice service that doesn't have notification listener
permissions, notify it to request permissions from the user.
Bug: 123095811
Test: manual
Change-Id: I069a1c5956d300960a3bb62178d54976e68b238b
diff --git a/car-assist-client-lib/src/com/android/car/assist/client/CarAssistUtils.java b/car-assist-client-lib/src/com/android/car/assist/client/CarAssistUtils.java
index 4472fa4..84f4408 100644
--- a/car-assist-client-lib/src/com/android/car/assist/client/CarAssistUtils.java
+++ b/car-assist-client-lib/src/com/android/car/assist/client/CarAssistUtils.java
@@ -19,6 +19,8 @@
import static android.app.Notification.Action.SEMANTIC_ACTION_REPLY;
import static android.service.voice.VoiceInteractionSession.SHOW_SOURCE_NOTIFICATION;
+import static com.android.car.assist.CarVoiceInteractionSession.EXCEPTION_NOTIFICATION_LISTENER_PERMISSIONS_MISSING;
+
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.Notification;
@@ -29,6 +31,7 @@
import android.service.notification.StatusBarNotification;
import android.util.Log;
+import androidx.annotation.StringDef;
import androidx.core.app.NotificationCompat;
import com.android.car.assist.CarVoiceInteractionSession;
@@ -68,11 +71,36 @@
private final AssistUtils mAssistUtils;
private final FallbackAssistant mFallbackAssistant;
private final String mErrorMessage;
+ private final boolean mIsFallbackAssistantEnabled;
/** Interface used to receive callbacks from voice action requests. */
public interface ActionRequestCallback {
- /** Callback issued from a voice request on success/error. */
- void onResult(boolean hasError);
+ /**
+ * The action was successfully completed either by the active or fallback assistant.
+ **/
+ String RESULT_SUCCESS = "SUCCESS";
+
+ /**
+ * The action was not successfully completed, but the active assistant has been prompted to
+ * alert the user of this error and handle it. The caller of this callback is recommended
+ * to NOT alert the user of this error again.
+ */
+ String RESULT_FAILED_WITH_ERROR_HANDLED = "FAILED_WITH_ERROR_HANDLED";
+
+ /**
+ * The action has not been successfully completed, and the error has not been handled.
+ **/
+ String RESULT_FAILED = "FAILED";
+
+ /**
+ * The list of result states.
+ */
+ @StringDef({RESULT_FAILED, RESULT_FAILED_WITH_ERROR_HANDLED, RESULT_SUCCESS})
+ @interface ResultState {
+ }
+
+ /** Callback containing the result of completing the voice action request. */
+ void onResult(@ResultState String state);
}
public CarAssistUtils(Context context) {
@@ -80,12 +108,27 @@
mAssistUtils = new AssistUtils(context);
mFallbackAssistant = new FallbackAssistant(context);
mErrorMessage = context.getString(R.string.assist_action_failed_toast);
+ mIsFallbackAssistantEnabled =
+ context.getResources().getBoolean(R.bool.config_enableFallbackAssistant);
+ }
+
+ /**
+ * @return {@code true} if there is an active assistant.
+ */
+ public boolean hasActiveAssistant() {
+ return mAssistUtils.getActiveServiceComponentName() != null;
}
/**
* Returns true if the current active assistant has notification listener permissions.
*/
public boolean assistantIsNotificationListener() {
+ if (!hasActiveAssistant()) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "No active assistant was found.");
+ }
+ return false;
+ }
final String activeComponent = mAssistUtils.getActiveServiceComponentName()
.flattenToString();
int slashIndex = activeComponent.indexOf("/");
@@ -216,7 +259,7 @@
ActionRequestCallback callback) {
if (!isCarCompatibleMessagingNotification(sbn)) {
Log.w(TAG, "Assistant action requested for non-compatible notification.");
- callback.onResult(/* hasError= */ true);
+ callback.onResult(ActionRequestCallback.RESULT_FAILED);
return;
}
@@ -229,7 +272,7 @@
return;
default:
Log.w(TAG, "Requested Assistant action for unsupported semantic action.");
- callback.onResult(/* hasError= */ true);
+ callback.onResult(ActionRequestCallback.RESULT_FAILED);
return;
}
}
@@ -269,26 +312,46 @@
private void requestAction(String action, StatusBarNotification sbn, Bundle payloadArguments,
ActionRequestCallback callback) {
+ if (!hasActiveAssistant()) {
+ if (mIsFallbackAssistantEnabled) {
+ handleFallback(sbn, action, callback);
+ } else {
+ // If there is no active assistant, and fallback assistant is not enabled, then
+ // there is nothing for us to do.
+ callback.onResult(ActionRequestCallback.RESULT_FAILED);
+ }
+ return;
+ }
+
if (!assistantIsNotificationListener()) {
- handleFallback(sbn, action, callback);
+ if (mIsFallbackAssistantEnabled) {
+ handleFallback(sbn, action, callback);
+ } else {
+ // If there is an active assistant, alert them to request permissions.
+ callback.onResult(requestHandleMissingPermissions()
+ ? ActionRequestCallback.RESULT_FAILED_WITH_ERROR_HANDLED
+ : ActionRequestCallback.RESULT_FAILED);
+ }
return;
}
IVoiceActionCheckCallback actionCheckCallback = new IVoiceActionCheckCallback.Stub() {
@Override
public void onComplete(List<String> supportedActions) {
+ String resultState = ActionRequestCallback.RESULT_FAILED;
boolean success;
if (supportedActions != null && supportedActions.contains(action)) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Launching active Assistant for action: " + action);
}
- success = mAssistUtils.showSessionForActiveService(payloadArguments,
- SHOW_SOURCE_NOTIFICATION, null, null);
+ if (mAssistUtils.showSessionForActiveService(payloadArguments,
+ SHOW_SOURCE_NOTIFICATION, null, null)) {
+ resultState = ActionRequestCallback.RESULT_SUCCESS;
+ }
} else {
Log.w(TAG, "Active Assistant does not support voice action: " + action);
- success = false;
}
- callback.onResult(/* hasError= */ !success);
+ callback.onResult(resultState);
}
};
@@ -300,8 +363,16 @@
ActionRequestCallback callback) {
FallbackAssistant.Listener listener = new FallbackAssistant.Listener() {
@Override
- public void onMessageRead(boolean error) {
- callback.onResult(error);
+ public void onMessageRead(boolean hasError) {
+ String resultState = hasError ? ActionRequestCallback.RESULT_FAILED
+ : ActionRequestCallback.RESULT_SUCCESS;
+ // Only change the resultState if fallback failed, and assistant successfully
+ // alerted to prompt user for permissions.
+ if (hasActiveAssistant() && requestHandleMissingPermissions()
+ && resultState.equals(ActionRequestCallback.RESULT_FAILED)) {
+ resultState = ActionRequestCallback.RESULT_FAILED_WITH_ERROR_HANDLED;
+ }
+ callback.onResult(resultState);
}
};
@@ -314,8 +385,25 @@
break;
default:
Log.w(TAG, "Requested unsupported FallbackAssistant action.");
- callback.onResult(/* hasError= */ true);
+ callback.onResult(ActionRequestCallback.RESULT_FAILED);
return;
}
}
+
+ /**
+ * Requests the active voice service to handle the permissions missing error.
+ *
+ * @return {@code true} if active assistant was successfully alerted.
+ **/
+ private boolean requestHandleMissingPermissions() {
+ Bundle payloadArguments = BundleBuilder
+ .buildAssistantHandleExceptionBundle(
+ EXCEPTION_NOTIFICATION_LISTENER_PERMISSIONS_MISSING);
+ boolean requestedSuccessfully = mAssistUtils.showSessionForActiveService(payloadArguments,
+ SHOW_SOURCE_NOTIFICATION, null, null);
+ if (!requestedSuccessfully) {
+ Log.w(TAG, "Failed to alert assistant to request permissions from user");
+ }
+ return requestedSuccessfully;
+ }
}