Merge "Collects metrics for the new Sharing Shortcuts api" into qt-dev
am: 573b3162a2

Change-Id: I15797e72d4369c84ed42a1c867787f2ab222036d
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index 9a34ffa..b7d838e 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -202,6 +202,9 @@
     private long mChooserShownTime;
     protected boolean mIsSuccessfullySelected;
 
+    private long mQueriedTargetServicesTimeMs;
+    private long mQueriedSharingShortcutsTimeMs;
+
     private ChooserListAdapter mChooserListAdapter;
     private ChooserRowAdapter mChooserRowAdapter;
     private int mChooserRowServiceSpacing;
@@ -273,6 +276,8 @@
                     sri.connection.destroy();
                     mServiceConnections.remove(sri.connection);
                     if (mServiceConnections.isEmpty()) {
+                        logDirectShareTargetReceived(
+                                MetricsEvent.ACTION_DIRECT_SHARE_TARGETS_LOADED_CHOOSER_SERVICE);
                         sendVoiceChoicesIfNeeded();
                     }
                     break;
@@ -283,6 +288,8 @@
                     }
 
                     unbindRemainingServices();
+                    logDirectShareTargetReceived(
+                            MetricsEvent.ACTION_DIRECT_SHARE_TARGETS_LOADED_CHOOSER_SERVICE);
                     sendVoiceChoicesIfNeeded();
                     mChooserListAdapter.completeServiceTargetLoading();
                     break;
@@ -305,6 +312,8 @@
                     break;
 
                 case SHORTCUT_MANAGER_SHARE_TARGET_RESULT_COMPLETED:
+                    logDirectShareTargetReceived(
+                            MetricsEvent.ACTION_DIRECT_SHARE_TARGETS_LOADED_SHORTCUT_MANAGER);
                     sendVoiceChoicesIfNeeded();
                     break;
 
