Merge "NAS Feedback API Change" into sc-dev

This commit is contained in:
Chloris Kuo
2021-02-19 22:41:40 +00:00
committed by Android (Google) Code Review
14 changed files with 207 additions and 53 deletions

View File

@@ -5556,6 +5556,7 @@ package android.app {
method public android.graphics.drawable.Icon getSmallIcon();
method public String getSortKey();
method public long getTimeoutAfter();
method public boolean hasImage();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.media.AudioAttributes AUDIO_ATTRIBUTES_DEFAULT;
field public static final int BADGE_ICON_LARGE = 2; // 0x2

View File

@@ -10180,6 +10180,7 @@ package android.service.notification {
method @Nullable public android.service.notification.Adjustment onNotificationEnqueued(@NonNull android.service.notification.StatusBarNotification, @NonNull android.app.NotificationChannel);
method @Nullable public android.service.notification.Adjustment onNotificationEnqueued(@NonNull android.service.notification.StatusBarNotification, @NonNull android.app.NotificationChannel, @NonNull android.service.notification.NotificationListenerService.RankingMap);
method public void onNotificationExpansionChanged(@NonNull String, boolean, boolean);
method public void onNotificationFeedbackReceived(@NonNull String, @NonNull android.service.notification.NotificationListenerService.RankingMap, @NonNull android.os.Bundle);
method public abstract void onNotificationSnoozedUntilContext(@NonNull android.service.notification.StatusBarNotification, @NonNull String);
method public void onNotificationVisibilityChanged(@NonNull String, boolean);
method public void onNotificationsSeen(@NonNull java.util.List<java.lang.String>);
@@ -10187,6 +10188,7 @@ package android.service.notification {
method public void onPanelRevealed(int);
method public void onSuggestedReplySent(@NonNull String, @NonNull CharSequence, int);
method public final void unsnoozeNotification(@NonNull String);
field public static final String FEEDBACK_RATING = "feedback.rating";
field public static final String SERVICE_INTERFACE = "android.service.notification.NotificationAssistantService";
field public static final int SOURCE_FROM_APP = 0; // 0x0
field public static final int SOURCE_FROM_ASSISTANT = 1; // 0x1

View File

@@ -300,6 +300,7 @@ package android.app {
method public void clickNotification(@Nullable String, int, int, boolean);
method @RequiresPermission(android.Manifest.permission.STATUS_BAR) public void collapsePanels();
method public void expandNotificationsPanel();
method public void sendNotificationFeedback(@Nullable String, @Nullable android.os.Bundle);
method @RequiresPermission(android.Manifest.permission.STATUS_BAR) public void setExpansionDisabledForSimNetworkLock(boolean);
}

View File

@@ -6766,6 +6766,31 @@ public class Notification implements Parcelable
return when != 0 && extras.getBoolean(EXTRA_SHOW_CHRONOMETER);
}
/**
* @return true if the notification has image
*/
public boolean hasImage() {
if (MessagingStyle.class.equals(getNotificationStyle()) && extras != null) {
final Parcelable[] messages = extras.getParcelableArray(EXTRA_MESSAGES);
if (!ArrayUtils.isEmpty(messages)) {
for (MessagingStyle.Message m : MessagingStyle.Message
.getMessagesFromBundleArray(messages)) {
if (m.getDataUri() != null
&& m.getDataMimeType() != null
&& m.getDataMimeType().startsWith("image/")) {
return true;
}
}
}
} else if (hasLargeIcon()) {
return true;
} else if (extras.containsKey(EXTRA_BACKGROUND_IMAGE_URI)) {
return true;
}
return false;
}
/**
* @removed
*/

View File

@@ -27,6 +27,7 @@ import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -285,6 +286,23 @@ public class StatusBarManager {
}
}
/**
* Simulate notification feedback for testing
*
* @hide
*/
@TestApi
public void sendNotificationFeedback(@Nullable String key, @Nullable Bundle feedback) {
try {
final IStatusBarService svc = getService();
if (svc != null) {
svc.onNotificationFeedbackReceived(key, feedback);
}
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
}
/**
* Expand the notifications panel.
*

View File

@@ -20,6 +20,7 @@ import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationChannelGroup;
import android.content.pm.ParceledListSlice;
import android.os.Bundle;
import android.os.UserHandle;
import android.service.notification.NotificationStats;
import android.service.notification.IStatusBarNotificationHolder;
@@ -58,4 +59,5 @@ oneway interface INotificationListener
void onActionClicked(String key, in Notification.Action action, int source);
void onNotificationClicked(String key);
void onAllowedAdjustmentsChanged();
void onNotificationFeedbackReceived(String key, in NotificationRankingUpdate update, in Bundle feedback);
}

View File

@@ -30,6 +30,7 @@ import android.app.admin.DevicePolicyManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
@@ -89,6 +90,11 @@ public abstract class NotificationAssistantService extends NotificationListenerS
public static final String SERVICE_INTERFACE
= "android.service.notification.NotificationAssistantService";
/**
* Data type: int, the feedback rating score provided by user
*/
public static final String FEEDBACK_RATING = "feedback.rating";
/**
* @hide
*/
@@ -271,6 +277,17 @@ public abstract class NotificationAssistantService extends NotificationListenerS
public void onAllowedAdjustmentsChanged() {
}
/**
* Implement this to know when user provides a feedback.
* @param key the notification key
* @param rankingMap The current ranking map that can be used to retrieve ranking information
* for active notifications.
* @param feedback the feedback detail
*/
public void onNotificationFeedbackReceived(@NonNull String key, @NonNull RankingMap rankingMap,
@NonNull Bundle feedback) {
}
/**
* Updates a notification. N.B. this won’t cause
* an existing notification to alert, but might allow a future update to
@@ -455,6 +472,18 @@ public abstract class NotificationAssistantService extends NotificationListenerS
public void onAllowedAdjustmentsChanged() {
mHandler.obtainMessage(MyHandler.MSG_ON_ALLOWED_ADJUSTMENTS_CHANGED).sendToTarget();
}
@Override
public void onNotificationFeedbackReceived(String key, NotificationRankingUpdate update,
Bundle feedback) {
applyUpdateLocked(update);
SomeArgs args = SomeArgs.obtain();
args.arg1 = key;
args.arg2 = getCurrentRanking();
args.arg3 = feedback;
mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATION_FEEDBACK_RECEIVED,
args).sendToTarget();
}
}
private void setAdjustmentIssuer(@Nullable Adjustment adjustment) {
@@ -476,6 +505,7 @@ public abstract class NotificationAssistantService extends NotificationListenerS
public static final int MSG_ON_PANEL_HIDDEN = 10;
public static final int MSG_ON_NOTIFICATION_VISIBILITY_CHANGED = 11;
public static final int MSG_ON_NOTIFICATION_CLICKED = 12;
public static final int MSG_ON_NOTIFICATION_FEEDBACK_RECEIVED = 13;
public MyHandler(Looper looper) {
super(looper, null, false);
@@ -589,6 +619,15 @@ public abstract class NotificationAssistantService extends NotificationListenerS
onNotificationClicked(key);
break;
}
case MSG_ON_NOTIFICATION_FEEDBACK_RECEIVED: {
SomeArgs args = (SomeArgs) msg.obj;
String key = (String) args.arg1;
RankingMap ranking = (RankingMap) args.arg2;
Bundle feedback = (Bundle) args.arg3;
args.recycle();
onNotificationFeedbackReceived(key, ranking, feedback);
break;
}
}
}
}

View File

@@ -43,6 +43,7 @@ import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
@@ -1543,6 +1544,14 @@ public abstract class NotificationListenerService extends Service {
mHandler.obtainMessage(MyHandler.MSG_ON_STATUS_BAR_ICON_BEHAVIOR_CHANGED,
hideSilentStatusIcons).sendToTarget();
}
@Override
public void onNotificationFeedbackReceived(String key, NotificationRankingUpdate update,
Bundle feedback) {
// no-op in the listener
}
}
/**
@@ -1628,6 +1637,7 @@ public abstract class NotificationListenerService extends Service {
private int mSuppressedVisualEffects;
private @NotificationManager.Importance int mImportance;
private CharSequence mImportanceExplanation;
private float mRankingScore;
// System specified group key.
private String mOverrideGroupKey;
// Notification assistant channel override.
@@ -1668,6 +1678,7 @@ public abstract class NotificationListenerService extends Service {
out.writeInt(mSuppressedVisualEffects);
out.writeInt(mImportance);
out.writeCharSequence(mImportanceExplanation);
out.writeFloat(mRankingScore);
out.writeString(mOverrideGroupKey);
out.writeParcelable(mChannel, flags);
out.writeStringList(mOverridePeople);
@@ -1705,6 +1716,7 @@ public abstract class NotificationListenerService extends Service {
mSuppressedVisualEffects = in.readInt();
mImportance = in.readInt();
mImportanceExplanation = in.readCharSequence(); // may be null
mRankingScore = in.readFloat();
mOverrideGroupKey = in.readString(); // may be null
mChannel = in.readParcelable(cl); // may be null
mOverridePeople = in.createStringArrayList();
@@ -1801,6 +1813,17 @@ public abstract class NotificationListenerService extends Service {
return mImportanceExplanation;
}
/**
* Returns the ranking score provided by the {@link NotificationAssistantService} to
* sort the notifications in the shade
*
* @return the ranking score of the notification, range from -1 to 1
* @hide
*/
public float getRankingScore() {
return mRankingScore;
}
/**
* If the system has overridden the group key, then this will be non-null, and this
* key should be used to bundle notifications.

View File

@@ -22,6 +22,7 @@ import android.graphics.Rect;
import android.hardware.biometrics.IBiometricSysuiReceiver;
import android.hardware.biometrics.PromptInfo;
import android.net.Uri;
import android.os.Bundle;
import android.os.UserHandle;
import android.service.notification.StatusBarNotification;
@@ -82,6 +83,7 @@ interface IStatusBarService
void hideCurrentInputMethodForBubbles();
void grantInlineReplyUriPermission(String key, in Uri uri, in UserHandle user, String packageName);
void clearInlineReplyUriPermissions(String key);
void onNotificationFeedbackReceived(String key, in Bundle feedback);
void onGlobalActionsShown();
void onGlobalActionsHidden();

View File

@@ -109,10 +109,10 @@ public class AssistantFeedbackController {
&& newImportance < NotificationManager.IMPORTANCE_DEFAULT) {
return STATUS_SILENCED;
} else if (oldImportance < newImportance
|| ranking.getRankingAdjustment() == ranking.RANKING_PROMOTED) {
|| ranking.getRankingAdjustment() == Ranking.RANKING_PROMOTED) {
return STATUS_PROMOTED;
} else if (oldImportance > newImportance
|| ranking.getRankingAdjustment() == ranking.RANKING_DEMOTED) {
|| ranking.getRankingAdjustment() == Ranking.RANKING_DEMOTED) {
return STATUS_DEMOTED;
} else {
return STATUS_UNCHANGED;

View File

@@ -16,8 +16,14 @@
package com.android.systemui.statusbar.notification.row;
import static android.service.notification.NotificationAssistantService.FEEDBACK_RATING;
import static com.android.systemui.statusbar.notification.AssistantFeedbackController.STATUS_ALERTED;
import static com.android.systemui.statusbar.notification.AssistantFeedbackController.STATUS_DEMOTED;
import static com.android.systemui.statusbar.notification.AssistantFeedbackController.STATUS_PROMOTED;
import static com.android.systemui.statusbar.notification.AssistantFeedbackController.STATUS_SILENCED;
import android.annotation.SuppressLint;
import android.app.Notification;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
@@ -36,20 +42,17 @@ import android.widget.LinearLayout;
import android.widget.TextView;
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.statusbar.NotificationVisibility;
import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
import com.android.systemui.statusbar.notification.AssistantFeedbackController;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
public class FeedbackInfo extends LinearLayout implements NotificationGuts.GutsContent {
private static final String TAG = "FeedbackInfo";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private static final String FEEDBACK_KEY = "feedback_key";
private NotificationGuts mGutsContainer;
private NotificationListenerService.Ranking mRanking;
@@ -146,15 +149,15 @@ public class FeedbackInfo extends LinearLayout implements NotificationGuts.GutsC
sb.append(String.format(
"[DEBUG]: oldImportance=%d, newImportance=%d, ranking=%d\n\n",
mRanking.getChannel().getImportance(), mRanking.getImportance(),
mRanking.getRankingAdjustment()));
mRanking.getRankingScore()));
}
if (status == mFeedbackController.STATUS_ALERTED) {
if (status == STATUS_ALERTED) {
sb.append(mContext.getText(R.string.feedback_alerted));
} else if (status == mFeedbackController.STATUS_SILENCED) {
} else if (status == STATUS_SILENCED) {
sb.append(mContext.getText(R.string.feedback_silenced));
} else if (status == mFeedbackController.STATUS_PROMOTED) {
} else if (status == STATUS_PROMOTED) {
sb.append(mContext.getText(R.string.feedback_promoted));
} else if (status == mFeedbackController.STATUS_DEMOTED) {
} else if (status == STATUS_DEMOTED) {
sb.append(mContext.getText(R.string.feedback_demoted));
}
sb.append(" ");
@@ -182,7 +185,8 @@ public class FeedbackInfo extends LinearLayout implements NotificationGuts.GutsC
private void handleFeedback(boolean positive) {
Bundle feedback = new Bundle();
feedback.putBoolean(FEEDBACK_KEY, positive);
feedback.putInt(FEEDBACK_RATING, positive ? 1 : -1);
sendFeedbackToAssistant(feedback);
}
@@ -191,19 +195,8 @@ public class FeedbackInfo extends LinearLayout implements NotificationGuts.GutsC
return;
}
//TODO(b/154257994): remove this when feedback apis are in place
final int count = mNotificationEntryManager.getActiveNotificationsCount();
final int rank = mEntry.getRanking().getRank();
NotificationVisibility.NotificationLocation location =
NotificationLogger.getNotificationLocation(mEntry);
final NotificationVisibility nv = NotificationVisibility.obtain(
mEntry.getKey(), rank, count, true, location);
Notification.Action action = new Notification.Action.Builder(null, null,
null)
.addExtras(feedback)
.build();
try {
mStatusBarService.onNotificationActionClick(mRanking.getKey(), -1, action, nv, true);
mStatusBarService.onNotificationFeedbackReceived(mRanking.getKey(), feedback);
} catch (RemoteException e) {
if (DEBUG) {
Log.e(TAG, "Failed to send feedback to assistant", e);

View File

@@ -18,6 +18,7 @@ package com.android.server.notification;
import android.app.Notification;
import android.net.Uri;
import android.os.Bundle;
import android.os.UserHandle;
import android.service.notification.NotificationStats;
@@ -88,5 +89,13 @@ public interface NotificationDelegate {
void onNotificationSmartReplySent(String key, int clickedIndex, CharSequence reply,
int notificationLocation, boolean modifiedBeforeSending);
/**
* Notifies a user feedback is provided.
*
* @param key the notification key
* @param feedback the feedback detail
*/
void onNotificationFeedbackReceived(String key, Bundle feedback);
void prepareForPossibleShutdown();
}

View File

@@ -172,7 +172,6 @@ import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.PackageManagerInternal;
import android.content.pm.ParceledListSlice;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.content.pm.ShortcutInfo;
import android.content.pm.ShortcutServiceInternal;
@@ -215,7 +214,6 @@ import android.provider.DeviceConfig;
import android.provider.Settings;
import android.service.notification.Adjustment;
import android.service.notification.Condition;
import android.service.notification.ConditionProviderService;
import android.service.notification.ConversationChannelWrapper;
import android.service.notification.IConditionProvider;
import android.service.notification.INotificationListener;
@@ -383,7 +381,8 @@ public class NotificationManagerService extends SystemService {
Adjustment.KEY_TEXT_REPLIES,
Adjustment.KEY_NOT_CONVERSATION,
Adjustment.KEY_IMPORTANCE,
Adjustment.KEY_RANKING_SCORE};
Adjustment.KEY_RANKING_SCORE
};
static final String[] NON_BLOCKABLE_DEFAULT_ROLES = new String[] {
RoleManager.ROLE_DIALER,
@@ -432,8 +431,6 @@ public class NotificationManagerService extends SystemService {
private static final String SCHEME_TIMEOUT = "timeout";
private static final String EXTRA_KEY = "key";
private static final String FEEDBACK_KEY = "feedback_key";
private static final int NOTIFICATION_INSTANCE_ID_MAX = (1 << 13);
/**
@@ -1020,30 +1017,26 @@ public class NotificationManagerService extends SystemService {
return;
}
final long now = System.currentTimeMillis();
//TODO(b/154257994): remove this when feedback apis are in place
boolean isFeedback = action.getExtras().containsKey(FEEDBACK_KEY);
if (!isFeedback) {
MetricsLogger.action(r.getLogMaker(now)
.setCategory(MetricsEvent.NOTIFICATION_ITEM_ACTION)
.setType(MetricsEvent.TYPE_ACTION)
.setSubtype(actionIndex)
.addTaggedData(MetricsEvent.NOTIFICATION_SHADE_INDEX, nv.rank)
.addTaggedData(MetricsEvent.NOTIFICATION_SHADE_COUNT, nv.count)
.addTaggedData(MetricsEvent.NOTIFICATION_ACTION_IS_SMART,
action.isContextual() ? 1 : 0)
.addTaggedData(
MetricsEvent.NOTIFICATION_SMART_SUGGESTION_ASSISTANT_GENERATED,
generatedByAssistant ? 1 : 0)
.addTaggedData(MetricsEvent.NOTIFICATION_LOCATION,
nv.location.toMetricsEventEnum()));
mNotificationRecordLogger.log(
NotificationRecordLogger.NotificationEvent.fromAction(actionIndex,
generatedByAssistant, action.isContextual()), r);
EventLogTags.writeNotificationActionClicked(key, actionIndex,
r.getLifespanMs(now), r.getFreshnessMs(now), r.getExposureMs(now),
nv.rank, nv.count);
nv.recycle();
}
MetricsLogger.action(r.getLogMaker(now)
.setCategory(MetricsEvent.NOTIFICATION_ITEM_ACTION)
.setType(MetricsEvent.TYPE_ACTION)
.setSubtype(actionIndex)
.addTaggedData(MetricsEvent.NOTIFICATION_SHADE_INDEX, nv.rank)
.addTaggedData(MetricsEvent.NOTIFICATION_SHADE_COUNT, nv.count)
.addTaggedData(MetricsEvent.NOTIFICATION_ACTION_IS_SMART,
action.isContextual() ? 1 : 0)
.addTaggedData(
MetricsEvent.NOTIFICATION_SMART_SUGGESTION_ASSISTANT_GENERATED,
generatedByAssistant ? 1 : 0)
.addTaggedData(MetricsEvent.NOTIFICATION_LOCATION,
nv.location.toMetricsEventEnum()));
mNotificationRecordLogger.log(
NotificationRecordLogger.NotificationEvent.fromAction(actionIndex,
generatedByAssistant, action.isContextual()), r);
EventLogTags.writeNotificationActionClicked(key, actionIndex,
r.getLifespanMs(now), r.getFreshnessMs(now), r.getExposureMs(now),
nv.rank, nv.count);
nv.recycle();
reportUserInteraction(r);
mAssistants.notifyAssistantActionClicked(r, action, generatedByAssistant);
}
@@ -1386,6 +1379,20 @@ public class NotificationManagerService extends SystemService {
}
}
}
@Override
public void onNotificationFeedbackReceived(String key, Bundle feedback) {
exitIdle();
synchronized (mNotificationLock) {
NotificationRecord r = mNotificationsByKey.get(key);
if (r == null) {
if (DBG) Slog.w(TAG, "No notification with key: " + key);
return;
}
mAssistants.notifyAssistantFeedbackReceived(r, feedback);
}
}
};
@VisibleForTesting
@@ -9505,6 +9512,26 @@ public class NotificationManagerService extends SystemService {
});
}
@GuardedBy("mNotificationLock")
void notifyAssistantFeedbackReceived(final NotificationRecord r, Bundle feedback) {
final StatusBarNotification sbn = r.getSbn();
for (final ManagedServiceInfo info : NotificationAssistants.this.getServices()) {
boolean sbnVisible = isVisibleToListener(
sbn, r.getNotificationType(), info)
&& info.isSameUser(r.getUserId());
if (sbnVisible) {
final INotificationListener assistant = (INotificationListener) info.service;
try {
final NotificationRankingUpdate update = makeRankingUpdateLocked(info);
assistant.onNotificationFeedbackReceived(sbn.getKey(), update, feedback);
} catch (RemoteException ex) {
Slog.e(TAG, "unable to notify assistant (feedback): " + assistant, ex);
}
}
}
}
/**
* Notifies the assistant something about the specified notification, only assistant
* that is visible to the notification will be notified.

View File

@@ -1500,6 +1500,18 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D
}
}
@Override
public void onNotificationFeedbackReceived(String key, Bundle feedback) {
enforceStatusBarService();
final long identity = Binder.clearCallingIdentity();
try {
mNotificationDelegate.onNotificationFeedbackReceived(key, feedback);
} finally {
Binder.restoreCallingIdentity(identity);
}
}
@Override
public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
String[] args, ShellCallback callback, ResultReceiver resultReceiver) {