Merge "ShortcutManager: Implement usage report API" into nyc-mr1-dev
This commit is contained in:
@@ -6494,11 +6494,13 @@ package android.app.usage {
|
||||
method public android.content.res.Configuration getConfiguration();
|
||||
method public int getEventType();
|
||||
method public java.lang.String getPackageName();
|
||||
method public java.lang.String getShortcutId();
|
||||
method public long getTimeStamp();
|
||||
field public static final int CONFIGURATION_CHANGE = 5; // 0x5
|
||||
field public static final int MOVE_TO_BACKGROUND = 2; // 0x2
|
||||
field public static final int MOVE_TO_FOREGROUND = 1; // 0x1
|
||||
field public static final int NONE = 0; // 0x0
|
||||
field public static final int SHORTCUT_INVOCATION = 8; // 0x8
|
||||
field public static final int USER_INTERACTION = 7; // 0x7
|
||||
}
|
||||
|
||||
|
||||
@@ -6775,11 +6775,13 @@ package android.app.usage {
|
||||
method public android.content.res.Configuration getConfiguration();
|
||||
method public int getEventType();
|
||||
method public java.lang.String getPackageName();
|
||||
method public java.lang.String getShortcutId();
|
||||
method public long getTimeStamp();
|
||||
field public static final int CONFIGURATION_CHANGE = 5; // 0x5
|
||||
field public static final int MOVE_TO_BACKGROUND = 2; // 0x2
|
||||
field public static final int MOVE_TO_FOREGROUND = 1; // 0x1
|
||||
field public static final int NONE = 0; // 0x0
|
||||
field public static final int SHORTCUT_INVOCATION = 8; // 0x8
|
||||
field public static final int USER_INTERACTION = 7; // 0x7
|
||||
}
|
||||
|
||||
|
||||
@@ -6500,11 +6500,13 @@ package android.app.usage {
|
||||
method public android.content.res.Configuration getConfiguration();
|
||||
method public int getEventType();
|
||||
method public java.lang.String getPackageName();
|
||||
method public java.lang.String getShortcutId();
|
||||
method public long getTimeStamp();
|
||||
field public static final int CONFIGURATION_CHANGE = 5; // 0x5
|
||||
field public static final int MOVE_TO_BACKGROUND = 2; // 0x2
|
||||
field public static final int MOVE_TO_FOREGROUND = 1; // 0x1
|
||||
field public static final int NONE = 0; // 0x0
|
||||
field public static final int SHORTCUT_INVOCATION = 8; // 0x8
|
||||
field public static final int USER_INTERACTION = 7; // 0x7
|
||||
}
|
||||
|
||||
|
||||
@@ -78,6 +78,13 @@ public final class UsageEvents implements Parcelable {
|
||||
*/
|
||||
public static final int USER_INTERACTION = 7;
|
||||
|
||||
/**
|
||||
* An event type denoting that an action equivalent to a ShortcutInfo is taken by the user.
|
||||
*
|
||||
* @see android.content.pm.ShortcutManager#reportShortcutUsed(String)
|
||||
*/
|
||||
public static final int SHORTCUT_INVOCATION = 8;
|
||||
|
||||
/**
|
||||
* {@hide}
|
||||
*/
|
||||
@@ -104,6 +111,13 @@ public final class UsageEvents implements Parcelable {
|
||||
*/
|
||||
public Configuration mConfiguration;
|
||||
|
||||
/**
|
||||
* ID of the shortcut.
|
||||
* Only present for {@link #SHORTCUT_INVOCATION} event types.
|
||||
* {@hide}
|
||||
*/
|
||||
public String mShortcutId;
|
||||
|
||||
/**
|
||||
* The package name of the source of this event.
|
||||
*/
|
||||
@@ -145,6 +159,16 @@ public final class UsageEvents implements Parcelable {
|
||||
public Configuration getConfiguration() {
|
||||
return mConfiguration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ID of a {@link android.content.pm.ShortcutInfo} for this event
|
||||
* if the event is of type {@link #SHORTCUT_INVOCATION}, otherwise it returns null.
|
||||
*
|
||||
* @see android.content.pm.ShortcutManager#reportShortcutUsed(String)
|
||||
*/
|
||||
public String getShortcutId() {
|
||||
return mShortcutId;
|
||||
}
|
||||
}
|
||||
|
||||
// Only used when creating the resulting events. Not used for reading/unparceling.
|
||||
@@ -276,8 +300,13 @@ public final class UsageEvents implements Parcelable {
|
||||
p.writeInt(event.mEventType);
|
||||
p.writeLong(event.mTimeStamp);
|
||||
|
||||
if (event.mEventType == Event.CONFIGURATION_CHANGE) {
|
||||
event.mConfiguration.writeToParcel(p, flags);
|
||||
switch (event.mEventType) {
|
||||
case Event.CONFIGURATION_CHANGE:
|
||||
event.mConfiguration.writeToParcel(p, flags);
|
||||
break;
|
||||
case Event.SHORTCUT_INVOCATION:
|
||||
p.writeString(event.mShortcutId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -301,11 +330,18 @@ public final class UsageEvents implements Parcelable {
|
||||
eventOut.mEventType = p.readInt();
|
||||
eventOut.mTimeStamp = p.readLong();
|
||||
|
||||
// Extract the configuration for configuration change events.
|
||||
if (eventOut.mEventType == Event.CONFIGURATION_CHANGE) {
|
||||
eventOut.mConfiguration = Configuration.CREATOR.createFromParcel(p);
|
||||
} else {
|
||||
eventOut.mConfiguration = null;
|
||||
// Fill out the event-dependant fields.
|
||||
eventOut.mConfiguration = null;
|
||||
eventOut.mShortcutId = null;
|
||||
|
||||
switch (eventOut.mEventType) {
|
||||
case Event.CONFIGURATION_CHANGE:
|
||||
// Extract the configuration for configuration change events.
|
||||
eventOut.mConfiguration = Configuration.CREATOR.createFromParcel(p);
|
||||
break;
|
||||
case Event.SHORTCUT_INVOCATION:
|
||||
eventOut.mShortcutId = p.readString();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -55,6 +55,17 @@ public abstract class UsageStatsManagerInternal {
|
||||
*/
|
||||
public abstract void reportConfigurationChange(Configuration config, int userId);
|
||||
|
||||
/**
|
||||
* Reports that an action equivalent to a ShortcutInfo is taken by the user.
|
||||
*
|
||||
* @param packageName The package name of the shortcut publisher
|
||||
* @param shortcutId The ID of the shortcut in question
|
||||
* @param userId The user in which the content provider was accessed.
|
||||
*
|
||||
* @see android.content.pm.ShortcutManager#reportShortcutUsed(String)
|
||||
*/
|
||||
public abstract void reportShortcutUsage(String packageName, String shortcutId, int userId);
|
||||
|
||||
/**
|
||||
* Reports that a content provider has been accessed by a foreground app.
|
||||
* @param name The authority of the content provider
|
||||
|
||||
@@ -17,6 +17,7 @@ package android.content.pm;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.TestApi;
|
||||
import android.app.usage.UsageStatsManager;
|
||||
import android.content.Context;
|
||||
import android.os.RemoteException;
|
||||
import android.os.ServiceManager;
|
||||
@@ -357,6 +358,18 @@ public class ShortcutManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Applications that publish shortcuts should call this method whenever an action that's
|
||||
* equivalent to an existing shortcut has been taken by the user. This includes not only when
|
||||
* the user manually taps a shortcut, but when the user takes an equivalent action within the
|
||||
* application -- for example, when a music player application has a shortcut to playlist X,
|
||||
* then the application should not only report it when the playlist is opened from the
|
||||
* shortcut, but also when the user plays the playlist within the application.
|
||||
*
|
||||
* <p>The information is accessible via {@link UsageStatsManager#queryEvents}
|
||||
* Typically, launcher applications use this information to build a prediction model
|
||||
* so that they can promote the shortcuts that are likely to be used at the moment.
|
||||
*/
|
||||
public void reportShortcutUsed(String shortcutId) {
|
||||
try {
|
||||
mService.reportShortcutUsed(mContext.getPackageName(), shortcutId,
|
||||
|
||||
@@ -23,6 +23,7 @@ import android.app.ActivityManager;
|
||||
import android.app.ActivityManagerNative;
|
||||
import android.app.AppGlobals;
|
||||
import android.app.IUidObserver;
|
||||
import android.app.usage.UsageStatsManagerInternal;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
@@ -67,6 +68,7 @@ import android.text.format.Time;
|
||||
import android.util.ArraySet;
|
||||
import android.util.AtomicFile;
|
||||
import android.util.KeyValueListParser;
|
||||
import android.util.Log;
|
||||
import android.util.Slog;
|
||||
import android.util.SparseArray;
|
||||
import android.util.SparseIntArray;
|
||||
@@ -277,6 +279,7 @@ public class ShortcutService extends IShortcutService.Stub {
|
||||
private final IPackageManager mIPackageManager;
|
||||
private final PackageManagerInternal mPackageManagerInternal;
|
||||
private final UserManager mUserManager;
|
||||
private final UsageStatsManagerInternal mUsageStatsManagerInternal;
|
||||
|
||||
@GuardedBy("mLock")
|
||||
final SparseIntArray mUidState = new SparseIntArray();
|
||||
@@ -353,8 +356,11 @@ public class ShortcutService extends IShortcutService.Stub {
|
||||
LocalServices.addService(ShortcutServiceInternal.class, new LocalService());
|
||||
mHandler = new Handler(looper);
|
||||
mIPackageManager = AppGlobals.getPackageManager();
|
||||
mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
|
||||
mUserManager = context.getSystemService(UserManager.class);
|
||||
mPackageManagerInternal = Preconditions.checkNotNull(
|
||||
LocalServices.getService(PackageManagerInternal.class));
|
||||
mUserManager = Preconditions.checkNotNull(context.getSystemService(UserManager.class));
|
||||
mUsageStatsManagerInternal = Preconditions.checkNotNull(
|
||||
LocalServices.getService(UsageStatsManagerInternal.class));
|
||||
|
||||
mPackageMonitor.register(context, looper, UserHandle.ALL, /* externalStorage= */ false);
|
||||
|
||||
@@ -1740,7 +1746,28 @@ public class ShortcutService extends IShortcutService.Stub {
|
||||
public void reportShortcutUsed(String packageName, String shortcutId, int userId) {
|
||||
verifyCaller(packageName, userId);
|
||||
|
||||
// TODO Implement it.
|
||||
Preconditions.checkNotNull(shortcutId);
|
||||
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, String.format("reportShortcutUsed: Shortcut %s package %s used on user %d",
|
||||
shortcutId, packageName, userId));
|
||||
}
|
||||
|
||||
synchronized (mLock) {
|
||||
final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId);
|
||||
if (ps.findShortcutById(shortcutId) == null) {
|
||||
Log.w(TAG, String.format("reportShortcutUsed: package %s doesn't have shortcut %s",
|
||||
packageName, shortcutId));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
final long token = injectClearCallingIdentity();
|
||||
try {
|
||||
mUsageStatsManagerInternal.reportShortcutUsage(packageName, shortcutId, userId);
|
||||
} finally {
|
||||
injectRestoreCallingIdentity(token);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -36,6 +36,7 @@ import android.annotation.RequiresPermission;
|
||||
import android.annotation.UserIdInt;
|
||||
import android.app.Activity;
|
||||
import android.app.IUidObserver;
|
||||
import android.app.usage.UsageStatsManagerInternal;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
@@ -450,6 +451,7 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase {
|
||||
protected PackageManager mMockPackageManager;
|
||||
protected PackageManagerInternal mMockPackageManagerInternal;
|
||||
protected UserManager mMockUserManager;
|
||||
protected UsageStatsManagerInternal mMockUsageStatsManagerInternal;
|
||||
|
||||
protected static final String CALLING_PACKAGE_1 = "com.android.test.1";
|
||||
protected static final int CALLING_UID_1 = 10001;
|
||||
@@ -538,6 +540,12 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase {
|
||||
mMockPackageManager = mock(PackageManager.class);
|
||||
mMockPackageManagerInternal = mock(PackageManagerInternal.class);
|
||||
mMockUserManager = mock(UserManager.class);
|
||||
mMockUsageStatsManagerInternal = mock(UsageStatsManagerInternal.class);
|
||||
|
||||
LocalServices.removeServiceForTest(PackageManagerInternal.class);
|
||||
LocalServices.addService(PackageManagerInternal.class, mMockPackageManagerInternal);
|
||||
LocalServices.removeServiceForTest(UsageStatsManagerInternal.class);
|
||||
LocalServices.addService(UsageStatsManagerInternal.class, mMockUsageStatsManagerInternal);
|
||||
|
||||
// Prepare injection values.
|
||||
|
||||
|
||||
@@ -20,6 +20,13 @@ import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils
|
||||
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.parceled;
|
||||
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.set;
|
||||
|
||||
import static org.mockito.Matchers.anyInt;
|
||||
import static org.mockito.Matchers.anyString;
|
||||
import static org.mockito.Matchers.eq;
|
||||
import static org.mockito.Mockito.reset;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
import android.Manifest.permission;
|
||||
import android.app.ActivityManager;
|
||||
import android.content.ComponentName;
|
||||
@@ -1418,4 +1425,46 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest {
|
||||
assertEquals(3, mManager.getRemainingCallCount());
|
||||
});
|
||||
}
|
||||
|
||||
public void testReportShortcutUsed() {
|
||||
runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
|
||||
reset(mMockUsageStatsManagerInternal);
|
||||
|
||||
// Report with an nonexistent shortcut.
|
||||
mManager.reportShortcutUsed("s1");
|
||||
verify(mMockUsageStatsManagerInternal, times(0)).reportShortcutUsage(
|
||||
anyString(), anyString(), anyInt());
|
||||
|
||||
// Publish s2, but s1 still doesn't exist.
|
||||
mManager.setDynamicShortcuts(list(makeShortcut("s2")));
|
||||
mManager.reportShortcutUsed("s1");
|
||||
verify(mMockUsageStatsManagerInternal, times(0)).reportShortcutUsage(
|
||||
anyString(), anyString(), anyInt());
|
||||
|
||||
mManager.reportShortcutUsed("s2");
|
||||
verify(mMockUsageStatsManagerInternal, times(1)).reportShortcutUsage(
|
||||
eq(CALLING_PACKAGE_1), eq("s2"), eq(USER_10));
|
||||
|
||||
});
|
||||
runWithCaller(CALLING_PACKAGE_2, USER_10, () -> {
|
||||
// Try with a different package.
|
||||
reset(mMockUsageStatsManagerInternal);
|
||||
|
||||
// Report with an nonexistent shortcut.
|
||||
mManager.reportShortcutUsed("s2");
|
||||
verify(mMockUsageStatsManagerInternal, times(0)).reportShortcutUsage(
|
||||
anyString(), anyString(), anyInt());
|
||||
|
||||
// Publish s2, but s1 still doesn't exist.
|
||||
mManager.setDynamicShortcuts(list(makeShortcut("s3")));
|
||||
mManager.reportShortcutUsed("s2");
|
||||
verify(mMockUsageStatsManagerInternal, times(0)).reportShortcutUsage(
|
||||
anyString(), anyString(), anyInt());
|
||||
|
||||
mManager.reportShortcutUsed("s3");
|
||||
verify(mMockUsageStatsManagerInternal, times(1)).reportShortcutUsage(
|
||||
eq(CALLING_PACKAGE_2), eq("s3"), eq(USER_10));
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1411,6 +1411,24 @@ public class UsageStatsService extends SystemService implements
|
||||
mHandler.obtainMessage(MSG_REPORT_EVENT, userId, 0, event).sendToTarget();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportShortcutUsage(String packageName, String shortcutId, int userId) {
|
||||
if (packageName == null || shortcutId == null) {
|
||||
Slog.w(TAG, "Event reported without a package name or a shortcut ID");
|
||||
return;
|
||||
}
|
||||
|
||||
UsageEvents.Event event = new UsageEvents.Event();
|
||||
event.mPackage = packageName.intern();
|
||||
event.mShortcutId = shortcutId.intern();
|
||||
|
||||
// This will later be converted to system time.
|
||||
event.mTimeStamp = SystemClock.elapsedRealtime();
|
||||
|
||||
event.mEventType = Event.SHORTCUT_INVOCATION;
|
||||
mHandler.obtainMessage(MSG_REPORT_EVENT, userId, 0, event).sendToTarget();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportContentProviderUsage(String name, String packageName, int userId) {
|
||||
SomeArgs args = SomeArgs.obtain();
|
||||
|
||||
@@ -51,6 +51,7 @@ final class UsageStatsXmlV1 {
|
||||
private static final String ACTIVE_ATTR = "active";
|
||||
private static final String LAST_EVENT_ATTR = "lastEvent";
|
||||
private static final String TYPE_ATTR = "type";
|
||||
private static final String SHORTCUT_ID_ATTR = "shortcutId";
|
||||
|
||||
// Time attributes stored as an offset of the beginTime.
|
||||
private static final String LAST_TIME_ACTIVE_ATTR = "lastTimeActive";
|
||||
@@ -106,9 +107,15 @@ final class UsageStatsXmlV1 {
|
||||
event.mTimeStamp = statsOut.beginTime + XmlUtils.readLongAttribute(parser, TIME_ATTR);
|
||||
|
||||
event.mEventType = XmlUtils.readIntAttribute(parser, TYPE_ATTR);
|
||||
if (event.mEventType == UsageEvents.Event.CONFIGURATION_CHANGE) {
|
||||
event.mConfiguration = new Configuration();
|
||||
Configuration.readXmlAttrs(parser, event.mConfiguration);
|
||||
switch (event.mEventType) {
|
||||
case UsageEvents.Event.CONFIGURATION_CHANGE:
|
||||
event.mConfiguration = new Configuration();
|
||||
Configuration.readXmlAttrs(parser, event.mConfiguration);
|
||||
break;
|
||||
case UsageEvents.Event.SHORTCUT_INVOCATION:
|
||||
final String id = XmlUtils.readStringAttribute(parser, SHORTCUT_ID_ATTR);
|
||||
event.mShortcutId = (id != null) ? id.intern() : null;
|
||||
break;
|
||||
}
|
||||
|
||||
if (statsOut.events == null) {
|
||||
@@ -165,9 +172,17 @@ final class UsageStatsXmlV1 {
|
||||
}
|
||||
XmlUtils.writeIntAttribute(xml, TYPE_ATTR, event.mEventType);
|
||||
|
||||
if (event.mEventType == UsageEvents.Event.CONFIGURATION_CHANGE
|
||||
&& event.mConfiguration != null) {
|
||||
Configuration.writeXmlAttrs(xml, event.mConfiguration);
|
||||
switch (event.mEventType) {
|
||||
case UsageEvents.Event.CONFIGURATION_CHANGE:
|
||||
if (event.mConfiguration != null) {
|
||||
Configuration.writeXmlAttrs(xml, event.mConfiguration);
|
||||
}
|
||||
break;
|
||||
case UsageEvents.Event.SHORTCUT_INVOCATION:
|
||||
if (event.mShortcutId != null) {
|
||||
XmlUtils.writeStringAttribute(xml, SHORTCUT_ID_ATTR, event.mShortcutId);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
xml.endTag(null, EVENT_TAG);
|
||||
|
||||
@@ -549,6 +549,9 @@ class UserUsageStatsService {
|
||||
if (event.mConfiguration != null) {
|
||||
pw.printPair("config", Configuration.resourceQualifierString(event.mConfiguration));
|
||||
}
|
||||
if (event.mShortcutId != null) {
|
||||
pw.printPair("shortcutId", event.mShortcutId);
|
||||
}
|
||||
pw.println();
|
||||
}
|
||||
pw.decreaseIndent();
|
||||
@@ -588,6 +591,8 @@ class UserUsageStatsService {
|
||||
return "SYSTEM_INTERACTION";
|
||||
case UsageEvents.Event.USER_INTERACTION:
|
||||
return "USER_INTERACTION";
|
||||
case UsageEvents.Event.SHORTCUT_INVOCATION:
|
||||
return "SHORTCUT_INVOCATION";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user