blob: b834fd6a36e03363f0f5bc7e2fe7ebf8c4a2bac4 [file] [log] [blame]
/*
* 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.payloadhandlers;
import android.app.Notification;
import android.app.Notification.Action;
import android.app.PendingIntent;
import android.app.RemoteInput;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.service.notification.StatusBarNotification;
import android.util.Log;
import androidx.annotation.Nullable;
import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationCompat.MessagingStyle;
import androidx.core.app.NotificationCompat.MessagingStyle.Message;
import com.android.car.assist.CarVoiceInteractionSession;
import java.util.ArrayList;
import java.util.List;
/**
* Class used by {@link CarVoiceInteractionSession} to handle payload actions for
* {@link StatusBarNotification} payloads, such as getting payload data, writing to remote inputs,
* and firing the appropriate actions upon completion.
*/
public class NotificationPayloadHandler {
private static final String TAG = "NotificationPayloadHandler";
/** The context used by this instance to fire actions */
private final Context mContext;
public NotificationPayloadHandler(Context context) {
mContext = context;
}
/** @return The {@link StatusBarNotification}, or null if not found. */
@Nullable
public StatusBarNotification getStatusBarNotification(Bundle args) {
return args.getParcelable(CarVoiceInteractionSession.KEY_NOTIFICATION);
}
/**
* Returns the {@link Notification} of the {@link StatusBarNotification}
* provided in the args {@link Bundle}.
*
* @return The {@link StatusBarNotification}'s {@link Notification}, or null if not found.
*/
@Nullable
public Notification getNotification(Bundle args) {
StatusBarNotification sbn = args.getParcelable(CarVoiceInteractionSession.KEY_NOTIFICATION);
return sbn == null ? null : sbn.getNotification();
}
/**
* Retrieves all messages associated with the provided {@link StatusBarNotification} in the
* args {@link Bundle}. These messages are provided through the notification's
* {@link MessagingStyle}, using {@link MessagingStyle#addMessage(Message)}.
*
* @param args the payload delivered to the voice interaction session
* @return all messages provided in the {@link MessagingStyle}
*/
public List<Message> getMessages(Bundle args) {
Notification notification = getNotification(args);
MessagingStyle messagingStyle = NotificationCompat.MessagingStyle
.extractMessagingStyleFromNotification(notification);
return messagingStyle == null ? new ArrayList<>() : messagingStyle.getMessages();
}
/**
* Retrieves the corresponding {@link Action} from the notification's callback actions.
*
* @param args the payload delivered to the voice interaction session
* @param semanticAction the {@link Action.SemanticAction} on which to select
* @return the first action for which {@link Action#getSemanticAction()} returns semanticAction,
* or null if no such action exists
*/
@Nullable
public Action getAction(Bundle args, int semanticAction) {
Notification notification = getNotification(args);
if (notification == null) {
Log.w(TAG, "getAction args bundle did not contain a notification");
return null;
}
for (Action action : notification.actions) {
if (action.getSemanticAction() == semanticAction) {
return action;
}
}
Log.w(TAG, String.format("Semantic action not found: %d", semanticAction));
return null;
}
/**
* Fires the {@link PendingIntent} of the corresponding {@link Action}, ensuring that any
* {@link RemoteInput}s corresponding to this action contain any addidional data.
*
* @param action the action to fire
* @return true if the {@link PendingIntent} was sent successfully; false otherwise.
*/
public boolean fireAction(Action action, @Nullable Intent additionalData) {
PendingIntent pendingIntent = action.actionIntent;
int resultCode = 0;
try {
if (additionalData == null) {
pendingIntent.send(resultCode);
} else {
pendingIntent.send(mContext, resultCode, additionalData);
}
} catch (PendingIntent.CanceledException e) {
return false;
}
return true;
}
/**
* Writes the given reply to the {@link RemoteInput} of the provided action callback.
* Requires that the action callback contains at least one {@link RemoteInput}.
* In the case that multiple {@link RemoteInput}s are provided, the first will be used.
*
* @param actionCallback the action containing the {@link RemoteInput}
* @param reply the reply that should be written to the {@link RemoteInput}
* @return the additional data to provide to the action intent upon firing; null on error
*
* @see NotificationPayloadHandler#fireAction(Action action, Intent additionalData)
*/
@Nullable
public Intent writeReply(@Nullable Action actionCallback, CharSequence reply) {
if (actionCallback == null) {
Log.e(TAG, "No action callback was provided.");
return null;
}
RemoteInput[] remoteInputs = actionCallback.getRemoteInputs();
if (remoteInputs == null || remoteInputs.length == 0) {
Log.e(TAG, "No RemoteInputs were provided in the action callback.");
return null;
}
if (remoteInputs.length > 1) {
Log.w(TAG, "Vague arguments. Using first RemoteInput.");
}
RemoteInput remoteInput = remoteInputs[0];
if (remoteInput == null) {
Log.e(TAG, "RemoteInput provided was null.");
return null;
}
Intent additionalData = new Intent();
Bundle results = new Bundle();
results.putCharSequence(remoteInput.getResultKey(), reply);
RemoteInput.addResultsToIntent(remoteInputs, additionalData, results);
return additionalData;
}
/**
* Writes the given reply to the {@link RemoteInput} of the reply callback, if present.
* Requires that a reply callback be included in the args {@link Bundle}, and that this
* callback contains at least one {@link RemoteInput}. In the case that multiple
* {@link RemoteInput}s are provided, the first will be used.
*
* @param args the payload arguments provided to the session
* @param reply the reply that should be written to the {@link RemoteInput}
* @return the additional data to provide to the reply action intent upon firing; null on error
*/
@Nullable
public Intent writeReply(Bundle args, CharSequence reply) {
return writeReply(getAction(args, Action.SEMANTIC_ACTION_REPLY), reply);
}
}