From 1918ef7569e90c70246e535478b26732b82d92d3 Mon Sep 17 00:00:00 2001 From: Jason Monk Date: Wed, 14 Mar 2018 09:20:39 -0400 Subject: [PATCH] UsageEvents for slices pinning Test: atest frameworks/base/services/tests/servicestests Bug: 73455786 Change-Id: I6a37f5525c29d3f47aa37e262c8834840309ba44 --- api/system-current.txt | 2 + core/java/android/app/usage/UsageEvents.java | 14 ++++ .../server/slice/SliceManagerService.java | 77 ++++++++++++------- .../usage/AppStandbyControllerTests.java | 26 +++++++ .../server/slice/SliceManagerServiceTest.java | 4 + .../server/usage/AppStandbyController.java | 11 ++- .../server/usage/UserUsageStatsService.java | 4 + 7 files changed, 107 insertions(+), 31 deletions(-) diff --git a/api/system-current.txt b/api/system-current.txt index a5be12fdfc9cc..1e2fe3df0d2c7 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -725,6 +725,8 @@ package android.app.usage { 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 8550ac0f32cd4..b354e8122a980 100644 --- a/core/java/android/app/usage/UsageEvents.java +++ b/core/java/android/app/usage/UsageEvents.java @@ -125,6 +125,20 @@ public final class UsageEvents implements Parcelable { @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 a7dfd35acf1b5..0b7d9d02f1cb4 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.IActivityManager; 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 @@ public class SliceManagerService extends ISliceManager.Stub { 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 @@ public class SliceManagerService extends ISliceManager.Stub { 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 class SliceManagerService extends ISliceManager.Stub { 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 @@ public class SliceManagerService extends ISliceManager.Stub { 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 edf1f746fb4bd..552c915f84802 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; @@ -405,6 +407,30 @@ public class AppStandbyControllerTests { assertEquals(STANDBY_BUCKET_WORKING_SET, getStandbyBucket(mController)); } + @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); 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 4f446a9afb980..1073a800527aa 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 static org.mockito.Mockito.when; 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 @@ public class SliceManagerServiceTest extends UiServiceTestCase { @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 @@ public class SliceManagerServiceTest extends UiServiceTestCase { @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 e836677e38fc5..1af5f469c0f00 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_FREQUENT; 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.annotation.UserIdInt; 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.time.format.DateTimeParseException; 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 @@ public class AppStandbyController { || 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 @@ public class AppStandbyController { 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 d9742825ac705..c2e38f2f0bd81 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 @@ class UserUsageStatsService { 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"; }