Merge "Add back off timer configs as carrier config" into qt-qpr1-dev
diff --git a/core/java/android/service/contentsuggestions/ContentSuggestionsService.java b/core/java/android/service/contentsuggestions/ContentSuggestionsService.java
index 4bcd39f..306b483 100644
--- a/core/java/android/service/contentsuggestions/ContentSuggestionsService.java
+++ b/core/java/android/service/contentsuggestions/ContentSuggestionsService.java
@@ -64,6 +64,10 @@
         @Override
         public void provideContextImage(int taskId, GraphicBuffer contextImage,
                 int colorSpaceId, Bundle imageContextRequestExtras) {
+            if (imageContextRequestExtras.containsKey(ContentSuggestionsManager.EXTRA_BITMAP)
+                    && contextImage != null) {
+                throw new IllegalArgumentException("Two bitmaps provided; expected one.");
+            }
 
             Bitmap wrappedBuffer = null;
             if (imageContextRequestExtras.containsKey(ContentSuggestionsManager.EXTRA_BITMAP)) {
diff --git a/core/java/com/android/internal/app/AbstractResolverComparator.java b/core/java/com/android/internal/app/AbstractResolverComparator.java
index 9ac979b..3a6a71d 100644
--- a/core/java/com/android/internal/app/AbstractResolverComparator.java
+++ b/core/java/com/android/internal/app/AbstractResolverComparator.java
@@ -30,6 +30,7 @@
 
 import com.android.internal.app.ResolverActivity.ResolvedComponentInfo;
 
+import java.text.Collator;
 import java.util.ArrayList;
 import java.util.Comparator;
 import java.util.List;
@@ -37,7 +38,7 @@
 /**
  * Used to sort resolved activities in {@link ResolverListController}.
  */
-abstract class AbstractResolverComparator implements Comparator<ResolvedComponentInfo> {
+public abstract class AbstractResolverComparator implements Comparator<ResolvedComponentInfo> {
 
     private static final int NUM_OF_TOP_ANNOTATIONS_TO_USE = 3;
     private static final boolean DEBUG = false;
@@ -62,6 +63,8 @@
     // predicting ranking scores.
     private static final int WATCHDOG_TIMEOUT_MILLIS = 500;
 
+    private final Comparator<ResolveInfo> mAzComparator;
+
     protected final Handler mHandler = new Handler(Looper.getMainLooper()) {
         public void handleMessage(Message msg) {
             switch (msg.what) {
@@ -90,7 +93,7 @@
         }
     };
 
-    AbstractResolverComparator(Context context, Intent intent) {
+    public AbstractResolverComparator(Context context, Intent intent) {
         String scheme = intent.getScheme();
         mHttp = "http".equals(scheme) || "https".equals(scheme);
         mContentType = intent.getType();
@@ -100,6 +103,7 @@
         mDefaultBrowserPackageName = mHttp
                 ? mPm.getDefaultBrowserPackageNameAsUser(UserHandle.myUserId())
                 : null;
+        mAzComparator = new AzInfoComparator(context);
     }
 
     // get annotations of content from intent.
@@ -168,6 +172,20 @@
                 return lhsSpecific ? -1 : 1;
             }
         }
+
+        final boolean lPinned = lhsp.isPinned();
+        final boolean rPinned = rhsp.isPinned();
+
+        // Pinned items always receive priority.
+        if (lPinned && !rPinned) {
+            return -1;
+        } else if (!lPinned && rPinned) {
+            return 1;
+        } else if (lPinned && rPinned) {
+            // If both items are pinned, resolve the tie alphabetically.
+            return mAzComparator.compare(lhsp.getResolveInfoAt(0), rhsp.getResolveInfoAt(0));
+        }
+
         return compare(lhs, rhs);
     }
 
@@ -258,4 +276,25 @@
         }
         return false;
     }
+
+    /**
+     * Sort intents alphabetically based on package name.
+     */
+    class AzInfoComparator implements Comparator<ResolveInfo> {
+        Collator mCollator;
+        AzInfoComparator(Context context) {
+            mCollator = Collator.getInstance(context.getResources().getConfiguration().locale);
+        }
+
+        @Override
+        public int compare(ResolveInfo lhsp, ResolveInfo rhsp) {
+            if (lhsp == null) {
+                return -1;
+            } else if (rhsp == null) {
+                return 1;
+            }
+            return mCollator.compare(lhsp.activityInfo.packageName, rhsp.activityInfo.packageName);
+        }
+    }
+
 }
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index cae1f38..216b533 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -43,6 +43,7 @@
 import android.content.IntentSender;
 import android.content.IntentSender.SendIntentException;
 import android.content.ServiceConnection;
+import android.content.SharedPreferences;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.LabeledIntent;
@@ -68,6 +69,7 @@
 import android.net.Uri;
 import android.os.AsyncTask;
 import android.os.Bundle;
+import android.os.Environment;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Message;
@@ -77,6 +79,7 @@
 import android.os.ResultReceiver;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.os.storage.StorageManager;
 import android.provider.DeviceConfig;
 import android.provider.DocumentsContract;
 import android.provider.Downloads;
@@ -119,6 +122,7 @@
 
 import com.google.android.collect.Lists;
 
+import java.io.File;
 import java.io.IOException;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -252,6 +256,9 @@
     private static final int MAX_EXTRA_INITIAL_INTENTS = 2;
     private static final int MAX_EXTRA_CHOOSER_TARGETS = 2;
 
+    private SharedPreferences mPinnedSharedPrefs;
+    private static final String PINNED_SHARED_PREFS_NAME = "chooser_pin_settings";
+
     private boolean mListViewDataChanged = false;
 
     @Retention(SOURCE)
@@ -596,6 +603,8 @@
                 Intent.EXTRA_CHOOSER_REFINEMENT_INTENT_SENDER);
         setSafeForwardingMode(true);
 
+        mPinnedSharedPrefs = getPinnedSharedPrefs(this);
+
         pa = intent.getParcelableArrayExtra(Intent.EXTRA_EXCLUDE_COMPONENTS);
         if (pa != null) {
             ComponentName[] names = new ComponentName[pa.length];
@@ -727,6 +736,23 @@
         }
     }
 
+
+    static SharedPreferences getPinnedSharedPrefs(Context context) {
+        // The code below is because in the android:ui process, no one can hear you scream.
+        // The package info in the context isn't initialized in the way it is for normal apps,
+        // so the standard, name-based context.getSharedPreferences doesn't work. Instead, we
+        // build the path manually below using the same policy that appears in ContextImpl.
+        // This fails silently under the hood if there's a problem, so if we find ourselves in
+        // the case where we don't have access to credential encrypted storage we just won't
+        // have our pinned target info.
+        final File prefsFile = new File(new File(
+                Environment.getDataUserCePackageDirectory(StorageManager.UUID_PRIVATE_INTERNAL,
+                        context.getUserId(), context.getPackageName()),
+                "shared_prefs"),
+                PINNED_SHARED_PREFS_NAME + ".xml");
+        return context.getSharedPreferences(prefsFile, MODE_PRIVATE);
+    }
+
     /**
      * Returns true if app prediction service is defined and the component exists on device.
      */
@@ -1273,9 +1299,10 @@
         }
 
         ComponentName name = ri.activityInfo.getComponentName();
+        boolean pinned = mPinnedSharedPrefs.getBoolean(name.flattenToString(), false);
         ResolverTargetActionsDialogFragment f =
                 new ResolverTargetActionsDialogFragment(ri.loadLabel(getPackageManager()),
-                        name);
+                    name, pinned);
         f.show(getFragmentManager(), TARGET_DETAILS_FRAGMENT_TAG);
     }
 
@@ -1952,6 +1979,12 @@
             }
             return false;
         }
