diff options
13 files changed, 196 insertions, 4 deletions
diff --git a/api/current.txt b/api/current.txt index 1cfc99acca32..d9cb9dfcc4ea 100644 --- a/api/current.txt +++ b/api/current.txt @@ -3901,6 +3901,7 @@ package android.app { method public void setImmersive(boolean); method public void setInheritShowWhenLocked(boolean); method public void setIntent(android.content.Intent); + method public void setLocusContext(@Nullable android.content.LocusId, @Nullable android.os.Bundle); method public final void setMediaController(android.media.session.MediaController); method public void setPictureInPictureParams(@NonNull android.app.PictureInPictureParams); method @Deprecated public final void setProgress(int); diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index d952be5218a4..d8b5e7f3b5b0 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -45,6 +45,7 @@ import android.content.CursorLoader; import android.content.IIntentSender; import android.content.Intent; import android.content.IntentSender; +import android.content.LocusId; import android.content.SharedPreferences; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; @@ -1025,6 +1026,39 @@ public class Activity extends ContextThemeWrapper mIntent = newIntent; } + /** + * Sets the {@link android.content.LocusId} for this activity. The locus id + * helps identify different instances of the same {@code Activity} class. + * <p> For example, a locus id based on a specific conversation could be set on a + * conversation app's chat {@code Activity}. The system can then use this locus id + * along with app's contents to provide ranking signals in various UI surfaces + * including sharing, notifications, shortcuts and so on. + * <p> It is recommended to set the same locus id in the shortcut's locus id using + * {@link android.content.pm.ShortcutInfo.Builder#setLocusId(android.content.LocusId) + * setLocusId} + * so that the system can learn appropriate ranking signals linking the activity's + * locus id with the matching shortcut. + * + * @param locusId a unique, stable id that identifies this {@code Activity} instance from + * others. This can be linked to a shortcut using + * {@link android.content.pm.ShortcutInfo.Builder#setLocusId(android.content.LocusId) + * setLocusId} with the same locus id string. + * @param bundle extras set or updated as part of this locus context. This may help provide + * additional metadata such as URLs, conversation participants specific to this + * {@code Activity}'s context. + * + * @see android.view.contentcapture.ContentCaptureManager + * @see android.view.contentcapture.ContentCaptureContext + */ + public void setLocusContext(@Nullable LocusId locusId, @Nullable Bundle bundle) { + try { + ActivityManager.getService().setActivityLocusContext(mComponent, locusId, mToken); + } catch (RemoteException re) { + re.rethrowFromSystemServer(); + } + // TODO(b/147750355): Pass locusId and bundle to the Content Capture. + } + /** Return the application that owns this activity. */ public final Application getApplication() { return mApplication; diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl index 7d04ca0afe7e..3ffd7c70b40d 100644 --- a/core/java/android/app/IActivityManager.aidl +++ b/core/java/android/app/IActivityManager.aidl @@ -53,6 +53,7 @@ import android.content.pm.ParceledListSlice; import android.content.pm.ProviderInfo; import android.content.pm.UserInfo; import android.content.res.Configuration; +import android.content.LocusId; import android.graphics.Bitmap; import android.graphics.GraphicBuffer; import android.graphics.Point; @@ -637,4 +638,13 @@ interface IActivityManager { * and the given process is imperceptible. */ void killProcessesWhenImperceptible(in int[] pids, String reason); + + /** + * Set locus context for a given activity. + * @param activity + * @param locusId a unique, stable id that identifies this activity instance from others. + * @param appToken ActivityRecord's appToken. + */ + void setActivityLocusContext(in ComponentName activity, in LocusId locusId, + in IBinder appToken); } diff --git a/core/java/android/app/usage/UsageEvents.java b/core/java/android/app/usage/UsageEvents.java index 6ab880dfb36d..ab71e73fd58c 100644 --- a/core/java/android/app/usage/UsageEvents.java +++ b/core/java/android/app/usage/UsageEvents.java @@ -290,10 +290,16 @@ public final class UsageEvents implements Parcelable { public static final int USER_STOPPED = 29; /** + * An event type denoting that new locusId has been set for a given activity. + * @hide + */ + public static final int LOCUS_ID_SET = 30; + + /** * Keep in sync with the greatest event type value. * @hide */ - public static final int MAX_EVENT_TYPE = 29; + public static final int MAX_EVENT_TYPE = 30; /** @hide */ public static final int FLAG_IS_PACKAGE_INSTANT_APP = 1 << 0; @@ -436,6 +442,18 @@ public final class UsageEvents implements Parcelable { */ public int mNotificationChannelIdToken = UNASSIGNED_TOKEN; + /** + * LocusId. + * Currently LocusId only present for {@link #LOCUS_ID_SET} event types. + * {@hide} + */ + public String mLocusId; + + /** + * {@hide} + */ + public int mLocusIdToken = UNASSIGNED_TOKEN; + /** @hide */ @EventFlags public int mFlags; @@ -609,6 +627,16 @@ public final class UsageEvents implements Parcelable { return ret; } + /** + * Returns the locusId for this event if the event is of type {@link #LOCUS_ID_SET}, + * otherwise it returns null. + * @hide + */ + @Nullable + public String getLocusId() { + return mLocusId; + } + private void copyFrom(Event orig) { mPackage = orig.mPackage; mClass = orig.mClass; @@ -625,6 +653,7 @@ public final class UsageEvents implements Parcelable { mFlags = orig.mFlags; mBucketAndReason = orig.mBucketAndReason; mNotificationChannelId = orig.mNotificationChannelId; + mLocusId = orig.mLocusId; } } @@ -823,6 +852,9 @@ public final class UsageEvents implements Parcelable { case Event.NOTIFICATION_INTERRUPTION: p.writeString(event.mNotificationChannelId); break; + case Event.LOCUS_ID_SET: + p.writeString(event.mLocusId); + break; } p.writeInt(event.mFlags); } @@ -871,6 +903,7 @@ public final class UsageEvents implements Parcelable { eventOut.mContentType = null; eventOut.mContentAnnotations = null; eventOut.mNotificationChannelId = null; + eventOut.mLocusId = null; switch (eventOut.mEventType) { case Event.CONFIGURATION_CHANGE: @@ -891,6 +924,9 @@ public final class UsageEvents implements Parcelable { case Event.NOTIFICATION_INTERRUPTION: eventOut.mNotificationChannelId = p.readString(); break; + case Event.LOCUS_ID_SET: + eventOut.mLocusId = p.readString(); + break; } eventOut.mFlags = p.readInt(); } diff --git a/core/java/android/content/LocusId.aidl b/core/java/android/content/LocusId.aidl new file mode 100644 index 000000000000..eb98db06ccbf --- /dev/null +++ b/core/java/android/content/LocusId.aidl @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2020, 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 android.content; + +parcelable LocusId; diff --git a/core/proto/android/server/usagestatsservice_v2.proto b/core/proto/android/server/usagestatsservice_v2.proto index a28fcf3589f1..24b0728c29ec 100644 --- a/core/proto/android/server/usagestatsservice_v2.proto +++ b/core/proto/android/server/usagestatsservice_v2.proto @@ -99,6 +99,7 @@ message EventObfuscatedProto { optional int32 instance_id = 10; optional int32 task_root_package_token = 11; optional int32 task_root_class_token = 12; + optional int32 locus_id_token = 13; } /** @@ -117,6 +118,7 @@ message PendingEventProto { optional int32 instance_id = 10; optional string task_root_package = 11; optional string task_root_class = 12; + optional string locus_id = 13; } /** diff --git a/services/core/java/android/app/usage/UsageStatsManagerInternal.java b/services/core/java/android/app/usage/UsageStatsManagerInternal.java index f3647602e69b..a8be66990fff 100644 --- a/services/core/java/android/app/usage/UsageStatsManagerInternal.java +++ b/services/core/java/android/app/usage/UsageStatsManagerInternal.java @@ -16,10 +16,14 @@ package android.app.usage; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.UserIdInt; import android.app.usage.UsageStatsManager.StandbyBuckets; import android.content.ComponentName; +import android.content.LocusId; import android.content.res.Configuration; +import android.os.IBinder; import android.os.UserHandle; import android.os.UserManager; @@ -111,6 +115,20 @@ public abstract class UsageStatsManagerInternal { public abstract void reportContentProviderUsage(String name, String pkgName, @UserIdInt int userId); + + /** + * Reports locusId update for a given activity. + * + * @param activity The component name of the app. + * @param userId The user id of who uses the app. + * @param locusId The locusId a unique, stable id that identifies this activity. + * @param appToken ActivityRecord's appToken. + * {@link UsageEvents} + * @hide + */ + public abstract void reportLocusUpdate(@NonNull ComponentName activity, @UserIdInt int userId, + @Nullable LocusId locusId, @NonNull IBinder appToken); + /** * Prepares the UsageStatsService for shutdown. */ diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 5596b2fcb762..41a4bd433be5 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -199,6 +199,7 @@ import android.content.IIntentReceiver; import android.content.IIntentSender; import android.content.Intent; import android.content.IntentFilter; +import android.content.LocusId; import android.content.pm.ActivityInfo; import android.content.pm.ActivityPresentationInfo; import android.content.pm.ApplicationInfo; @@ -19642,4 +19643,19 @@ public class ActivityManagerService extends IActivityManager.Stub } } } + + @Override + public void setActivityLocusContext(ComponentName activity, LocusId locusId, IBinder appToken) { + final int callingUid = Binder.getCallingUid(); + final int userId = UserHandle.getCallingUserId(); + if (getPackageManagerInternalLocked().getPackageUid(activity.getPackageName(), + /*flags=*/ 0, userId) != callingUid) { + throw new SecurityException("Calling uid " + callingUid + " cannot set locusId" + + "for package " + activity.getPackageName()); + } + + if (mUsageStatsService != null) { + mUsageStatsService.reportLocusUpdate(activity, userId, locusId, appToken); + } + } } diff --git a/services/tests/servicestests/src/com/android/server/usage/IntervalStatsTests.java b/services/tests/servicestests/src/com/android/server/usage/IntervalStatsTests.java index f1b2ef811885..5d849c114e69 100644 --- a/services/tests/servicestests/src/com/android/server/usage/IntervalStatsTests.java +++ b/services/tests/servicestests/src/com/android/server/usage/IntervalStatsTests.java @@ -92,6 +92,9 @@ public class IntervalStatsTests { case UsageEvents.Event.NOTIFICATION_INTERRUPTION: event.mNotificationChannelId = "channel" + (i % 5); //"random" channel break; + case UsageEvents.Event.LOCUS_ID_SET: + event.mLocusId = "locus" + (i % 7); //"random" locus + break; } intervalStats.addEvent(event); diff --git a/services/tests/servicestests/src/com/android/server/usage/UsageStatsDatabaseTest.java b/services/tests/servicestests/src/com/android/server/usage/UsageStatsDatabaseTest.java index e6bb244ef05b..f1c39067994c 100644 --- a/services/tests/servicestests/src/com/android/server/usage/UsageStatsDatabaseTest.java +++ b/services/tests/servicestests/src/com/android/server/usage/UsageStatsDatabaseTest.java @@ -52,6 +52,7 @@ import java.util.Set; public class UsageStatsDatabaseTest { private static final int MAX_TESTED_VERSION = 5; + private static final int OLDER_VERSION_MAX_EVENT_TYPE = 29; protected Context mContext; private UsageStatsDatabase mUsageStatsDatabase; private File mTestDir; @@ -79,7 +80,7 @@ public class UsageStatsDatabaseTest { mUsageStatsDatabase = new UsageStatsDatabase(mTestDir); mUsageStatsDatabase.readMappingsLocked(); mUsageStatsDatabase.init(1); - populateIntervalStats(); + populateIntervalStats(MAX_TESTED_VERSION); clearUsageStatsFiles(); } @@ -117,7 +118,7 @@ public class UsageStatsDatabaseTest { return sb.toString(); } - private void populateIntervalStats() { + private void populateIntervalStats(int minVersion) { final int numberOfEvents = 3000; final int timeProgression = 23; long time = System.currentTimeMillis() - (numberOfEvents*timeProgression); @@ -147,9 +148,12 @@ public class UsageStatsDatabaseTest { final int instanceId = i % 11; event.mClass = ".fake.class.name" + instanceId; event.mTimeStamp = time; - event.mEventType = i % (MAX_EVENT_TYPE + 1); //"random" event type event.mInstanceId = instanceId; + int maxEventType = (minVersion < 5) ? OLDER_VERSION_MAX_EVENT_TYPE : MAX_EVENT_TYPE; + event.mEventType = i % (maxEventType + 1); //"random" event type + + final int rootPackageInt = (i % 5); // 5 "apps" start each task event.mTaskRootPackage = "fake.package.name" + rootPackageInt; @@ -174,6 +178,9 @@ public class UsageStatsDatabaseTest { //"random" channel event.mNotificationChannelId = "channel" + (i % 5); break; + case Event.LOCUS_ID_SET: + event.mLocusId = "locus" + (i % 7); //"random" locus + break; } mIntervalStats.addEvent(event); @@ -281,6 +288,10 @@ public class UsageStatsDatabaseTest { assertEquals(e1.mNotificationChannelIdToken, e2.mNotificationChannelIdToken, "Usage event " + debugId); break; + case Event.LOCUS_ID_SET: + assertEquals(e1.mLocusIdToken, e2.mLocusIdToken, + "Usage event " + debugId); + break; } // fallthrough case 4: // test fields added in version 4 @@ -392,6 +403,7 @@ public class UsageStatsDatabaseTest { * version and read the automatically upgraded files on disk in the new file format. */ void runVersionChangeTest(int oldVersion, int newVersion, int interval) throws IOException { + populateIntervalStats(oldVersion); // Write IntervalStats to disk in old version format UsageStatsDatabase prevDB = new UsageStatsDatabase(mTestDir, oldVersion); prevDB.readMappingsLocked(); diff --git a/services/usage/java/com/android/server/usage/IntervalStats.java b/services/usage/java/com/android/server/usage/IntervalStats.java index 8fb283adc740..8fadf5eb9333 100644 --- a/services/usage/java/com/android/server/usage/IntervalStats.java +++ b/services/usage/java/com/android/server/usage/IntervalStats.java @@ -28,6 +28,7 @@ import static android.app.usage.UsageEvents.Event.FOREGROUND_SERVICE_START; import static android.app.usage.UsageEvents.Event.FOREGROUND_SERVICE_STOP; import static android.app.usage.UsageEvents.Event.KEYGUARD_HIDDEN; import static android.app.usage.UsageEvents.Event.KEYGUARD_SHOWN; +import static android.app.usage.UsageEvents.Event.LOCUS_ID_SET; import static android.app.usage.UsageEvents.Event.NOTIFICATION_INTERRUPTION; import static android.app.usage.UsageEvents.Event.ROLLOVER_FOREGROUND_SERVICE; import static android.app.usage.UsageEvents.Event.SCREEN_INTERACTIVE; @@ -568,6 +569,16 @@ public class IntervalStats { continue; } break; + case LOCUS_ID_SET: + event.mLocusId = packagesTokenData.getString(packageToken, event.mLocusIdToken); + if (event.mLocusId == null) { + Slog.e(TAG, "Unable to parse locus " + event.mLocusIdToken + + " for package " + packageToken); + this.events.remove(i); + dataOmitted = true; + continue; + } + break; } } return dataOmitted; @@ -675,6 +686,12 @@ public class IntervalStats { packageToken, event.mPackage, event.mNotificationChannelId); } break; + case LOCUS_ID_SET: + if (!TextUtils.isEmpty(event.mLocusId)) { + event.mLocusIdToken = packagesTokenData.getTokenOrAdd(packageToken, + event.mPackage, event.mLocusId); + } + break; } } } diff --git a/services/usage/java/com/android/server/usage/UsageStatsProtoV2.java b/services/usage/java/com/android/server/usage/UsageStatsProtoV2.java index fe5da923597e..e4aa9fe0dc1e 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsProtoV2.java +++ b/services/usage/java/com/android/server/usage/UsageStatsProtoV2.java @@ -277,6 +277,10 @@ final class UsageStatsProtoV2 { event.mTaskRootClassToken = proto.readInt( EventObfuscatedProto.TASK_ROOT_CLASS_TOKEN) - 1; break; + case (int) EventObfuscatedProto.LOCUS_ID_TOKEN: + event.mLocusIdToken = proto.readInt( + EventObfuscatedProto.LOCUS_ID_TOKEN) - 1; + break; case ProtoInputStream.NO_MORE_FIELDS: // timeStamp was not read, assume default value 0 plus beginTime if (event.mTimeStamp == 0) { @@ -398,6 +402,11 @@ final class UsageStatsProtoV2 { proto.write(EventObfuscatedProto.SHORTCUT_ID_TOKEN, event.mShortcutIdToken + 1); } break; + case UsageEvents.Event.LOCUS_ID_SET: + if (event.mLocusIdToken != PackagesTokenData.UNASSIGNED_TOKEN) { + proto.write(EventObfuscatedProto.LOCUS_ID_TOKEN, event.mLocusIdToken + 1); + } + break; case UsageEvents.Event.NOTIFICATION_INTERRUPTION: if (event.mNotificationChannelIdToken != PackagesTokenData.UNASSIGNED_TOKEN) { proto.write(EventObfuscatedProto.NOTIFICATION_CHANNEL_ID_TOKEN, diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java index 5119e5824f7f..9a18f8cd3a46 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UsageStatsService.java @@ -21,6 +21,7 @@ import static android.app.usage.UsageEvents.Event.CONFIGURATION_CHANGE; import static android.app.usage.UsageEvents.Event.DEVICE_EVENT_PACKAGE_NAME; import static android.app.usage.UsageEvents.Event.DEVICE_SHUTDOWN; import static android.app.usage.UsageEvents.Event.FLUSH_TO_DISK; +import static android.app.usage.UsageEvents.Event.LOCUS_ID_SET; import static android.app.usage.UsageEvents.Event.NOTIFICATION_INTERRUPTION; import static android.app.usage.UsageEvents.Event.SHORTCUT_INVOCATION; import static android.app.usage.UsageEvents.Event.USER_STOPPED; @@ -30,6 +31,8 @@ import static android.app.usage.UsageStatsManager.USAGE_SOURCE_TASK_ROOT_ACTIVIT import android.Manifest; import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.AppOpsManager; import android.app.IUidObserver; @@ -52,6 +55,7 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.LocusId; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; @@ -1987,6 +1991,17 @@ public class UsageStatsService extends SystemService implements } @Override + public void reportLocusUpdate(@NonNull ComponentName activity, @UserIdInt int userId, + @Nullable LocusId locusId, @NonNull IBinder appToken) { + Event event = new Event(LOCUS_ID_SET, SystemClock.elapsedRealtime()); + event.mLocusId = locusId.getId(); + event.mPackage = activity.getPackageName(); + event.mClass = activity.getClassName(); + event.mInstanceId = appToken.hashCode(); + reportEventOrAddToQueue(userId, event); + } + + @Override public void reportContentProviderUsage(String name, String packageName, int userId) { mAppStandby.postReportContentProviderUsage(name, packageName, userId); } |