Add onActionClicked in NotificationAssistantService

This is added to report clicks on actions buttons to NAS.

BUG: 119010281
Test: atest SystemUITests
Test: atest RemoteViewsTest
Test: atest NotificationManagerServiceTest
Test: Manual. Tapped on the action (both normal and contextual) and
      observed the log.

Change-Id: I381994737d8c3185d3fabf9b6c481fd01a89a634
diff --git a/core/java/android/app/Notification.aidl b/core/java/android/app/Notification.aidl
index 9d8129c..8a7156e 100644
--- a/core/java/android/app/Notification.aidl
+++ b/core/java/android/app/Notification.aidl
@@ -17,3 +17,4 @@
 package android.app;
 
 parcelable Notification;
+parcelable Notification.Action;
\ No newline at end of file
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 75b56f3..cf43f59 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -5434,6 +5434,8 @@
                             ambient ? resolveAmbientColor() : resolveContrastColor());
                 }
             }
+            button.setIntTag(R.id.action0, R.id.notification_action_index_tag,
+                    mActions.indexOf(action));
             return button;
         }
 
diff --git a/core/java/android/service/notification/INotificationListener.aidl b/core/java/android/service/notification/INotificationListener.aidl
index ab94f43..1ddc099e 100644
--- a/core/java/android/service/notification/INotificationListener.aidl
+++ b/core/java/android/service/notification/INotificationListener.aidl
@@ -16,6 +16,7 @@
 
 package android.service.notification;
 
+import android.app.Notification;
 import android.app.NotificationChannel;
 import android.app.NotificationChannelGroup;
 import android.content.pm.ParceledListSlice;
@@ -50,4 +51,5 @@
     void onNotificationExpansionChanged(String key, boolean userAction, boolean expanded);
     void onNotificationDirectReply(String key);
     void onSuggestedReplySent(String key, in CharSequence reply, int source);
+    void onActionClicked(String key, in Notification.Action action, int source);
 }
diff --git a/core/java/android/service/notification/NotificationAssistantService.java b/core/java/android/service/notification/NotificationAssistantService.java
index 68da83f..c850a4e 100644
--- a/core/java/android/service/notification/NotificationAssistantService.java
+++ b/core/java/android/service/notification/NotificationAssistantService.java
@@ -19,9 +19,11 @@
 import static java.lang.annotation.RetentionPolicy.SOURCE;
 
 import android.annotation.IntDef;
+import android.annotation.Nullable;
 import android.annotation.SdkConstant;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
+import android.app.Notification;
 import android.app.NotificationChannel;
 import android.app.admin.DevicePolicyManager;
 import android.content.ComponentName;
@@ -189,11 +191,20 @@
      * Implement this to know when a suggested reply is sent.
      * @param key the notification key
      * @param reply the reply that is just sent