+
+        @Override
+        public boolean isComponentPinned(ComponentName name) {
+            return mPinnedSharedPrefs.getBoolean(name.flattenToString(), false);
+        }
+
     }
 
     @Override
@@ -2085,6 +2118,10 @@
         public boolean isSuspended() {
             return false;
         }
+
+        public boolean isPinned() {
+            return false;
+        }
     }
 
     final class PlaceHolderTargetInfo extends NotSelectableTargetInfo {
@@ -2173,6 +2210,10 @@
             return mIsSuspended;
         }
 
+        public boolean isPinned() {
+            return mSourceInfo != null && mSourceInfo.isPinned();
+        }
+
         /**
          * Since ShortcutInfos are returned by ShortcutManager, we can cache the shortcuts and skip
          * the call to LauncherApps#getShortcuts(ShortcutQuery).
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index 068056f..f82c28e 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -1413,6 +1413,7 @@
         private final Intent mResolvedIntent;
         private final List<Intent> mSourceIntents = new ArrayList<>();
         private boolean mIsSuspended;
+        private boolean mPinned = false;
 
         public DisplayResolveInfo(Intent originalIntent, ResolveInfo pri, CharSequence pLabel,
                 CharSequence pInfo, Intent pOrigIntent) {
@@ -1516,6 +1517,15 @@
         public boolean isSuspended() {
             return mIsSuspended;
         }
+
+        @Override
+        public boolean isPinned() {
+            return mPinned;
+        }
+
+        public void setPinned(boolean pinned) {
+            mPinned = pinned;
+        }
     }
 
     List<DisplayResolveInfo> getDisplayList() {
@@ -1616,6 +1626,11 @@
           * @return true if this target can be selected by the user
           */
         boolean isSuspended();
+
+        /**
+         * @return true if this target should be pinned to the front by the request of the user
+         */
+        boolean isPinned();
     }
 
     public class ResolveListAdapter extends BaseAdapter {
@@ -1922,6 +1937,10 @@
             final Intent replaceIntent = getReplacementIntent(add.activityInfo, intent);
             final DisplayResolveInfo dri = new DisplayResolveInfo(intent, add, roLabel,
                     extraInfo, replaceIntent);
+            dri.setPinned(rci.isPinned());
+            if (rci.isPinned()) {
+                Log.i(TAG, "Pinned item: " + rci.name);
+            }
             addResolveInfo(dri);
             if (replaceIntent == intent) {
                 // Only add alternates if we didn't get a specific replacement from
@@ -2090,6 +2109,7 @@
         public final ComponentName name;
         private final List<Intent> mIntents = new ArrayList<>();
         private final List<ResolveInfo> mResolveInfos = new ArrayList<>();
+        private boolean mPinned;
 
         public ResolvedComponentInfo(ComponentName name, Intent intent, ResolveInfo info) {
             this.name = name;
@@ -2130,6 +2150,15 @@
             }
             return -1;
         }
+
+        public boolean isPinned() {
+            return mPinned;
+        }
+
+        public void setPinned(boolean pinned) {
+            mPinned = pinned;
+        }
+
     }
 
     static class ViewHolder {
diff --git a/core/java/com/android/internal/app/ResolverListController.java b/core/java/com/android/internal/app/ResolverListController.java
index 5f92cdd..7efd5e1 100644
--- a/core/java/com/android/internal/app/ResolverListController.java
+++ b/core/java/com/android/internal/app/ResolverListController.java
@@ -156,11 +156,22 @@
                         newInfo.activityInfo.packageName, newInfo.activityInfo.name);
                 final ResolverActivity.ResolvedComponentInfo rci =
                         new ResolverActivity.ResolvedComponentInfo(name, intent, newInfo);
+                rci.setPinned(isComponentPinned(name));
                 into.add(rci);
             }
         }
     }
 
+
+    /**
+     * Whether this component is pinned by the user. Always false for resolver; overridden in
+     * Chooser.
+     */
+    public boolean isComponentPinned(ComponentName name) {
+        return false;
+    }
+
+
     // Filter out any activities that the launched uid does not have permission for.
     // To preserve the inputList, optionally will return the original list if any modification has
     // been made.
diff --git a/core/java/com/android/internal/app/ResolverTargetActionsDialogFragment.java b/core/java/com/android/internal/app/ResolverTargetActionsDialogFragment.java
index a49240c..df91c4a 100644
--- a/core/java/com/android/internal/app/ResolverTargetActionsDialogFragment.java
+++ b/core/java/com/android/internal/app/ResolverTargetActionsDialogFragment.java
@@ -23,6 +23,7 @@
 import android.content.ComponentName;
 import android.content.DialogInterface;
 import android.content.Intent;
+import android.content.SharedPreferences;
 import android.net.Uri;
 import android.os.Bundle;
 import android.provider.Settings;
