From c2e7bfb7929d06271b5de413af3cb8f36fd69faa Mon Sep 17 00:00:00 2001 From: Sudheer Shanka Date: Mon, 24 Jan 2022 15:10:26 +0000 Subject: [PATCH] Inform UsageStatsManager about broadcast-dispatched events. Bug: 206518114 Test: treehugger CTS-Coverage-Bug: 206518114 Change-Id: If6b81970461854bf607f2bbd51f47a6f7ec44e03 --- core/api/system-current.txt | 1 + core/java/android/app/BroadcastOptions.java | 34 ++++++++++++++ .../com/android/server/am/BroadcastQueue.java | 44 +++++++++++++++++++ 3 files changed, 79 insertions(+) diff --git a/core/api/system-current.txt b/core/api/system-current.txt index ea2a6418ad54f..ed5314935322b 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -744,6 +744,7 @@ package android.app { method public void clearRequireCompatChange(); method public boolean isPendingIntentBackgroundActivityLaunchAllowed(); method public static android.app.BroadcastOptions makeBasic(); + method @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public void recordResponseEventWhileInBackground(@IntRange(from=0) long); method @RequiresPermission(android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND) public void setBackgroundActivityStartsAllowed(boolean); method public void setDontSendToRestrictedApps(boolean); method public void setPendingIntentBackgroundActivityLaunchAllowed(boolean); diff --git a/core/java/android/app/BroadcastOptions.java b/core/java/android/app/BroadcastOptions.java index 7812aba210e72..e31a566904084 100644 --- a/core/java/android/app/BroadcastOptions.java +++ b/core/java/android/app/BroadcastOptions.java @@ -16,6 +16,7 @@ package android.app; +import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; @@ -52,6 +53,7 @@ public class BroadcastOptions extends ComponentOptions { private String[] mRequireNoneOfPermissions; private long mRequireCompatChangeId = CHANGE_INVALID; private boolean mRequireCompatChangeEnabled = true; + private long mIdForResponseEvent; /** * Change ID which is invalid. @@ -164,6 +166,12 @@ public class BroadcastOptions extends ComponentOptions { public static final int TEMPORARY_WHITELIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED = PowerExemptionManager.TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED; + /** + * Corresponds to {@link #recordResponseEventWhileInBackground(long)}. + */ + private static final String KEY_ID_FOR_RESPONSE_EVENT = + "android:broadcast.idForResponseEvent"; + public static BroadcastOptions makeBasic() { BroadcastOptions opts = new BroadcastOptions(); return opts; @@ -198,6 +206,7 @@ public class BroadcastOptions extends ComponentOptions { mRequireNoneOfPermissions = opts.getStringArray(KEY_REQUIRE_NONE_OF_PERMISSIONS); mRequireCompatChangeId = opts.getLong(KEY_REQUIRE_COMPAT_CHANGE_ID, CHANGE_INVALID); mRequireCompatChangeEnabled = opts.getBoolean(KEY_REQUIRE_COMPAT_CHANGE_ENABLED, true); + mIdForResponseEvent = opts.getLong(KEY_ID_FOR_RESPONSE_EVENT); } /** @@ -510,6 +519,28 @@ public class BroadcastOptions extends ComponentOptions { } } + /** + * Sets whether events (such as posting a notification) originating from an app after it + * receives the broadcast while in background should be recorded as responses to the broadcast. + * + * @param id ID to be used for the response events corresponding to this broadcast. If the + * value is {@code 0} (default), then response events will not be recorded. Otherwise, + * they will be recorded with the ID provided. + * + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) + public void recordResponseEventWhileInBackground(@IntRange(from = 0) long id) { + mIdForResponseEvent = id; + } + + /** @hide */ + @IntRange(from = 0) + public long getIdForResponseEvent() { + return mIdForResponseEvent; + } + /** * Returns the created options as a Bundle, which can be passed to * {@link android.content.Context#sendBroadcast(android.content.Intent) @@ -549,6 +580,9 @@ public class BroadcastOptions extends ComponentOptions { b.putLong(KEY_REQUIRE_COMPAT_CHANGE_ID, mRequireCompatChangeId); b.putBoolean(KEY_REQUIRE_COMPAT_CHANGE_ENABLED, mRequireCompatChangeEnabled); } + if (mIdForResponseEvent != 0) { + b.putLong(KEY_ID_FOR_RESPONSE_EVENT, mIdForResponseEvent); + } return b.isEmpty() ? null : b; } } diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java index bb939b7ac0267..ec6b7827cf95f 100644 --- a/services/core/java/com/android/server/am/BroadcastQueue.java +++ b/services/core/java/com/android/server/am/BroadcastQueue.java @@ -28,6 +28,7 @@ import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PERMISSIONS import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_BROADCAST; import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_MU; +import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; import android.app.AppGlobals; @@ -37,6 +38,7 @@ import android.app.IApplicationThread; import android.app.PendingIntent; import android.app.RemoteServiceException.CannotDeliverBroadcastException; import android.app.usage.UsageEvents.Event; +import android.app.usage.UsageStatsManagerInternal; import android.content.ComponentName; import android.content.ContentResolver; import android.content.IIntentReceiver; @@ -69,6 +71,7 @@ import android.util.proto.ProtoOutputStream; import com.android.internal.util.ArrayUtils; import com.android.internal.util.FrameworkStatsLog; +import com.android.server.LocalServices; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -325,6 +328,7 @@ public final class BroadcastQueue { mService.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_START_RECEIVER); // Tell the application to launch this receiver. + maybeReportBroadcastDispatchedEventLocked(r); r.intent.setComponent(r.curComponent); boolean started = false; @@ -917,6 +921,7 @@ public final class BroadcastQueue { r.receiverTime = SystemClock.uptimeMillis(); maybeAddAllowBackgroundActivityStartsToken(filter.receiverList.app, r); maybeScheduleTempAllowlistLocked(filter.owningUid, r, r.options); + maybeReportBroadcastDispatchedEventLocked(r); performReceiveLocked(filter.receiverList.app, filter.receiverList.receiver, new Intent(r.intent), r.resultCode, r.resultData, r.resultExtras, r.ordered, r.initialSticky, r.userId); @@ -1831,6 +1836,45 @@ public final class BroadcastQueue { mPendingBroadcastRecvIndex = recIdx; } + + @Nullable + private String getTargetPackage(BroadcastRecord r) { + if (r.intent == null) { + return null; + } + if (r.intent.getPackage() != null) { + return r.intent.getPackage(); + } else if (r.intent.getComponent() != null) { + return r.intent.getComponent().getPackageName(); + } + return null; + } + + private void maybeReportBroadcastDispatchedEventLocked(BroadcastRecord r) { + final String targetPackage = getTargetPackage(r); + // Ignore non-explicit broadcasts + if (targetPackage == null) { + return; + } + // TODO (206518114): Only allow apps with ACCESS_PACKAGE_USAGE_STATS to set + // getIdForResponseEvent. + if (r.options == null || r.options.getIdForResponseEvent() <= 0) { + return; + } + // TODO (206518114): Only report this event when the broadcast is dispatched while the app + // is in the background state. + getUsageStatsManagerInternal().reportBroadcastDispatched( + r.callingUid, targetPackage, UserHandle.of(r.userId), + r.options.getIdForResponseEvent(), SystemClock.elapsedRealtime()); + } + + @NonNull + private UsageStatsManagerInternal getUsageStatsManagerInternal() { + final UsageStatsManagerInternal usageStatsManagerInternal = + LocalServices.getService(UsageStatsManagerInternal.class); + return usageStatsManagerInternal; + } + private boolean noteOpForManifestReceiver(int appOp, BroadcastRecord r, ResolveInfo info, ComponentName component) { if (ArrayUtils.isEmpty(info.activityInfo.attributionTags)) {