-     * @param source the source of the reply, e.g. SOURCE_FROM_APP
+     * @param source the source that provided the reply, e.g. SOURCE_FROM_APP
      */
     public void onSuggestedReplySent(String key, CharSequence reply, @Source int source) {}
 
     /**
+     * Implement this to know when an action is clicked.
+     * @param key the notification key
+     * @param action the action that is just clicked
+     * @param source the source that provided the action, e.g. SOURCE_FROM_APP
+     */
+    public void onActionClicked(String key, @Nullable Notification.Action action, int source) {
+    }
+
+    /**
      * Updates a notification.  N.B. this won’t cause
      * an existing notification to alert, but might allow a future update to
      * this notification to alert.
@@ -317,6 +328,15 @@
             args.argi2 = source;
             mHandler.obtainMessage(MyHandler.MSG_ON_SUGGESTED_REPLY_SENT, args).sendToTarget();
         }
+
+        @Override
+        public void onActionClicked(String key, Notification.Action action, int source) {
+            SomeArgs args = SomeArgs.obtain();
+            args.arg1 = key;
+            args.arg2 = action;
+            args.argi2 = source;
+            mHandler.obtainMessage(MyHandler.MSG_ON_ACTION_CLICKED, args).sendToTarget();
+        }
     }
 
     private final class MyHandler extends Handler {
@@ -326,6 +346,7 @@
         public static final int MSG_ON_NOTIFICATION_EXPANSION_CHANGED = 4;
         public static final int MSG_ON_NOTIFICATION_DIRECT_REPLY_SENT = 5;
         public static final int MSG_ON_SUGGESTED_REPLY_SENT = 6;
+        public static final int MSG_ON_ACTION_CLICKED = 7;
 
         public MyHandler(Looper looper) {
             super(looper, null, false);
@@ -395,6 +416,15 @@
                     onSuggestedReplySent(key, reply, source);
                     break;
                 }
+                case MSG_ON_ACTION_CLICKED: {
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    String key = (String) args.arg1;
+                    Notification.Action action = (Notification.Action) args.arg2;
+                    int source = args.argi2;
+                    args.recycle();
+                    onActionClicked(key, action, source);
+                    break;
+                }
             }
         }
     }
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index 756a7c6..1fe97b7 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -1382,6 +1382,11 @@
         }
 
         @Override
+        public void onActionClicked(String key, Notification.Action action, int source) {
+            // no-op in the listener
+        }
+
+        @Override
         public void onNotificationChannelModification(String pkgName, UserHandle user,
                 NotificationChannel channel,
                 @ChannelOrGroupModificationTypes int modificationType) {
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index c0979fe..7b39efe 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -161,6 +161,7 @@
     private static final int LAYOUT_PARAM_ACTION_TAG = 19;
     private static final int OVERRIDE_TEXT_COLORS_TAG = 20;
     private static final int SET_RIPPLE_DRAWABLE_COLOR_TAG = 21;
+    private static final int SET_INT_TAG_TAG = 22;
 
     /**
      * Application that hosts the remote views.
@@ -274,6 +275,15 @@
     }
 
     /**
+     * Sets an integer tag to the view.
+     *
+     * @hide
+     */
+    public void setIntTag(int viewId, int key, int tag) {
+        addAction(new SetIntTagAction(viewId, key, tag));
+    }
+
+    /**
      * Set that it is disallowed to reapply another remoteview with the same layout as this view.
      * This should be done if an action is destroying the view tree of the base layout.
      *
@@ -2122,6 +2132,43 @@
         }
     }
 
+    private class SetIntTagAction extends Action {
+        private final int mViewId;
+        private final int mKey;
+        private final int mTag;
+
+        SetIntTagAction(int viewId, int key, int tag) {
+            mViewId = viewId;
+            mKey = key;
+            mTag = tag;
+        }
+
+        SetIntTagAction(Parcel parcel) {
+            mViewId = parcel.readInt();
+            mKey = parcel.readInt();
+            mTag = parcel.readInt();
+        }
+
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeInt(mViewId);
+            dest.writeInt(mKey);
+            dest.writeInt(mTag);
+        }
+
+        @Override
+        public void apply(View root, ViewGroup rootParent, OnClickHandler handler) {
+            final View target = root.findViewById(mViewId);
+            if (target == null) return;
+
+            target.setTagInternal(mKey, mTag);
+        }
+
+        @Override
+        public int getActionTag() {
+            return SET_INT_TAG_TAG;
+        }
+    }
+
     /**
      * Create a new RemoteViews object that will display the views contained
      * in the specified layout file.
@@ -2326,6 +2373,8 @@
                 return new OverrideTextColorsAction(parcel);
             case SET_RIPPLE_DRAWABLE_COLOR_TAG:
                 return new SetRippleDrawableColor(parcel);
+            case SET_INT_TAG_TAG:
+                return new SetIntTagAction(parcel);
             default:
                 throw new ActionException("Tag " + tag + " not found");
         }
diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
index 69ba070..b7ffb57 100644
--- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
@@ -16,6 +16,7 @@
 
 package com.android.internal.statusbar;
 
+import android.app.Notification;
 import android.content.ComponentName;
 import android.graphics.Rect;
 import android.os.Bundle;
@@ -55,7 +56,7 @@
     // Mark current notifications as "seen" and stop ringing, vibrating, blinking.
     void clearNotificationEffects();
     void onNotificationClick(String key, in NotificationVisibility nv);
-    void onNotificationActionClick(String key, int actionIndex, in NotificationVisibility nv);
+    void onNotificationActionClick(String key, int actionIndex, in Notification.Action action, in NotificationVisibility nv, boolean generatedByAssistant);
     void onNotificationError(String pkg, String tag, int id,
             int uid, int initialPid, String message, int userId);
     void onClearAllNotifications(int userId);
diff --git a/core/res/res/values/ids.xml b/core/res/res/values/ids.xml
index 64e5bc0..bbe3ff9 100644
--- a/core/res/res/values/ids.xml
+++ b/core/res/res/values/ids.xml
@@ -190,4 +190,7 @@
 
   <!-- A tag used to save the view added to a transition overlay -->
   <item type="id" name="transition_overlay_view_tag" />
+
+  <!-- A tag used to save the notification action object -->
+  <item type="id" name="notification_action_index_tag" />
 </resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 9264f90..e251e27 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2683,6 +2683,7 @@
   <java-symbol type="id" name="smart_reply_container" />
   <java-symbol type="id" name="remote_input_tag" />
   <java-symbol type="id" name="pending_intent_tag" />
+  <java-symbol type="id" name="notification_action_index_tag" />
 
   <java-symbol type="attr" name="seekBarDialogPreferenceStyle" />
   <java-symbol type="string" name="ext_media_status_removed" />
diff --git a/core/tests/coretests/src/android/widget/RemoteViewsTest.java b/core/tests/coretests/src/android/widget/RemoteViewsTest.java
index 4456122..36792bb 100644
--- a/core/tests/coretests/src/android/widget/RemoteViewsTest.java
+++ b/core/tests/coretests/src/android/widget/RemoteViewsTest.java
@@ -467,6 +467,21 @@
         assertArrayEquals(container.mSharedViewNames, new String[] {"e0", "e1", "e2"});
     }
 
+    @Test
+    public void setIntTag() {
+        RemoteViews views = new RemoteViews(mPackage, R.layout.remote_views_test);
+        int index = 10;
+        views.setIntTag(
+                R.id.layout, com.android.internal.R.id.notification_action_index_tag, index);
+
+        RemoteViews recovered = parcelAndRecreate(views);
+        RemoteViews cloned = new RemoteViews(recovered);
+        View inflated = cloned.apply(mContext, mContainer);
+
+        assertEquals(
+                index, inflated.getTag(com.android.internal.R.id.notification_action_index_tag));
+    }
+
     private class WidgetContainer extends AppWidgetHostView {
         int[] mSharedViewIds;
         String[] mSharedViewNames;