@@ -36,26 +37,33 @@
         implements DialogInterface.OnClickListener {
     private static final String NAME_KEY = "componentName";
     private static final String TITLE_KEY = "title";
+    private static final String PINNED_KEY = "pinned";
 
     // Sync with R.array.resolver_target_actions_* resources
-    private static final int APP_INFO_INDEX = 0;
+    private static final int TOGGLE_PIN_INDEX = 0;
+    private static final int APP_INFO_INDEX = 1;
 
     public ResolverTargetActionsDialogFragment() {
     }
 
-    public ResolverTargetActionsDialogFragment(CharSequence title, ComponentName name) {
+    public ResolverTargetActionsDialogFragment(CharSequence title, ComponentName name,
+            boolean pinned) {
         Bundle args = new Bundle();
         args.putCharSequence(TITLE_KEY, title);
         args.putParcelable(NAME_KEY, name);
+        args.putBoolean(PINNED_KEY, pinned);
         setArguments(args);
     }
 
     @Override
     public Dialog onCreateDialog(Bundle savedInstanceState) {
         final Bundle args = getArguments();
+        final int itemRes = args.getBoolean(PINNED_KEY, false)
+                ? R.array.resolver_target_actions_unpin
+                : R.array.resolver_target_actions_pin;
         return new Builder(getContext())
                 .setCancelable(true)
-                .setItems(R.array.resolver_target_actions, this)
+                .setItems(itemRes, this)
                 .setTitle(args.getCharSequence(TITLE_KEY))
                 .create();
     }
@@ -65,6 +73,19 @@
         final Bundle args = getArguments();
         ComponentName name = args.getParcelable(NAME_KEY);
         switch (which) {
+            case TOGGLE_PIN_INDEX:
+                SharedPreferences sp = ChooserActivity.getPinnedSharedPrefs(getContext());
+                final String key = name.flattenToString();
+                boolean currentVal = sp.getBoolean(name.flattenToString(), false);
+                if (currentVal) {
+                    sp.edit().remove(key).apply();
+                } else {
+                    sp.edit().putBoolean(key, true).apply();
+                }
+
+                // Force the chooser to requery and resort things
+                getActivity().recreate();
+                break;
             case APP_INFO_INDEX:
                 Intent in = new Intent().setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
                         .setData(Uri.fromParts("package", name.getPackageName(), null))
diff --git a/core/res/res/values/arrays.xml b/core/res/res/values/arrays.xml
index f058985..dca9c72 100644
--- a/core/res/res/values/arrays.xml
+++ b/core/res/res/values/arrays.xml
@@ -175,7 +175,15 @@
     </array>
 
     <!-- Used in ResolverTargetActionsDialogFragment -->
-    <string-array name="resolver_target_actions">
+    
+    <!-- Used in ResolverTargetActionsDialogFragment -->
+    <string-array name="resolver_target_actions_pin">
+        <item>@string/pin_target</item>
+        <item>@string/app_info</item>
+    </string-array>
+
+    <string-array name="resolver_target_actions_unpin">
+        <item>@string/unpin_target</item>
         <item>@string/app_info</item>
     </string-array>
 
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 7a63122..045f4bb 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -5072,6 +5072,10 @@
     <string name="usb_mtp_launch_notification_description">Tap to view files</string>
 
     <!-- Resolver target actions strings -->
+    <!-- Pin this app to the top of the Sharesheet app list. [CHAR LIMIT=60]-->
+    <string name="pin_target">Pin</string>
+    <!-- Un-pin this app in the Sharesheet, so that it is sorted normally. [CHAR LIMIT=60]-->
+    <string name="unpin_target">Unpin</string>
     <!-- View application info for a target. -->
     <string name="app_info">App info</string>
 
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 8e52301..8206596 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3047,7 +3047,8 @@
   <java-symbol type="color" name="notification_material_background_color" />
 
   <!-- Resolver target actions -->
-  <java-symbol type="array" name="resolver_target_actions" />
+  <java-symbol type="array" name="resolver_target_actions_pin" />
+  <java-symbol type="array" name="resolver_target_actions_unpin" />
 
   <java-symbol type="array" name="non_removable_euicc_slots" />
 
diff --git a/core/tests/coretests/src/com/android/internal/app/AbstractResolverComparatorTest.java b/core/tests/coretests/src/com/android/internal/app/AbstractResolverComparatorTest.java
new file mode 100644
index 0000000..36dd3e4
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/app/AbstractResolverComparatorTest.java
@@ -0,0 +1,105 @@
+/*
+ * 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.internal.app;
+
+import static junit.framework.Assert.assertEquals;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ResolveInfo;
+import android.os.Message;
+
+import androidx.test.InstrumentationRegistry;
+
+import org.junit.Test;
+
+import java.util.List;
+
+public class AbstractResolverComparatorTest {
+
+    @Test
+    public void testPinned() {
+        ResolverActivity.ResolvedComponentInfo r1 = new ResolverActivity.ResolvedComponentInfo(
+                new ComponentName("package", "class"), new Intent(), new ResolveInfo()
+        );
+        r1.setPinned(true);
+
+        ResolverActivity.ResolvedComponentInfo r2 = new ResolverActivity.ResolvedComponentInfo(
+                new ComponentName("zackage", "zlass"), new Intent(), new ResolveInfo()
+        );
+
+        Context context = InstrumentationRegistry.getTargetContext();
+        AbstractResolverComparator comparator = getTestComparator(context);
+
+        assertEquals("Pinned ranks over unpinned", -1, comparator.compare(r1, r2));
+        assertEquals("Unpinned ranks under pinned", 1, comparator.compare(r2, r1));
+    }
+
+
+    @Test
+    public void testBothPinned() {
+        ResolveInfo pmInfo1 = new ResolveInfo();
+        pmInfo1.activityInfo = new ActivityInfo();
+        pmInfo1.activityInfo.packageName = "aaa";
+
+        ResolverActivity.ResolvedComponentInfo r1 = new ResolverActivity.ResolvedComponentInfo(
+                new ComponentName("package", "class"), new Intent(), pmInfo1);
+        r1.setPinned(true);
+
+        ResolveInfo pmInfo2 = new ResolveInfo();
+        pmInfo2.activityInfo = new ActivityInfo();
+        pmInfo2.activityInfo.packageName = "zzz";
+        ResolverActivity.ResolvedComponentInfo r2 = new ResolverActivity.ResolvedComponentInfo(
+                new ComponentName("zackage", "zlass"), new Intent(), pmInfo2);
+        r2.setPinned(true);
+
+        Context context = InstrumentationRegistry.getTargetContext();
+        AbstractResolverComparator comparator = getTestComparator(context);
+
+        assertEquals("Both pinned should rank alphabetically", -1, comparator.compare(r1, r2));
+    }
+
+    private AbstractResolverComparator getTestComparator(Context context) {
+        Intent intent = new Intent();
+
+        AbstractResolverComparator testComparator =
+                new AbstractResolverComparator(context, intent) {
+
+            @Override
+            int compare(ResolveInfo lhs, ResolveInfo rhs) {
+                // Used for testing pinning, so we should never get here --- the overrides should
+                // determine the result instead.
+                return 1;
+            }
+
+            @Override
+            void doCompute(List<ResolverActivity.ResolvedComponentInfo> targets) {}
+
+            @Override
+            float getScore(ComponentName name) {
+                return 0;
+            }
+
+            @Override
+            void handleResultMessage(Message message) {}
+        };
+        return testComparator;
+    }
+
+}
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 43fec6b..862abfd 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -369,6 +369,10 @@
         <receiver android:name=".screenshot.GlobalScreenshot$DeleteScreenshotReceiver"
             android:exported="false" />
 
+        <!-- Callback for invoking a smart action from the screenshot notification. -->
+        <receiver android:name=".screenshot.GlobalScreenshot$SmartActionsReceiver"
+                  android:exported="false"/>
+
         <!-- started from UsbDeviceSettingsManager -->
         <activity android:name=".usb.UsbConfirmActivity"
             android:exported="true"
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index ca7cd0d..ace24a3 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -44,6 +44,7 @@
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
+import com.android.settingslib.utils.ThreadUtils;
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
 import com.android.systemui.SystemUIFactory;
@@ -615,6 +616,15 @@
                 StatsLog.write(StatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED,
                     StatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED__RESULT__SUCCESS);
                 mLockPatternUtils.reportSuccessfulPasswordAttempt(userId);
+                // Force a garbage collection in an attempt to erase any lockscreen password left in
+                // memory. Do it asynchronously with a 5-sec delay to avoid making the keyguard
+                // dismiss animation janky.
+                ThreadUtils.postOnBackgroundThread(() -> {
+                    try {
+                        Thread.sleep(5000);
+                    } catch (InterruptedException ignored) { }
+                    Runtime.getRuntime().gc();
+                });
             } else {
                 StatsLog.write(StatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED,
                     StatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED__RESULT__FAILURE);
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
index 4fc6a36..2531b60 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
@@ -50,6 +50,7 @@
 import com.android.systemui.statusbar.policy.KeyguardMonitor;
 import com.android.systemui.volume.VolumeDialogComponent;
 
+import java.util.concurrent.Executor;
 import java.util.function.Consumer;
 
 import dagger.Module;
@@ -116,7 +117,9 @@
      * This method is overridden in vendor specific implementation of Sys UI.
      */
     public ScreenshotNotificationSmartActionsProvider
-            createScreenshotNotificationSmartActionsProvider() {
+            createScreenshotNotificationSmartActionsProvider(Context context,
+            Executor executor,
+            Handler uiHandler) {
         return new ScreenshotNotificationSmartActionsProvider();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
index f7a26a8..da2692e 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
@@ -62,6 +62,7 @@
 import android.media.MediaActionSound;
 import android.net.Uri;
 import android.os.AsyncTask;
+import android.os.Bundle;
 import android.os.Environment;
 import android.os.Handler;
 import android.os.PowerManager;
@@ -103,9 +104,12 @@
 import java.io.OutputStream;
 import java.text.DateFormat;
 import java.text.SimpleDateFormat;
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Date;
 import java.util.List;
+import java.util.Random;
+import java.util.UUID;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeUnit;
@@ -143,6 +147,7 @@
     private static final String TAG = "SaveImageInBackgroundTask";
 
     private static final String SCREENSHOT_FILE_NAME_TEMPLATE = "Screenshot_%s.png";
+    private static final String SCREENSHOT_ID_TEMPLATE = "Screenshot_%s";
     private static final String SCREENSHOT_SHARE_SUBJECT_TEMPLATE = "Screenshot (%s)";
 
     private final SaveImageInBackgroundData mParams;
@@ -153,8 +158,10 @@
     private final BigPictureStyle mNotificationStyle;
     private final int mImageWidth;
     private final int mImageHeight;
-    private final Handler mHandler;
     private final ScreenshotNotificationSmartActionsProvider mSmartActionsProvider;
+    private final String mScreenshotId;
+    private final boolean mSmartActionsEnabled;
+    private final Random mRandom = new Random();
 
     SaveImageInBackgroundTask(Context context, SaveImageInBackgroundData data,
             NotificationManager nManager) {
@@ -165,11 +172,20 @@
         mImageTime = System.currentTimeMillis();
         String imageDate = new SimpleDateFormat("yyyyMMdd-HHmmss").format(new Date(mImageTime));
         mImageFileName = String.format(SCREENSHOT_FILE_NAME_TEMPLATE, imageDate);
+        mScreenshotId = String.format(SCREENSHOT_ID_TEMPLATE, UUID.randomUUID());
 
         // Initialize screenshot notification smart actions provider.
-        mHandler = new Handler();
-        mSmartActionsProvider =
-                SystemUIFactory.getInstance().createScreenshotNotificationSmartActionsProvider();
+        mSmartActionsEnabled = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
+                SystemUiDeviceConfigFlags.ENABLE_SCREENSHOT_NOTIFICATION_SMART_ACTIONS, false);
+        if (mSmartActionsEnabled) {
+            mSmartActionsProvider =
+                    SystemUIFactory.getInstance()
+                            .createScreenshotNotificationSmartActionsProvider(
+                                    context, THREAD_POOL_EXECUTOR, new Handler());
+        } else {
+            // If smart actions is not enabled use empty implementation.
+            mSmartActionsProvider = new ScreenshotNotificationSmartActionsProvider();
+        }
 
         // Create the large notification icon
         mImageWidth = data.image.getWidth();
@@ -244,6 +260,38 @@
         mNotificationStyle.bigLargeIcon((Bitmap) null);
     }
 
+    private List<Notification.Action> buildSmartActions(
+            List<Notification.Action> actions, Context context) {
+        List<Notification.Action> broadcastActions = new ArrayList<>();
+        for (Notification.Action action : actions) {
+            // Proxy smart actions through {@link GlobalScreenshot.SmartActionsReceiver}
+            // for logging smart actions.
+            Bundle extras = action.getExtras();
+            String actionType = extras.getString(
+                    ScreenshotNotificationSmartActionsProvider.ACTION_TYPE,
+                    ScreenshotNotificationSmartActionsProvider.DEFAULT_ACTION_TYPE);
+            Intent intent = new Intent(context,
+                    GlobalScreenshot.SmartActionsReceiver.class).putExtra(
+                    GlobalScreenshot.EXTRA_ACTION_INTENT, action.actionIntent);
+            addIntentExtras(mScreenshotId, intent, actionType, mSmartActionsEnabled);
+            PendingIntent broadcastIntent = PendingIntent.getBroadcast(context,
+                    mRandom.nextInt(),
+                    intent,
+                    PendingIntent.FLAG_CANCEL_CURRENT);
+            broadcastActions.add(new Notification.Action.Builder(action.getIcon(), action.title,
+                    broadcastIntent).setContextual(true).addExtras(extras).build());
+        }
+        return broadcastActions;
+    }
+
+    private static void addIntentExtras(String screenshotId, Intent intent, String actionType,
+            boolean smartActionsEnabled) {
+        intent
+            .putExtra(GlobalScreenshot.EXTRA_ACTION_TYPE, actionType)
+            .putExtra(GlobalScreenshot.EXTRA_ID, screenshotId)
+            .putExtra(GlobalScreenshot.EXTRA_SMART_ACTIONS_ENABLED, smartActionsEnabled);
+    }
+
     private int getUserHandleOfForegroundApplication(Context context) {
         // This logic matches
         // com.android.systemui.statusbar.phone.PhoneStatusBarPolicy#updateManagedProfile
@@ -287,15 +335,13 @@
 
         Context context = mParams.context;
         Bitmap image = mParams.image;
-        boolean smartActionsEnabled = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
-                SystemUiDeviceConfigFlags.ENABLE_SCREENSHOT_NOTIFICATION_SMART_ACTIONS, false);
-        CompletableFuture<List<Notification.Action>> smartActionsFuture =
-                GlobalScreenshot.getSmartActionsFuture(
-                        context, image, mSmartActionsProvider, mHandler, smartActionsEnabled,
-                        isManagedProfile(context));
         Resources r = context.getResources();
 
         try {
+            CompletableFuture<List<Notification.Action>> smartActionsFuture =
+                    GlobalScreenshot.getSmartActionsFuture(mScreenshotId, image,
+                            mSmartActionsProvider, mSmartActionsEnabled, isManagedProfile(context));
+
             // Save the screenshot to the MediaStore
             final MediaStore.PendingParams params = new MediaStore.PendingParams(
                     MediaStore.Images.Media.EXTERNAL_CONTENT_URI, mImageFileName, "image/png");
@@ -318,94 +364,11 @@
                 IoUtils.closeQuietly(session);
             }
 
-            // Note: Both the share and edit actions are proxied through ActionProxyReceiver in
-            // order to do some common work like dismissing the keyguard and sending
-            // closeSystemWindows
-
-            // Create a share intent, this will always go through the chooser activity first which
-            // should not trigger auto-enter PiP
-            String subjectDate = DateFormat.getDateTimeInstance().format(new Date(mImageTime));
-            String subject = String.format(SCREENSHOT_SHARE_SUBJECT_TEMPLATE, subjectDate);
-            Intent sharingIntent = new Intent(Intent.ACTION_SEND);
-            sharingIntent.setType("image/png");
-            sharingIntent.putExtra(Intent.EXTRA_STREAM, uri);
-            // Include URI in ClipData also, so that grantPermission picks it up.
-            // We don't use setData here because some apps interpret this as "to:".
-            ClipData clipdata = new ClipData(new ClipDescription("content",
-                    new String[]{ClipDescription.MIMETYPE_TEXT_PLAIN}),
-                    new ClipData.Item(uri));
-            sharingIntent.setClipData(clipdata);
-            sharingIntent.putExtra(Intent.EXTRA_SUBJECT, subject);
-            sharingIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
-
-            PendingIntent chooserAction = PendingIntent.getBroadcast(context, 0,
-                    new Intent(context, GlobalScreenshot.TargetChosenReceiver.class),
-                    PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT);
-            Intent sharingChooserIntent = Intent.createChooser(sharingIntent, null,
-                    chooserAction.getIntentSender())
-                    .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK)
-                    .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
-
-            // Create a share action for the notification
-            PendingIntent shareAction = PendingIntent.getBroadcastAsUser(context, 0,
-                    new Intent(context, GlobalScreenshot.ActionProxyReceiver.class)
-                            .putExtra(EXTRA_ACTION_INTENT, sharingChooserIntent)
-                            .putExtra(EXTRA_DISALLOW_ENTER_PIP, true),
-                    PendingIntent.FLAG_CANCEL_CURRENT, UserHandle.SYSTEM);
-            Notification.Action.Builder shareActionBuilder = new Notification.Action.Builder(
-                    R.drawable.ic_screenshot_share,
-                    r.getString(com.android.internal.R.string.share), shareAction);
-            mNotificationBuilder.addAction(shareActionBuilder.build());
-
-            // Create an edit intent, if a specific package is provided as the editor, then launch
-            // that directly
-            String editorPackage = context.getString(R.string.config_screenshotEditor);
-            Intent editIntent = new Intent(Intent.ACTION_EDIT);
-            if (!TextUtils.isEmpty(editorPackage)) {
-                editIntent.setComponent(ComponentName.unflattenFromString(editorPackage));
-            }
-            editIntent.setType("image/png");
-            editIntent.setData(uri);
-            editIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
-            editIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
-
-            // Create a edit action
-            PendingIntent editAction = PendingIntent.getBroadcastAsUser(context, 1,
-                    new Intent(context, GlobalScreenshot.ActionProxyReceiver.class)
-                            .putExtra(EXTRA_ACTION_INTENT, editIntent)
-                            .putExtra(EXTRA_CANCEL_NOTIFICATION, editIntent.getComponent() != null),
-                    PendingIntent.FLAG_CANCEL_CURRENT, UserHandle.SYSTEM);
-            Notification.Action.Builder editActionBuilder = new Notification.Action.Builder(
-                    R.drawable.ic_screenshot_edit,
-                    r.getString(com.android.internal.R.string.screenshot_edit), editAction);
-            mNotificationBuilder.addAction(editActionBuilder.build());
-
-            // Create a delete action for the notification
-            PendingIntent deleteAction = PendingIntent.getBroadcast(context, 0,
-                    new Intent(context, GlobalScreenshot.DeleteScreenshotReceiver.class)
-                            .putExtra(GlobalScreenshot.SCREENSHOT_URI_ID, uri.toString()),
-                    PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT);
-            Notification.Action.Builder deleteActionBuilder = new Notification.Action.Builder(
-                    R.drawable.ic_screenshot_delete,
-                    r.getString(com.android.internal.R.string.delete), deleteAction);
-            mNotificationBuilder.addAction(deleteActionBuilder.build());
+            populateNotificationActions(context, r, uri, smartActionsFuture, mNotificationBuilder);
 
             mParams.imageUri = uri;
             mParams.image = null;
             mParams.errorMsgResId = 0;
-
-            if (smartActionsEnabled) {
-                int timeoutMs = DeviceConfig.getInt(DeviceConfig.NAMESPACE_SYSTEMUI,
-                        SystemUiDeviceConfigFlags
-                                .SCREENSHOT_NOTIFICATION_SMART_ACTIONS_TIMEOUT_MS,
-                        1000);
-                List<Notification.Action> smartActions = GlobalScreenshot.getSmartActions(
-                        smartActionsFuture,
-                        timeoutMs);
-                for (Notification.Action action : smartActions) {
-                    mNotificationBuilder.addAction(action);
-                }
-            }
         } catch (Exception e) {
             // IOException/UnsupportedOperationException may be thrown if external storage is not
             // mounted
@@ -422,6 +385,105 @@
         return null;
     }
 
+    @VisibleForTesting
+    void populateNotificationActions(Context context, Resources r, Uri uri,
+            CompletableFuture<List<Notification.Action>> smartActionsFuture,
+            Notification.Builder notificationBuilder) {
+        // Note: Both the share and edit actions are proxied through ActionProxyReceiver in
+        // order to do some common work like dismissing the keyguard and sending
+        // closeSystemWindows
+
+        // Create a share intent, this will always go through the chooser activity first which
+        // should not trigger auto-enter PiP
+        String subjectDate = DateFormat.getDateTimeInstance().format(new Date(mImageTime));
+        String subject = String.format(SCREENSHOT_SHARE_SUBJECT_TEMPLATE, subjectDate);
+        Intent sharingIntent = new Intent(Intent.ACTION_SEND);
+        sharingIntent.setType("image/png");
+        sharingIntent.putExtra(Intent.EXTRA_STREAM, uri);
+        // Include URI in ClipData also, so that grantPermission picks it up.
+        // We don't use setData here because some apps interpret this as "to:".
+        ClipData clipdata = new ClipData(new ClipDescription("content",
+                new String[]{ClipDescription.MIMETYPE_TEXT_PLAIN}),
+                new ClipData.Item(uri));
+        sharingIntent.setClipData(clipdata);
+        sharingIntent.putExtra(Intent.EXTRA_SUBJECT, subject);
+        sharingIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+
+        PendingIntent chooserAction = PendingIntent.getBroadcast(context, 0,
+                new Intent(context, GlobalScreenshot.TargetChosenReceiver.class),
+                PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT);
+        Intent sharingChooserIntent = Intent.createChooser(sharingIntent, null,
+                chooserAction.getIntentSender())
+                .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK)
+                .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+
+        // Create a share action for the notification
+        PendingIntent shareAction = PendingIntent.getBroadcastAsUser(context, 0,
+                new Intent(context, GlobalScreenshot.ActionProxyReceiver.class)
+                        .putExtra(EXTRA_ACTION_INTENT, sharingChooserIntent)
+                        .putExtra(EXTRA_DISALLOW_ENTER_PIP, true)
+                        .putExtra(GlobalScreenshot.EXTRA_ID, mScreenshotId)
+                        .putExtra(GlobalScreenshot.EXTRA_SMART_ACTIONS_ENABLED,
+                                mSmartActionsEnabled),
+                PendingIntent.FLAG_CANCEL_CURRENT, UserHandle.SYSTEM);
+        Notification.Action.Builder shareActionBuilder = new Notification.Action.Builder(
+                R.drawable.ic_screenshot_share,
+                r.getString(com.android.internal.R.string.share), shareAction);
+        notificationBuilder.addAction(shareActionBuilder.build());
+
+        // Create an edit intent, if a specific package is provided as the editor, then launch
+        // that directly
+        String editorPackage = context.getString(R.string.config_screenshotEditor);
+        Intent editIntent = new Intent(Intent.ACTION_EDIT);
+        if (!TextUtils.isEmpty(editorPackage)) {
+            editIntent.setComponent(ComponentName.unflattenFromString(editorPackage));
+        }
+        editIntent.setType("image/png");
+        editIntent.setData(uri);
+        editIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+        editIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+
+        // Create a edit action
+        PendingIntent editAction = PendingIntent.getBroadcastAsUser(context, 1,
+                new Intent(context, GlobalScreenshot.ActionProxyReceiver.class)
+                        .putExtra(EXTRA_ACTION_INTENT, editIntent)
+                        .putExtra(EXTRA_CANCEL_NOTIFICATION, editIntent.getComponent() != null)
+                        .putExtra(GlobalScreenshot.EXTRA_ID, mScreenshotId)
+                        .putExtra(GlobalScreenshot.EXTRA_SMART_ACTIONS_ENABLED,
+                                mSmartActionsEnabled),
+                PendingIntent.FLAG_CANCEL_CURRENT, UserHandle.SYSTEM);
+        Notification.Action.Builder editActionBuilder = new Notification.Action.Builder(
+                R.drawable.ic_screenshot_edit,
+                r.getString(com.android.internal.R.string.screenshot_edit), editAction);
+        notificationBuilder.addAction(editActionBuilder.build());
+
+        // Create a delete action for the notification
+        PendingIntent deleteAction = PendingIntent.getBroadcast(context, 0,
+                new Intent(context, GlobalScreenshot.DeleteScreenshotReceiver.class)
+                        .putExtra(GlobalScreenshot.SCREENSHOT_URI_ID, uri.toString())
+                        .putExtra(GlobalScreenshot.EXTRA_ID, mScreenshotId)
+                        .putExtra(GlobalScreenshot.EXTRA_SMART_ACTIONS_ENABLED,
+                                mSmartActionsEnabled),
+                PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT);
+        Notification.Action.Builder deleteActionBuilder = new Notification.Action.Builder(
+                R.drawable.ic_screenshot_delete,
+                r.getString(com.android.internal.R.string.delete), deleteAction);
+        notificationBuilder.addAction(deleteActionBuilder.build());
+
+        if (mSmartActionsEnabled) {
+            int timeoutMs = DeviceConfig.getInt(DeviceConfig.NAMESPACE_SYSTEMUI,
+                    SystemUiDeviceConfigFlags
+                            .SCREENSHOT_NOTIFICATION_SMART_ACTIONS_TIMEOUT_MS,
+                    1000);
+            List<Notification.Action> smartActions = buildSmartActions(
+                    GlobalScreenshot.getSmartActions(mScreenshotId, smartActionsFuture,
+                            timeoutMs, mSmartActionsProvider), context);
+            for (Notification.Action action : smartActions) {
+                notificationBuilder.addAction(action);
+            }
+        }
+    }
+
     @Override
     protected void onPostExecute(Void params) {
         if (mParams.errorMsgResId != 0) {
@@ -504,6 +566,15 @@
 }
 
 class GlobalScreenshot {
+    // These strings are used for communicating the action invoked to
+    // ScreenshotNotificationSmartActionsProvider.
+    static final String EXTRA_ACTION_TYPE = "android:screenshot_action_type";
+    static final String EXTRA_ID = "android:screenshot_id";
+    static final String ACTION_TYPE_DELETE = "Delete";
+    static final String ACTION_TYPE_SHARE = "Share";
+    static final String ACTION_TYPE_EDIT = "Edit";
+    static final String EXTRA_SMART_ACTIONS_ENABLED = "android:smart_actions_enabled";
+
     static final String SCREENSHOT_URI_ID = "android:screenshot_uri_id";
     static final String EXTRA_ACTION_INTENT = "android:screenshot_action_intent";
     static final String EXTRA_CANCEL_NOTIFICATION = "android:screenshot_cancel_notification";
@@ -963,9 +1034,9 @@
     }
 
     @VisibleForTesting
-    static CompletableFuture<List<Notification.Action>> getSmartActionsFuture(Context context,
+    static CompletableFuture<List<Notification.Action>> getSmartActionsFuture(String screenshotId,
             Bitmap image, ScreenshotNotificationSmartActionsProvider smartActionsProvider,
-            Handler handler, boolean smartActionsEnabled, boolean isManagedProfile) {
+            boolean smartActionsEnabled, boolean isManagedProfile) {
         if (!smartActionsEnabled) {
             Slog.i(TAG, "Screenshot Intelligence not enabled, returning empty list.");
             return CompletableFuture.completedFuture(Collections.emptyList());
@@ -979,6 +1050,7 @@
 
         Slog.d(TAG, "Screenshot from a managed profile: " + isManagedProfile);
         CompletableFuture<List<Notification.Action>> smartActionsFuture;
+        long startTimeMs = SystemClock.uptimeMillis();
         try {
             ActivityManager.RunningTaskInfo runningTask =
                     ActivityManagerWrapper.getInstance().getRunningTask();
@@ -986,34 +1058,74 @@
                     (runningTask != null && runningTask.topActivity != null)
                             ? runningTask.topActivity
                             : new ComponentName("", "");
-            smartActionsFuture = smartActionsProvider.getActions(image, context,
-                    THREAD_POOL_EXECUTOR,
-                    handler,
+            smartActionsFuture = smartActionsProvider.getActions(screenshotId, image,
                     componentName,
                     isManagedProfile);
         } catch (Throwable e) {
+            long waitTimeMs = SystemClock.uptimeMillis() - startTimeMs;
             smartActionsFuture = CompletableFuture.completedFuture(Collections.emptyList());
             Slog.e(TAG, "Failed to get future for screenshot notification smart actions.", e);
+            notifyScreenshotOp(screenshotId, smartActionsProvider,
+                    ScreenshotNotificationSmartActionsProvider.ScreenshotOp.REQUEST_SMART_ACTIONS,
+                    ScreenshotNotificationSmartActionsProvider.ScreenshotOpStatus.ERROR,
+                    waitTimeMs);
         }
         return smartActionsFuture;
     }
 
     @VisibleForTesting
-    static List<Notification.Action> getSmartActions(
-            CompletableFuture<List<Notification.Action>> smartActionsFuture, int timeoutMs) {
+    static List<Notification.Action> getSmartActions(String screenshotId,
+            CompletableFuture<List<Notification.Action>> smartActionsFuture, int timeoutMs,
+            ScreenshotNotificationSmartActionsProvider smartActionsProvider) {
+        long startTimeMs = SystemClock.uptimeMillis();
         try {
-            long startTimeMs = SystemClock.uptimeMillis();
             List<Notification.Action> actions = smartActionsFuture.get(timeoutMs,
                     TimeUnit.MILLISECONDS);
+            long waitTimeMs = SystemClock.uptimeMillis() - startTimeMs;
             Slog.d(TAG, String.format("Wait time for smart actions: %d ms",
-                    SystemClock.uptimeMillis() - startTimeMs));
+                    waitTimeMs));
+            notifyScreenshotOp(screenshotId, smartActionsProvider,
+                    ScreenshotNotificationSmartActionsProvider.ScreenshotOp.WAIT_FOR_SMART_ACTIONS,
+                    ScreenshotNotificationSmartActionsProvider.ScreenshotOpStatus.SUCCESS,
+                    waitTimeMs);
             return actions;
         } catch (Throwable e) {
-            Slog.e(TAG, "Failed to obtain screenshot notification smart actions.", e);
+            long waitTimeMs = SystemClock.uptimeMillis() - startTimeMs;
+            Slog.d(TAG, "Failed to obtain screenshot notification smart actions.", e);
+            ScreenshotNotificationSmartActionsProvider.ScreenshotOpStatus status =
+                    (e instanceof TimeoutException)
+                            ? ScreenshotNotificationSmartActionsProvider.ScreenshotOpStatus.TIMEOUT
+                            : ScreenshotNotificationSmartActionsProvider.ScreenshotOpStatus.ERROR;
+            notifyScreenshotOp(screenshotId, smartActionsProvider,
+                    ScreenshotNotificationSmartActionsProvider.ScreenshotOp.WAIT_FOR_SMART_ACTIONS,
+                    status, waitTimeMs);
             return Collections.emptyList();
         }
     }
 
+    static void notifyScreenshotOp(String screenshotId,
+            ScreenshotNotificationSmartActionsProvider smartActionsProvider,
+            ScreenshotNotificationSmartActionsProvider.ScreenshotOp op,
+            ScreenshotNotificationSmartActionsProvider.ScreenshotOpStatus status, long durationMs) {
+        try {
+            smartActionsProvider.notifyOp(screenshotId, op, status, durationMs);
+        } catch (Throwable e) {
+            Slog.e(TAG, "Error in notifyScreenshotOp: ", e);
+        }
+    }
+
+    static void notifyScreenshotAction(Context context, String screenshotId, String action,
+            boolean isSmartAction) {
+        try {
+            ScreenshotNotificationSmartActionsProvider provider =
+                    SystemUIFactory.getInstance().createScreenshotNotificationSmartActionsProvider(
+                            context, THREAD_POOL_EXECUTOR, new Handler());
+            provider.notifyAction(screenshotId, action, isSmartAction);
+        } catch (Throwable e) {
+            Slog.e(TAG, "Error in notifyScreenshotAction: ", e);
+        }
+    }
+
     /**
      * Receiver to proxy the share or edit intent, used to clean up the notification and send
      * appropriate signals to the system (ie. to dismiss the keyguard if necessary).
@@ -1023,6 +1135,7 @@
 
         @Override
         public void onReceive(Context context, final Intent intent) {
+            Intent actionIntent = intent.getParcelableExtra(EXTRA_ACTION_INTENT);
             Runnable startActivityRunnable = () -> {
                 try {
                     ActivityManagerWrapper.getInstance().closeSystemWindows(
@@ -1033,7 +1146,6 @@
                     return;
                 }
 
-                Intent actionIntent = intent.getParcelableExtra(EXTRA_ACTION_INTENT);
                 if (intent.getBooleanExtra(EXTRA_CANCEL_NOTIFICATION, false)) {
                     cancelScreenshotNotification(context);
                 }
@@ -1045,6 +1157,14 @@
             StatusBar statusBar = SysUiServiceProvider.getComponent(context, StatusBar.class);
             statusBar.executeRunnableDismissingKeyguard(startActivityRunnable, null,
                     true /* dismissShade */, true /* afterKeyguardGone */, true /* deferred */);
+
+            if (intent.getBooleanExtra(EXTRA_SMART_ACTIONS_ENABLED, false)) {
+                String actionType = Intent.ACTION_EDIT.equals(actionIntent.getAction())
+                        ? ACTION_TYPE_EDIT
+                        : ACTION_TYPE_SHARE;
+                notifyScreenshotAction(context, intent.getStringExtra(EXTRA_ID),
+                        actionType, false);
+            }
         }
     }
 
@@ -1075,6 +1195,29 @@
             // And delete the image from the media store
             final Uri uri = Uri.parse(intent.getStringExtra(SCREENSHOT_URI_ID));
             new DeleteImageInBackgroundTask(context).execute(uri);
+            if (intent.getBooleanExtra(EXTRA_SMART_ACTIONS_ENABLED, false)) {
+                notifyScreenshotAction(context, intent.getStringExtra(EXTRA_ID),
+                        ACTION_TYPE_DELETE,
+                        false);
+            }
+        }
+    }
+
+    /**
+     * Executes the smart action tapped by the user in the notification.
+     */
+    public static class SmartActionsReceiver extends BroadcastReceiver {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            PendingIntent actionIntent = intent.getParcelableExtra(EXTRA_ACTION_INTENT);
+            ActivityOptions opts = ActivityOptions.makeBasic();
+            context.startActivityAsUser(actionIntent.getIntent(), opts.toBundle(),
+                    UserHandle.CURRENT);
+
+            Slog.d(TAG, "Screenshot notification smart action is invoked.");
+            notifyScreenshotAction(context, intent.getStringExtra(EXTRA_ID),
+                    intent.getStringExtra(EXTRA_ACTION_TYPE),
+                    true);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsProvider.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsProvider.java
index fa23bf7..b6f5447 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsProvider.java
@@ -18,41 +18,84 @@
 
 import android.app.Notification;
 import android.content.ComponentName;
-import android.content.Context;
 import android.graphics.Bitmap;
-import android.os.Handler;
 import android.util.Log;
 
 import java.util.Collections;
 import java.util.List;
 import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.Executor;
 
 /**
  * This class can be overridden by a vendor-specific sys UI implementation,
  * in order to provide smart actions in the screenshot notification.
  */
 public class ScreenshotNotificationSmartActionsProvider {
+    /* Key provided in the notification action to get the type of smart action. */
+    public static final String ACTION_TYPE = "action_type";
+    public static final String DEFAULT_ACTION_TYPE = "Smart Action";
+
+    /* Define phases of screenshot execution. */
+    protected enum ScreenshotOp {
+        OP_UNKNOWN,
+        RETRIEVE_SMART_ACTIONS,
+        REQUEST_SMART_ACTIONS,
+        WAIT_FOR_SMART_ACTIONS
+    }
+
+    /* Enum to report success or failure for screenshot execution phases. */
+    protected enum ScreenshotOpStatus {
+        OP_STATUS_UNKNOWN,
+        SUCCESS,
+        ERROR,
+        TIMEOUT
+    }
+
     private static final String TAG = "ScreenshotActions";
 
     /**
      * Default implementation that returns an empty list.
      * This method is overridden in vendor-specific Sys UI implementation.
      *
+     * @param screenshotId     A generated random unique id for the screenshot.
      * @param bitmap           The bitmap of the screenshot. The bitmap config must be {@link
      *                         HARDWARE}.
-     * @param context          The current app {@link Context}.
-     * @param executor         A {@link Executor} that can be used to execute tasks in parallel.
-     * @param handler          A {@link Handler} to possibly run UI-thread code.
      * @param componentName    Contains package and activity class names where the screenshot was
      *                         taken. This is used as an additional signal to generate and rank more
      *                         relevant actions.
      * @param isManagedProfile The screenshot was taken for a work profile app.
      */
-    public CompletableFuture<List<Notification.Action>> getActions(Bitmap bitmap, Context context,
-            Executor executor, Handler handler, ComponentName componentName,
+    public CompletableFuture<List<Notification.Action>> getActions(
+            String screenshotId,
+            Bitmap bitmap,
+            ComponentName componentName,
             boolean isManagedProfile) {
         Log.d(TAG, "Returning empty smart action list.");
         return CompletableFuture.completedFuture(Collections.emptyList());
     }
+
+    /**
+     * Notify exceptions and latency encountered during generating smart actions.
+     * This method is overridden in vendor-specific Sys UI implementation.
+     *
+     * @param screenshotId Unique id of the screenshot.
+     * @param op           screenshot execution phase defined in {@link ScreenshotOp}
+     * @param status       {@link ScreenshotOpStatus} to report success or failure.
+     * @param durationMs   latency experienced in different phases of screenshots.
+     */
+    public void notifyOp(String screenshotId, ScreenshotOp op, ScreenshotOpStatus status,
+            long durationMs) {
+        Log.d(TAG, "Return without notify.");
+    }
+
+    /**
+     * Notify screenshot notification action invoked.
+     * This method is overridden in vendor-specific Sys UI implementation.
+     *
+     * @param screenshotId  Unique id of the screenshot.
+     * @param action        type of notification action invoked.
+     * @param isSmartAction whether action invoked was a smart action.
+     */
+    public void notifyAction(String screenshotId, String action, boolean isSmartAction) {
+        Log.d(TAG, "Return without notify.");
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java
index 02e5515..d46d7a2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java
@@ -16,8 +16,12 @@
 
 package com.android.systemui.screenshot;
 
+import static android.content.Context.NOTIFICATION_SERVICE;
+
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
@@ -25,14 +29,20 @@
 import static org.mockito.Mockito.when;
 
 import android.app.Notification;
+import android.app.NotificationManager;
+import android.content.Intent;
 import android.graphics.Bitmap;
+import android.net.Uri;
+import android.os.Bundle;
 import android.os.Handler;
+import android.os.Looper;
 import android.testing.AndroidTestingRunner;
 
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SystemUIFactory;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.util.NotificationChannels;
 
 import org.junit.Assert;
 import org.junit.Before;
@@ -70,12 +80,11 @@
         when(bitmap.getConfig()).thenReturn(Bitmap.Config.HARDWARE);
         ScreenshotNotificationSmartActionsProvider smartActionsProvider = mock(
                 ScreenshotNotificationSmartActionsProvider.class);
-        when(smartActionsProvider.getActions(any(), any(), any(), any(), any(),
-                eq(false))).thenThrow(
-                RuntimeException.class);
+        when(smartActionsProvider.getActions(any(), any(), any(),
+                eq(false))).thenThrow(RuntimeException.class);
         CompletableFuture<List<Notification.Action>> smartActionsFuture =
-                GlobalScreenshot.getSmartActionsFuture(mContext, bitmap,
-                        smartActionsProvider, mHandler, true, false);
+                GlobalScreenshot.getSmartActionsFuture("", bitmap,
+                        smartActionsProvider, true, false);
         Assert.assertNotNull(smartActionsFuture);
         List<Notification.Action> smartActions = smartActionsFuture.get(5, TimeUnit.MILLISECONDS);
         Assert.assertEquals(Collections.emptyList(), smartActions);
@@ -92,10 +101,18 @@
         when(smartActionsFuture.get(timeoutMs, TimeUnit.MILLISECONDS)).thenThrow(
                 RuntimeException.class);
         List<Notification.Action> actions = GlobalScreenshot.getSmartActions(
-                smartActionsFuture, timeoutMs);
+                "", smartActionsFuture, timeoutMs, mSmartActionsProvider);
         Assert.assertEquals(Collections.emptyList(), actions);
     }
 
+    // Tests any exception thrown in notifying feedback does not affect regular screenshot flow.
+    @Test
+    public void testExceptionHandlingInNotifyingFeedback() {
+        doThrow(RuntimeException.class).when(mSmartActionsProvider).notifyOp(any(), any(), any(),
+                anyLong());
+        GlobalScreenshot.notifyScreenshotOp(null, mSmartActionsProvider, null, null, -1);
+    }
+
     // Tests for a non-hardware bitmap, ScreenshotNotificationSmartActionsProvider is never invoked
     // and a completed future is returned.
     @Test
@@ -104,9 +121,9 @@
         Bitmap bitmap = mock(Bitmap.class);
         when(bitmap.getConfig()).thenReturn(Bitmap.Config.RGB_565);
         CompletableFuture<List<Notification.Action>> smartActionsFuture =
-                GlobalScreenshot.getSmartActionsFuture(mContext, bitmap,
-                        mSmartActionsProvider, mHandler, true, true);
-        verify(mSmartActionsProvider, never()).getActions(any(), any(), any(), any(), any(),
+                GlobalScreenshot.getSmartActionsFuture("", bitmap,
+                        mSmartActionsProvider, true, true);
+        verify(mSmartActionsProvider, never()).getActions(any(), any(), any(),
                 eq(false));
         Assert.assertNotNull(smartActionsFuture);
         List<Notification.Action> smartActions = smartActionsFuture.get(5, TimeUnit.MILLISECONDS);
@@ -118,10 +135,10 @@
     public void testScreenshotNotificationSmartActionsProviderInvokedOnce() {
         Bitmap bitmap = mock(Bitmap.class);
         when(bitmap.getConfig()).thenReturn(Bitmap.Config.HARDWARE);
-        GlobalScreenshot.getSmartActionsFuture(mContext, bitmap, mSmartActionsProvider,
-                mHandler, true, true);
+        GlobalScreenshot.getSmartActionsFuture("", bitmap, mSmartActionsProvider,
+                true, true);
         verify(mSmartActionsProvider, times(1))
-                .getActions(any(), any(), any(), any(), any(), eq(true));
+                .getActions(any(), any(), any(), eq(true));
     }
 
     // Tests for a hardware bitmap, a completed future is returned.
@@ -131,13 +148,64 @@
         Bitmap bitmap = mock(Bitmap.class);
         when(bitmap.getConfig()).thenReturn(Bitmap.Config.HARDWARE);
         ScreenshotNotificationSmartActionsProvider actionsProvider =
-                SystemUIFactory.getInstance().createScreenshotNotificationSmartActionsProvider();
+                SystemUIFactory.getInstance().createScreenshotNotificationSmartActionsProvider(
+                        mContext, null, mHandler);
         CompletableFuture<List<Notification.Action>> smartActionsFuture =
-                GlobalScreenshot.getSmartActionsFuture(mContext, bitmap,
+                GlobalScreenshot.getSmartActionsFuture("", bitmap,
                         actionsProvider,
-                        mHandler, true, true);
+                        true, true);
         Assert.assertNotNull(smartActionsFuture);
         List<Notification.Action> smartActions = smartActionsFuture.get(5, TimeUnit.MILLISECONDS);
         Assert.assertEquals(smartActions.size(), 0);
     }
+
+    // Tests for notification action extras.
+    @Test
+    public void testNotificationActionExtras() {
+        if (Looper.myLooper() == null) {
+            Looper.prepare();
+        }
+        NotificationManager notificationManager =
+                (NotificationManager) mContext.getSystemService(NOTIFICATION_SERVICE);
+        SaveImageInBackgroundData data = new SaveImageInBackgroundData();
+        data.context = mContext;
+        data.image = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
+        data.iconSize = 10;
+        data.finisher = null;
+        data.previewWidth = 10;
+        data.previewheight = 10;
+        SaveImageInBackgroundTask task = new SaveImageInBackgroundTask(mContext, data,
+                notificationManager);
+        Uri uri = Uri.parse("Screenshot_123.png");
+        Notification.Builder notificationBuilder = new Notification.Builder(mContext,
+                NotificationChannels.SCREENSHOTS_HEADSUP);
+        task.populateNotificationActions(mContext, mContext.getResources(),
+                uri,
+                CompletableFuture.completedFuture(Collections.emptyList()), notificationBuilder);
+
+        Notification notification = notificationBuilder.build();
+        Assert.assertEquals(notification.actions.length, 3);
+        boolean isShareFound = false;
+        boolean isEditFound = false;
+        boolean isDeleteFound = false;
+        for (Notification.Action action : notification.actions) {
+            Intent intent = action.actionIntent.getIntent();
+            Intent actionIntent = intent.getParcelableExtra(GlobalScreenshot.EXTRA_ACTION_INTENT);
+            Assert.assertNotNull(intent);
+            Bundle bundle = intent.getExtras();
+            Assert.assertTrue(bundle.containsKey(GlobalScreenshot.EXTRA_ID));
+            Assert.assertTrue(bundle.containsKey(GlobalScreenshot.EXTRA_SMART_ACTIONS_ENABLED));
+            if (uri.toString().equals(bundle.getString(GlobalScreenshot.SCREENSHOT_URI_ID))) {
+                isDeleteFound = true;
+            } else if (Intent.ACTION_EDIT.equals(actionIntent.getAction())) {
+                isEditFound = true;
+            } else if (Intent.ACTION_CHOOSER.equals(actionIntent.getAction())) {
+                isShareFound = true;
+            }
+        }
+
+        Assert.assertTrue(isEditFound);
+        Assert.assertTrue(isShareFound);
+        Assert.assertTrue(isDeleteFound);
+    }
 }
diff --git a/services/core/java/com/android/server/BinderCallsStatsService.java b/services/core/java/com/android/server/BinderCallsStatsService.java
index e510259..f2ce444 100644
--- a/services/core/java/com/android/server/BinderCallsStatsService.java
+++ b/services/core/java/com/android/server/BinderCallsStatsService.java
@@ -19,6 +19,7 @@
 import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
 import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
 
+import android.app.ActivityThread;
 import android.content.Context;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
@@ -40,6 +41,7 @@
 import com.android.internal.os.BinderCallsStats;
 import com.android.internal.os.BinderInternal;
 import com.android.internal.os.CachedDeviceState;
+import com.android.internal.util.DumpUtils;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -49,6 +51,7 @@
 public class BinderCallsStatsService extends Binder {
 
     private static final String TAG = "BinderCallsStatsService";
+    private static final String SERVICE_NAME = "binder_calls_stats";
 
     private static final String PERSIST_SYS_BINDER_CALLS_DETAILED_TRACKING
             = "persist.sys.binder_calls_detailed_tracking";
@@ -246,7 +249,7 @@
             mService = new BinderCallsStatsService(
                     mBinderCallsStats, mWorkSourceProvider);
             publishLocalService(Internal.class, new Internal(mBinderCallsStats));
-            publishBinderService("binder_calls_stats", mService);
+            publishBinderService(SERVICE_NAME, mService);
             boolean detailedTrackingEnabled = SystemProperties.getBoolean(
                     PERSIST_SYS_BINDER_CALLS_DETAILED_TRACKING, false);
 
@@ -293,6 +296,11 @@
 
     @Override
     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        if (!DumpUtils.checkDumpAndUsageStatsPermission(ActivityThread.currentApplication(),
+                SERVICE_NAME, pw)) {
+            return;
+        }
+
         boolean verbose = false;
         if (args != null) {
             for (final String arg : args) {