@@ -1155,6 +1164,8 @@
     }
 
     void queryTargetServices(ChooserListAdapter adapter) {
+        mQueriedTargetServicesTimeMs = System.currentTimeMillis();
+
         final PackageManager pm = getPackageManager();
         ShortcutManager sm = (ShortcutManager) getSystemService(ShortcutManager.class);
         int targetsToQuery = 0;
@@ -1281,6 +1292,7 @@
 
     private void queryDirectShareTargets(
                 ChooserListAdapter adapter, boolean skipAppPredictionService) {
+        mQueriedSharingShortcutsTimeMs = System.currentTimeMillis();
         if (!skipAppPredictionService) {
             AppPredictor appPredictor = getAppPredictorForDirectShareIfEnabled();
             if (appPredictor != null) {
@@ -1391,6 +1403,14 @@
         // Do nothing. We'll send the voice stuff ourselves.
     }
 
+    private void logDirectShareTargetReceived(int logCategory) {
+        final long queryTime =
+                logCategory == MetricsEvent.ACTION_DIRECT_SHARE_TARGETS_LOADED_SHORTCUT_MANAGER
+                        ? mQueriedSharingShortcutsTimeMs : mQueriedTargetServicesTimeMs;
+        final int apiLatency = (int) (System.currentTimeMillis() - queryTime);
+        getMetricsLogger().write(new LogMaker(logCategory).setSubtype(apiLatency));
+    }
+
     void updateModelAndChooserCounts(TargetInfo info) {
         if (info != null) {
             sendClickToAppPredictor(info);
diff --git a/proto/src/metrics_constants/metrics_constants.proto b/proto/src/metrics_constants/metrics_constants.proto
index 65338cb..af8c631 100644
--- a/proto/src/metrics_constants/metrics_constants.proto
+++ b/proto/src/metrics_constants/metrics_constants.proto
@@ -263,6 +263,14 @@
     PREVIOUSLY_VISIBLE = 2;
   }
 
+  // Types for ACTION_SHORTCUTS_CHANGED
+  enum ShortcutsChangesInfo {
+    SHORTCUTS_CHANGED_UNKNOWN = 0;
+    SHORTCUTS_CHANGED_USER_ID = 1;
+    SHORTCUTS_CHANGED_PACKAGE_COUNT = 2;
+    SHORTCUTS_CHANGED_SHORTCUT_COUNT = 3;
+  }
+
   // Explanations for notification importance, derived from
   // NotificationRecord.mImportanceExplanation.
   enum NotificationImportanceExplanation {
@@ -7222,6 +7230,19 @@
     // OS: Q
     ASSISTANT = 1716;
 
+    // ACTION: Published shortcuts in ShortcutManager changed
+    //   TYPE: All the SHORTCUTS_CHANGED_* values in ShortcutsChangesInfo
+    // OS: Q
+    ACTION_SHORTCUTS_CHANGED = 1717;
+
+    // ACTION: Direct share targets loaded via ShortcutManager
+    // OS: Q
+    ACTION_DIRECT_SHARE_TARGETS_LOADED_SHORTCUT_MANAGER = 1718;
+
+    // ACTION: Direct share targets loaded via ChooserService
+    // OS: Q
+    ACTION_DIRECT_SHARE_TARGETS_LOADED_CHOOSER_SERVICE = 1719;
+
     // ---- End Q Constants, all Q constants go above this line ----
     // Add new aosp constants above this line.
     // END OF AOSP CONSTANTS
diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java
index 9782648..eec4b70 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackage.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackage.java
@@ -680,20 +680,23 @@
 
         final List<ShortcutManager.ShareShortcutInfo> result = new ArrayList<>();
         for (int i = 0; i < shortcuts.size(); i++) {
-            final ShortcutInfo si = shortcuts.get(i);
+            final Set<String> categories = shortcuts.get(i).getCategories();
+            if (categories == null || categories.isEmpty()) {
+                continue;
+            }
             for (int j = 0; j < matchedTargets.size(); j++) {
                 // Shortcut must have all of share target categories
                 boolean hasAllCategories = true;
                 final ShareTargetInfo target = matchedTargets.get(j);
                 for (int q = 0; q < target.mCategories.length; q++) {
-                    if (!si.getCategories().contains(target.mCategories[q])) {
+                    if (!categories.contains(target.mCategories[q])) {
                         hasAllCategories = false;
                         break;
                     }
                 }
                 if (hasAllCategories) {
-                    result.add(new ShortcutManager.ShareShortcutInfo(si, new ComponentName(
-                            getPackageName(), target.mTargetClass)));
+                    result.add(new ShortcutManager.ShareShortcutInfo(shortcuts.get(i),
+                            new ComponentName(getPackageName(), target.mTargetClass)));
                     break;
                 }
             }
@@ -706,6 +709,45 @@
     }
 
     /**
+     * Returns the number of shortcuts that can be used as a share target in the ShareSheet. Such
+     * shortcuts must have a matching category with at least one of the defined ShareTargets from
+     * the app's Xml resource.
+     */
+    int getSharingShortcutCount() {
+        if (mShortcuts.isEmpty() || mShareTargets.isEmpty()) {
+            return 0;
+        }
+
+        // Get the list of all dynamic shortcuts in this package
+        final ArrayList<ShortcutInfo> shortcuts = new ArrayList<>();
+        findAll(shortcuts, ShortcutInfo::isDynamicVisible, ShortcutInfo.CLONE_REMOVE_FOR_LAUNCHER);
+
+        int sharingShortcutCount = 0;
+        for (int i = 0; i < shortcuts.size(); i++) {
+            final Set<String> categories = shortcuts.get(i).getCategories();
+            if (categories == null || categories.isEmpty()) {
+                continue;
+            }
+            for (int j = 0; j < mShareTargets.size(); j++) {
+                // A SharingShortcut must have all of share target categories
+                boolean hasAllCategories = true;
+                final ShareTargetInfo target = mShareTargets.get(j);
+                for (int q = 0; q < target.mCategories.length; q++) {
+                    if (!categories.contains(target.mCategories[q])) {
+                        hasAllCategories = false;
+                        break;
+                    }
+                }
+                if (hasAllCategories) {
+                    sharingShortcutCount++;
+                    break;
+                }
+            }
+        }
+        return sharingShortcutCount;
+    }
+
+    /**
      * Return the filenames (excluding path names) of icon bitmap files from this package.
      */
     public ArraySet<String> getUsedBitmapFiles() {
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index daef4e0..2d8a2ac 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -95,6 +95,7 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.logging.MetricsLogger;
 import com.android.internal.os.BackgroundThread;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.FastXmlSerializer;
@@ -413,6 +414,9 @@
     @GuardedBy("mLock")
     private Exception mLastWtfStacktrace;
 
+    @GuardedBy("mLock")
+    private final MetricsLogger mMetricsLogger = new MetricsLogger();
+
     static class InvalidFileFormatException extends Exception {
         public InvalidFileFormatException(String message, Throwable cause) {
             super(message, cause);
@@ -981,6 +985,8 @@
             Slog.e(TAG, "Failed to write to file " + file.getBaseFile(), e);
             file.failWrite(os);
         }
+
+        getUserShortcutsLocked(userId).logSharingShortcutStats(mMetricsLogger);
     }
 
     @GuardedBy("mLock")
diff --git a/services/core/java/com/android/server/pm/ShortcutUser.java b/services/core/java/com/android/server/pm/ShortcutUser.java
index 1fd9b69..8c207a8 100644
--- a/services/core/java/com/android/server/pm/ShortcutUser.java
+++ b/services/core/java/com/android/server/pm/ShortcutUser.java
@@ -20,6 +20,7 @@
 import android.annotation.UserIdInt;
 import android.content.ComponentName;
 import android.content.pm.ShortcutManager;
+import android.metrics.LogMaker;
 import android.text.TextUtils;
 import android.text.format.Formatter;
 import android.util.ArrayMap;
@@ -27,6 +28,8 @@
 import android.util.Slog;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.internal.util.Preconditions;
 import com.android.server.pm.ShortcutService.DumpFilter;
 import com.android.server.pm.ShortcutService.InvalidFileFormatException;
@@ -647,4 +650,23 @@
 
         return result;
     }
+
+    void logSharingShortcutStats(MetricsLogger logger) {
+        int packageWithShareTargetsCount = 0;
+        int totalSharingShortcutCount = 0;
+        for (int i = 0; i < mPackages.size(); i++) {
+            if (mPackages.valueAt(i).hasShareTargets()) {
+                packageWithShareTargetsCount++;
+                totalSharingShortcutCount += mPackages.valueAt(i).getSharingShortcutCount();
+            }
+        }
+
+        final LogMaker logMaker = new LogMaker(MetricsEvent.ACTION_SHORTCUTS_CHANGED);
+        logger.write(logMaker.setType(MetricsEvent.SHORTCUTS_CHANGED_USER_ID)
+                .setSubtype(mUserId));
+        logger.write(logMaker.setType(MetricsEvent.SHORTCUTS_CHANGED_PACKAGE_COUNT)
+                .setSubtype(packageWithShareTargetsCount));
+        logger.write(logMaker.setType(MetricsEvent.SHORTCUTS_CHANGED_SHORTCUT_COUNT)
+                .setSubtype(totalSharingShortcutCount));
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
index 6845f15..b806180 100644
--- a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
@@ -1584,6 +1584,22 @@
     }
 
     /**
+     * Make a shortcut with an ID and Category.
+     */
+    protected ShortcutInfo makeShortcutWithCategory(String id, Set<String> categories) {
+        final ShortcutInfo.Builder  b = new ShortcutInfo.Builder(mClientContext, id)
+                .setActivity(new ComponentName(mClientContext.getPackageName(), "main"))
+                .setShortLabel("title-" + id)
+                .setIntent(makeIntent(Intent.ACTION_VIEW, ShortcutActivity.class))
+                .setCategories(categories);
+        final ShortcutInfo s = b.build();
+
+        s.setTimestamp(mInjectedCurrentTimeMillis); // HACK
+
+        return s;
+    }
+
+    /**
      * Make an intent.
      */
     protected Intent makeIntent(String action, Class<?> clazz, Object... bundleKeysAndValues) {
@@ -1818,6 +1834,17 @@
     }
 
     /**
+     * @return the number of shortcuts stored internally for the caller that can be used as a share
+     * target in the ShareSheet. Such shortcuts have a matching category with at least one of the
+     * defined ShareTargets from the app's Xml resource.
+     */
+    protected int getCallerSharingShortcutCount() {
+        final ShortcutPackage p = mService.getPackageShortcutForTest(
+                getCallingPackage(), getCallingUserId());
+        return p == null ? 0 : p.getSharingShortcutCount();
+    }
+
+    /**
      * @return all shortcuts owned by caller that are actually visible via ShortcutManager.
      * See also {@link #getCallerShortcuts}.
      */
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest3.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest3.java
index eb4db7a..ba26f79 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest3.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest3.java
@@ -17,6 +17,7 @@
 
 import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertWith;
 import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.list;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.set;
 
 import android.content.ComponentName;
 import android.content.pm.ShortcutInfo;
@@ -25,6 +26,8 @@
 import com.android.frameworks.servicestests.R;
 import com.android.server.pm.ShortcutService.ConfigConstants;
 
+import java.util.Set;
+
 /**
  * Tests related to shortcut rank auto-adjustment.
  */
@@ -50,6 +53,10 @@
         return makeShortcutWithActivityAndRank(id, activity, ShortcutInfo.RANK_NOT_SET);
     }
 
+    private ShortcutInfo shortcut(String id, Set<String> categories) {
+        return makeShortcutWithCategory(id, categories);
+    }
+
     @Override
     protected void setUp() throws Exception {
         super.setUp();
@@ -502,4 +509,30 @@
         runTestWithManifestShortcuts(() -> testDisableShortcuts_noManifestShortcuts());
     }
 
+    public void testGetSharingShortcutCount() {
+        addManifestShortcutResource(
+                new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
+                R.xml.shortcut_share_targets);
+        updatePackageVersion(CALLING_PACKAGE_1, 1);
+        mService.mPackageMonitor.onReceive(getTestContext(),
+                genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
+
+        // There are two valid <share-target> definitions in the test manifest with two different
+        // categories: {"com.test.category.CATEGORY1", "com.test.category.CATEGORY2"} and
+        // {"com.test.category.CATEGORY5", "com.test.category.CATEGORY6"}.
+        //
+        // Note that a shortcut is a match, only if it has ALL of the categories of at least one
+        // of the share-target definitions from the manifest.
+
+        mManager.addDynamicShortcuts(list(
+                shortcut("s1", set("com.test.category.CATEGORY1", "com.test.category.CATEGORY2")),
+                shortcut("s2", set("com.test.category.CATEGORY5")),
+                shortcut("s3", set("com.test.category.CATEGORY5", "com.test.category.CATEGORY6")),
+                shortcut("s4", set("com.test.category.CATEGORY1", "com.test.category.CATEGORY2",
+                        "com.test.category.CATEGORY5", "com.test.category.CATEGORY6")),
+                shortcut("s5", A1)
+        ));
+
+        assertEquals(3, getCallerSharingShortcutCount());
+    }
 }