Merge "UsageEvents for slices pinning" into pi-dev
diff --git a/api/system-current.txt b/api/system-current.txt
index a5be12f..1e2fe3d 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -725,6 +725,8 @@
method public java.lang.String getNotificationChannelId();
field public static final int NOTIFICATION_INTERRUPTION = 12; // 0xc
field public static final int NOTIFICATION_SEEN = 10; // 0xa
+ field public static final int SLICE_PINNED = 14; // 0xe
+ field public static final int SLICE_PINNED_PRIV = 13; // 0xd
field public static final int SYSTEM_INTERACTION = 6; // 0x6
}
diff --git a/core/java/android/app/usage/UsageEvents.java b/core/java/android/app/usage/UsageEvents.java
index 8550ac0..b354e81 100644
--- a/core/java/android/app/usage/UsageEvents.java
+++ b/core/java/android/app/usage/UsageEvents.java
@@ -125,6 +125,20 @@
@SystemApi
public static final int NOTIFICATION_INTERRUPTION = 12;
+ /**
+ * A Slice was pinned by the default launcher or the default assistant.
+ * @hide
+ */
+ @SystemApi
+ public static final int SLICE_PINNED_PRIV = 13;
+
+ /**
+ * A Slice was pinned by an app.
+ * @hide
+ */
+ @SystemApi
+ public static final int SLICE_PINNED = 14;
+
/** @hide */
public static final int FLAG_IS_PACKAGE_INSTANT_APP = 1 << 0;
diff --git a/services/core/java/com/android/server/slice/SliceManagerService.java b/services/core/java/com/android/server/slice/SliceManagerService.java
index a7dfd35..0b7d9d0 100644
--- a/services/core/java/com/android/server/slice/SliceManagerService.java
+++ b/services/core/java/com/android/server/slice/SliceManagerService.java
@@ -16,6 +16,8 @@
package com.android.server.slice;
+import static android.app.usage.UsageEvents.Event.SLICE_PINNED;
+import static android.app.usage.UsageEvents.Event.SLICE_PINNED_PRIV;
import static android.content.ContentProvider.getUriWithoutUserId;
import static android.content.ContentProvider.getUserIdFromUri;
import static android.content.ContentProvider.maybeAddUserId;
@@ -31,6 +33,7 @@
import android.app.slice.ISliceManager;
import android.app.slice.SliceManager;
import android.app.slice.SliceSpec;
+import android.app.usage.UsageStatsManagerInternal;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
@@ -95,6 +98,7 @@
private final AtomicFile mSliceAccessFile;
@GuardedBy("mAccessList")
private final SliceFullAccessList mAccessList;
+ private final UsageStatsManagerInternal mAppUsageStats;
public SliceManagerService(Context context) {
this(context, createHandler().getLooper());
@@ -112,6 +116,7 @@
final File systemDir = new File(Environment.getDataDirectory(), "system");
mSliceAccessFile = new AtomicFile(new File(systemDir, "slice_access.xml"));
mAccessList = new SliceFullAccessList(mContext);
+ mAppUsageStats = LocalServices.getService(UsageStatsManagerInternal.class);
synchronized (mSliceAccessFile) {
if (!mSliceAccessFile.exists()) return;
@@ -166,8 +171,19 @@
public void pinSlice(String pkg, Uri uri, SliceSpec[] specs, IBinder token) throws RemoteException {
verifyCaller(pkg);
enforceAccess(pkg, uri);
- uri = maybeAddUserId(uri, Binder.getCallingUserHandle().getIdentifier());
+ int user = Binder.getCallingUserHandle().getIdentifier();
+ uri = maybeAddUserId(uri, user);
getOrCreatePinnedSlice(uri, pkg).pin(pkg, specs, token);
+
+ Uri finalUri = uri;
+ mHandler.post(() -> {
+ String slicePkg = getProviderPkg(finalUri, user);
+ if (slicePkg != null && !Objects.equals(pkg, slicePkg)) {
+ mAppUsageStats.reportEvent(slicePkg, user,
+ isAssistant(pkg, user) || isDefaultHomeApp(pkg, user)
+ ? SLICE_PINNED_PRIV : SLICE_PINNED);
+ }
+ });
}
@Override
@@ -352,38 +368,45 @@
if (getContext().checkUriPermission(uri, pid, uid,
Intent.FLAG_GRANT_WRITE_URI_PERMISSION) != PERMISSION_GRANTED) {
// Last fallback (if the calling app owns the authority, then it can have access).
- long ident = Binder.clearCallingIdentity();
- try {
- IBinder token = new Binder();
- IActivityManager activityManager = ActivityManager.getService();
- ContentProviderHolder holder = null;
- String providerName = getUriWithoutUserId(uri).getAuthority();
- try {
- try {
- holder = activityManager.getContentProviderExternal(
- providerName, getUserIdFromUri(uri, user), token);
- if (holder == null || holder.info == null
- || !Objects.equals(holder.info.packageName, pkg)) {
- return PERMISSION_DENIED;
- }
- } finally {
- if (holder != null && holder.provider != null) {
- activityManager.removeContentProviderExternal(providerName, token);
- }
- }
- } catch (RemoteException e) {
- // Can't happen.
- e.rethrowAsRuntimeException();
- }
- } finally {
- // I know, the double finally seems ugly, but seems safest for the identity.
- Binder.restoreCallingIdentity(ident);
+ if (!Objects.equals(getProviderPkg(uri, user), pkg)) {
+ return PERMISSION_DENIED;
}
}
}
return PERMISSION_GRANTED;
}
+ private String getProviderPkg(Uri uri, int user) {
+ long ident = Binder.clearCallingIdentity();
+ try {
+ IBinder token = new Binder();
+ IActivityManager activityManager = ActivityManager.getService();
+ ContentProviderHolder holder = null;
+ String providerName = getUriWithoutUserId(uri).getAuthority();
+ try {
+ try {
+ holder = activityManager.getContentProviderExternal(
+ providerName, getUserIdFromUri(uri, user), token);
+ if (holder != null && holder.info != null) {
+ return holder.info.packageName;
+ } else {
+ return null;
+ }
+ } finally {
+ if (holder != null && holder.provider != null) {
+ activityManager.removeContentProviderExternal(providerName, token);
+ }
+ }
+ } catch (RemoteException e) {
+ // Can't happen.
+ throw e.rethrowAsRuntimeException();
+ }
+ } finally {
+ // I know, the double finally seems ugly, but seems safest for the identity.
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
private void enforceCrossUser(String pkg, Uri uri) {
int user = Binder.getCallingUserHandle().getIdentifier();
if (getUserIdFromUri(uri, user) != user) {
diff --git a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
index edf1f74..552c915 100644
--- a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
+++ b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
@@ -17,6 +17,8 @@
package com.android.server.usage;
import static android.app.usage.UsageEvents.Event.NOTIFICATION_SEEN;
+import static android.app.usage.UsageEvents.Event.SLICE_PINNED;
+import static android.app.usage.UsageEvents.Event.SLICE_PINNED_PRIV;
import static android.app.usage.UsageEvents.Event.USER_INTERACTION;
import static android.app.usage.UsageStatsManager.REASON_MAIN_DEFAULT;
import static android.app.usage.UsageStatsManager.REASON_MAIN_FORCED;
@@ -406,6 +408,30 @@
}
@Test
+ public void testSlicePinnedEvent() throws Exception {
+ setChargingState(mController, false);
+
+ reportEvent(mController, USER_INTERACTION, 0);
+ assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController));
+ mInjector.mElapsedRealtime = 1;
+ reportEvent(mController, SLICE_PINNED, mInjector.mElapsedRealtime);
+ assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController));
+
+ mController.forceIdleState(PACKAGE_1, USER_ID, true);
+ reportEvent(mController, SLICE_PINNED, mInjector.mElapsedRealtime);
+ assertEquals(STANDBY_BUCKET_WORKING_SET, getStandbyBucket(mController));
+ }
+
+ @Test
+ public void testSlicePinnedPrivEvent() throws Exception {
+ setChargingState(mController, false);
+
+ mController.forceIdleState(PACKAGE_1, USER_ID, true);
+ reportEvent(mController, SLICE_PINNED_PRIV, mInjector.mElapsedRealtime);
+ assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController));
+ }
+
+ @Test
public void testPredictionTimedout() throws Exception {
setChargingState(mController, false);
// Set it to timeout or usage, so that prediction can override it
diff --git a/services/tests/uiservicestests/src/com/android/server/slice/SliceManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/slice/SliceManagerServiceTest.java
index 4f446a9..1073a80 100644
--- a/services/tests/uiservicestests/src/com/android/server/slice/SliceManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/slice/SliceManagerServiceTest.java
@@ -30,6 +30,7 @@
import android.app.AppOpsManager;
import android.app.slice.SliceSpec;
+import android.app.usage.UsageStatsManagerInternal;
import android.content.pm.PackageManagerInternal;
import android.net.Uri;
import android.os.Binder;
@@ -66,6 +67,8 @@
@Before
public void setup() {
LocalServices.addService(PackageManagerInternal.class, mock(PackageManagerInternal.class));
+ LocalServices.addService(UsageStatsManagerInternal.class,
+ mock(UsageStatsManagerInternal.class));
mContext.addMockSystemService(AppOpsManager.class, mock(AppOpsManager.class));
mContext.getTestablePermissions().setPermission(TEST_URI, PERMISSION_GRANTED);
@@ -77,6 +80,7 @@
@After
public void teardown() {
LocalServices.removeServiceForTest(PackageManagerInternal.class);
+ LocalServices.removeServiceForTest(UsageStatsManagerInternal.class);
}
@Test
diff --git a/services/usage/java/com/android/server/usage/AppStandbyController.java b/services/usage/java/com/android/server/usage/AppStandbyController.java
index e836677..1af5f46 100644
--- a/services/usage/java/com/android/server/usage/AppStandbyController.java
+++ b/services/usage/java/com/android/server/usage/AppStandbyController.java
@@ -36,6 +36,7 @@
import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_NEVER;
import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_RARE;
import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_WORKING_SET;
+
import static com.android.server.SystemService.PHASE_BOOT_COMPLETED;
import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
@@ -43,8 +44,8 @@
import android.app.ActivityManager;
import android.app.AppGlobals;
import android.app.usage.AppStandbyInfo;
-import android.app.usage.UsageStatsManager.StandbyBuckets;
import android.app.usage.UsageEvents;
+import android.app.usage.UsageStatsManager.StandbyBuckets;
import android.app.usage.UsageStatsManagerInternal.AppIdleStateChangeListener;
import android.appwidget.AppWidgetManager;
import android.content.BroadcastReceiver;
@@ -100,7 +101,6 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
-import java.util.Map;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
@@ -690,7 +690,9 @@
|| event.mEventType == UsageEvents.Event.MOVE_TO_BACKGROUND
|| event.mEventType == UsageEvents.Event.SYSTEM_INTERACTION
|| event.mEventType == UsageEvents.Event.USER_INTERACTION
- || event.mEventType == UsageEvents.Event.NOTIFICATION_SEEN)) {
+ || event.mEventType == UsageEvents.Event.NOTIFICATION_SEEN
+ || event.mEventType == UsageEvents.Event.SLICE_PINNED
+ || event.mEventType == UsageEvents.Event.SLICE_PINNED_PRIV)) {
final AppUsageHistory appHistory = mAppIdleHistory.getAppUsageHistory(
event.mPackage, userId, elapsedRealtime);
@@ -699,7 +701,8 @@
final long nextCheckTime;
final int subReason = usageEventToSubReason(event.mEventType);
final int reason = REASON_MAIN_USAGE | subReason;
- if (event.mEventType == UsageEvents.Event.NOTIFICATION_SEEN) {
+ if (event.mEventType == UsageEvents.Event.NOTIFICATION_SEEN
+ || event.mEventType == UsageEvents.Event.SLICE_PINNED) {
// Mild usage elevates to WORKING_SET but doesn't change usage time.
mAppIdleHistory.reportUsage(appHistory, event.mPackage,
STANDBY_BUCKET_WORKING_SET, subReason,
diff --git a/services/usage/java/com/android/server/usage/UserUsageStatsService.java b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
index d974282..c2e38f2 100644
--- a/services/usage/java/com/android/server/usage/UserUsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
@@ -761,6 +761,10 @@
return "STANDBY_BUCKET_CHANGED";
case UsageEvents.Event.NOTIFICATION_INTERRUPTION:
return "NOTIFICATION_INTERRUPTION";
+ case UsageEvents.Event.SLICE_PINNED:
+ return "SLICE_PINNED";
+ case UsageEvents.Event.SLICE_PINNED_PRIV:
+ return "SLICE_PINNED_PRIV";
default:
return "UNKNOWN";
}