Merge "Restrict access to instant app data in usage stats" into oc-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
f38a1d3d2a
@@ -15,10 +15,13 @@
|
||||
*/
|
||||
package android.app.usage;
|
||||
|
||||
import android.annotation.IntDef;
|
||||
import android.content.res.Configuration;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
@@ -28,6 +31,12 @@ import java.util.List;
|
||||
*/
|
||||
public final class UsageEvents implements Parcelable {
|
||||
|
||||
/** @hide */
|
||||
public static final String INSTANT_APP_PACKAGE_NAME = "android.instant_app";
|
||||
|
||||
/** @hide */
|
||||
public static final String INSTANT_APP_CLASS_NAME = "android.instant_class";
|
||||
|
||||
/**
|
||||
* An event representing a state change for a component.
|
||||
*/
|
||||
@@ -91,6 +100,17 @@ public final class UsageEvents implements Parcelable {
|
||||
*/
|
||||
public static final int CHOOSER_ACTION = 9;
|
||||
|
||||
/** @hide */
|
||||
public static final int FLAG_IS_PACKAGE_INSTANT_APP = 1 << 0;
|
||||
|
||||
/** @hide */
|
||||
@IntDef(flag = true,
|
||||
value = {
|
||||
FLAG_IS_PACKAGE_INSTANT_APP,
|
||||
})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface EventFlags {}
|
||||
|
||||
/**
|
||||
* {@hide}
|
||||
*/
|
||||
@@ -145,6 +165,27 @@ public final class UsageEvents implements Parcelable {
|
||||
*/
|
||||
public String[] mContentAnnotations;
|
||||
|
||||
/** @hide */
|
||||
@EventFlags
|
||||
public int mFlags;
|
||||
|
||||
public Event() {
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public Event(Event orig) {
|
||||
mPackage = orig.mPackage;
|
||||
mClass = orig.mClass;
|
||||
mTimeStamp = orig.mTimeStamp;
|
||||
mEventType = orig.mEventType;
|
||||
mConfiguration = orig.mConfiguration;
|
||||
mShortcutId = orig.mShortcutId;
|
||||
mAction = orig.mAction;
|
||||
mContentType = orig.mContentType;
|
||||
mContentAnnotations = orig.mContentAnnotations;
|
||||
mFlags = orig.mFlags;
|
||||
}
|
||||
|
||||
/**
|
||||
* The package name of the source of this event.
|
||||
*/
|
||||
@@ -196,6 +237,20 @@ public final class UsageEvents implements Parcelable {
|
||||
public String getShortcutId() {
|
||||
return mShortcutId;
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public Event getObfuscatedIfInstantApp() {
|
||||
if ((mFlags & FLAG_IS_PACKAGE_INSTANT_APP) == 0) {
|
||||
return this;
|
||||
}
|
||||
final Event ret = new Event(this);
|
||||
ret.mPackage = INSTANT_APP_PACKAGE_NAME;
|
||||
ret.mClass = INSTANT_APP_CLASS_NAME;
|
||||
|
||||
// Note there are other string fields too, but they're for app shortcuts and choosers,
|
||||
// which instant apps can't use anyway, so there's no need to hide them.
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
// Only used when creating the resulting events. Not used for reading/unparceling.
|
||||
|
||||
@@ -85,6 +85,17 @@ public final class UsageStats implements Parcelable {
|
||||
mChooserCounts = stats.mChooserCounts;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@hide}
|
||||
*/
|
||||
public UsageStats getObfuscatedForInstantApp() {
|
||||
final UsageStats ret = new UsageStats(this);
|
||||
|
||||
ret.mPackageName = UsageEvents.INSTANT_APP_PACKAGE_NAME;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
public String getPackageName() {
|
||||
return mPackageName;
|
||||
}
|
||||
|
||||
@@ -127,7 +127,12 @@ public abstract class UsageStatsManagerInternal {
|
||||
|
||||
public abstract void applyRestoredPayload(int user, String key, byte[] payload);
|
||||
|
||||
/* Cache Quota Service API */
|
||||
/**
|
||||
* Return usage stats.
|
||||
*
|
||||
* @param obfuscateInstantApps whether instant app package names need to be obfuscated in the
|
||||
* result.
|
||||
*/
|
||||
public abstract List<UsageStats> queryUsageStatsForUser(
|
||||
int userId, int interval, long beginTime, long endTime);
|
||||
int userId, int interval, long beginTime, long endTime, boolean obfuscateInstantApps);
|
||||
}
|
||||
|
||||
@@ -341,4 +341,7 @@ public abstract class PackageManagerInternal {
|
||||
* Return the taget SDK version for the app with the given UID.
|
||||
*/
|
||||
public abstract int getUidTargetSdkVersion(int uid);
|
||||
|
||||
/** Whether the binder caller can access instant apps. */
|
||||
public abstract boolean canAccessInstantApps(int callingUid);
|
||||
}
|
||||
|
||||
@@ -3480,6 +3480,31 @@ public class PackageManagerService extends IPackageManager.Stub
|
||||
return cur;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not a full application can see an instant application.
|
||||
* <p>
|
||||
* Currently, there are three cases in which this can occur:
|
||||
* <ol>
|
||||
* <li>The calling application is a "special" process. The special
|
||||
* processes are {@link Process#SYSTEM_UID}, {@link Process#SHELL_UID}
|
||||
* and {@code 0}</li>
|
||||
* <li>The calling application has the permission
|
||||
* {@link android.Manifest.permission#ACCESS_INSTANT_APPS}</li>
|
||||
* <li>[TODO] The calling application is the default launcher on the
|
||||
* system partition.</li>
|
||||
* </ol>
|
||||
*/
|
||||
private boolean canAccessInstantApps(int callingUid) {
|
||||
final boolean isSpecialProcess =
|
||||
callingUid == Process.SYSTEM_UID
|
||||
|| callingUid == Process.SHELL_UID
|
||||
|| callingUid == 0;
|
||||
final boolean allowMatchInstant =
|
||||
isSpecialProcess
|
||||
|| mContext.checkCallingOrSelfPermission(
|
||||
android.Manifest.permission.ACCESS_INSTANT_APPS) == PERMISSION_GRANTED;
|
||||
return allowMatchInstant;
|
||||
}
|
||||
private PackageInfo generatePackageInfo(PackageSetting ps, int flags, int userId) {
|
||||
if (!sUserManager.exists(userId)) return null;
|
||||
if (ps == null) {
|
||||
@@ -3489,19 +3514,15 @@ public class PackageManagerService extends IPackageManager.Stub
|
||||
if (p == null) {
|
||||
return null;
|
||||
}
|
||||
final int callingUid = Binder.getCallingUid();
|
||||
// Filter out ephemeral app metadata:
|
||||
// * The system/shell/root can see metadata for any app
|
||||
// * An installed app can see metadata for 1) other installed apps
|
||||
// and 2) ephemeral apps that have explicitly interacted with it
|
||||
// * Ephemeral apps can only see their own data and exposed installed apps
|
||||
// * Holding a signature permission allows seeing instant apps
|
||||
final int callingAppId = UserHandle.getAppId(Binder.getCallingUid());
|
||||
if (callingAppId != Process.SYSTEM_UID
|
||||
&& callingAppId != Process.SHELL_UID
|
||||
&& callingAppId != Process.ROOT_UID
|
||||
&& checkUidPermission(Manifest.permission.ACCESS_INSTANT_APPS,
|
||||
Binder.getCallingUid()) != PackageManager.PERMISSION_GRANTED) {
|
||||
final String instantAppPackageName = getInstantAppPackageName(Binder.getCallingUid());
|
||||
if (!canAccessInstantApps(callingUid)) {
|
||||
final String instantAppPackageName = getInstantAppPackageName(callingUid);
|
||||
if (instantAppPackageName != null) {
|
||||
// ephemeral apps can only get information on themselves or
|
||||
// installed apps that are exposed.
|
||||
@@ -3512,6 +3533,7 @@ public class PackageManagerService extends IPackageManager.Stub
|
||||
} else {
|
||||
if (ps.getInstantApp(userId)) {
|
||||
// only get access to the ephemeral app if we've been granted access
|
||||
final int callingAppId = UserHandle.getAppId(callingUid);
|
||||
if (!mInstantAppRegistry.isInstantAccessGranted(
|
||||
userId, callingAppId, ps.appId)) {
|
||||
return null;
|
||||
@@ -23844,6 +23866,11 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName());
|
||||
return getUidTargetSdkVersionLockedLPr(uid);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canAccessInstantApps(int callingUid) {
|
||||
return PackageManagerService.this.canAccessInstantApps(callingUid);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -177,7 +177,7 @@ public class CacheQuotaStrategy implements RemoteCallback.OnResultListener {
|
||||
UserInfo info = users.get(i);
|
||||
List<UsageStats> stats =
|
||||
mUsageStats.queryUsageStatsForUser(info.id, UsageStatsManager.INTERVAL_BEST,
|
||||
oneYearAgo, timeNow);
|
||||
oneYearAgo, timeNow, /*obfuscateInstantApps=*/ false);
|
||||
if (stats == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -39,6 +39,7 @@ import android.content.IntentFilter;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.PackageManagerInternal;
|
||||
import android.content.pm.PackageManager.NameNotFoundException;
|
||||
import android.content.pm.ParceledListSlice;
|
||||
import android.content.pm.UserInfo;
|
||||
@@ -80,6 +81,7 @@ import com.android.internal.os.SomeArgs;
|
||||
import com.android.internal.util.ArrayUtils;
|
||||
import com.android.internal.util.DumpUtils;
|
||||
import com.android.internal.util.IndentingPrintWriter;
|
||||
import com.android.server.LocalServices;
|
||||
import com.android.server.SystemService;
|
||||
|
||||
import java.io.File;
|
||||
@@ -137,6 +139,7 @@ public class UsageStatsService extends SystemService implements
|
||||
AppOpsManager mAppOps;
|
||||
UserManager mUserManager;
|
||||
PackageManager mPackageManager;
|
||||
PackageManagerInternal mPackageManagerInternal;
|
||||
AppWidgetManager mAppWidgetManager;
|
||||
IDeviceIdleController mDeviceIdleController;
|
||||
private DisplayManager mDisplayManager;
|
||||
@@ -179,6 +182,7 @@ public class UsageStatsService extends SystemService implements
|
||||
mAppOps = (AppOpsManager) getContext().getSystemService(Context.APP_OPS_SERVICE);
|
||||
mUserManager = (UserManager) getContext().getSystemService(Context.USER_SERVICE);
|
||||
mPackageManager = getContext().getPackageManager();
|
||||
mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
|
||||
mHandler = new H(BackgroundThread.get().getLooper());
|
||||
|
||||
File systemDataDir = new File(Environment.getDataDirectory(), "system");
|
||||
@@ -407,6 +411,10 @@ public class UsageStatsService extends SystemService implements
|
||||
}
|
||||
}
|
||||
|
||||
private boolean shouldObfuscateInstantAppsForCaller(int callingUid) {
|
||||
return !mPackageManagerInternal.canAccessInstantApps(callingUid);
|
||||
}
|
||||
|
||||
void clearAppIdleForPackage(String packageName, int userId) {
|
||||
synchronized (mAppIdleLock) {
|
||||
mAppIdleHistory.clearUsage(packageName, userId);
|
||||
@@ -704,6 +712,11 @@ public class UsageStatsService extends SystemService implements
|
||||
final long elapsedRealtime = SystemClock.elapsedRealtime();
|
||||
convertToSystemTimeLocked(event);
|
||||
|
||||
if (event.getPackageName() != null
|
||||
&& mPackageManagerInternal.isPackageEphemeral(userId, event.getPackageName())) {
|
||||
event.mFlags |= Event.FLAG_IS_PACKAGE_INSTANT_APP;
|
||||
}
|
||||
|
||||
final UserUsageStatsService service =
|
||||
getUserDataAndInitializeIfNeededLocked(userId, timeNow);
|
||||
service.reportEvent(event);
|
||||
@@ -807,7 +820,8 @@ public class UsageStatsService extends SystemService implements
|
||||
/**
|
||||
* Called by the Binder stub.
|
||||
*/
|
||||
List<UsageStats> queryUsageStats(int userId, int bucketType, long beginTime, long endTime) {
|
||||
List<UsageStats> queryUsageStats(int userId, int bucketType, long beginTime, long endTime,
|
||||
boolean obfuscateInstantApps) {
|
||||
synchronized (mLock) {
|
||||
final long timeNow = checkAndGetTimeLocked();
|
||||
if (!validRange(timeNow, beginTime, endTime)) {
|
||||
@@ -816,7 +830,20 @@ public class UsageStatsService extends SystemService implements
|
||||
|
||||
final UserUsageStatsService service =
|
||||
getUserDataAndInitializeIfNeededLocked(userId, timeNow);
|
||||
return service.queryUsageStats(bucketType, beginTime, endTime);
|
||||
List<UsageStats> list = service.queryUsageStats(bucketType, beginTime, endTime);
|
||||
|
||||
// Mangle instant app names *using their current state (not whether they were ephemeral
|
||||
// when the data was recorded)*.
|
||||
if (obfuscateInstantApps) {
|
||||
for (int i = list.size() - 1; i >= 0; i--) {
|
||||
final UsageStats stats = list.get(i);
|
||||
if (mPackageManagerInternal.isPackageEphemeral(userId, stats.mPackageName)) {
|
||||
list.set(i, stats.getObfuscatedForInstantApp());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -840,7 +867,8 @@ public class UsageStatsService extends SystemService implements
|
||||
/**
|
||||
* Called by the Binder stub.
|
||||
*/
|
||||
UsageEvents queryEvents(int userId, long beginTime, long endTime) {
|
||||
UsageEvents queryEvents(int userId, long beginTime, long endTime,
|
||||
boolean shouldObfuscateInstantApps) {
|
||||
synchronized (mLock) {
|
||||
final long timeNow = checkAndGetTimeLocked();
|
||||
if (!validRange(timeNow, beginTime, endTime)) {
|
||||
@@ -849,7 +877,7 @@ public class UsageStatsService extends SystemService implements
|
||||
|
||||
final UserUsageStatsService service =
|
||||
getUserDataAndInitializeIfNeededLocked(userId, timeNow);
|
||||
return service.queryEvents(beginTime, endTime);
|
||||
return service.queryEvents(beginTime, endTime, shouldObfuscateInstantApps);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -884,10 +912,15 @@ public class UsageStatsService extends SystemService implements
|
||||
}
|
||||
}
|
||||
|
||||
boolean isAppIdleFilteredOrParoled(String packageName, int userId, long elapsedRealtime) {
|
||||
boolean isAppIdleFilteredOrParoled(String packageName, int userId, long elapsedRealtime,
|
||||
boolean shouldObfuscateInstantApps) {
|
||||
if (isParoledOrCharging()) {
|
||||
return false;
|
||||
}
|
||||
if (shouldObfuscateInstantApps &&
|
||||
mPackageManagerInternal.isPackageEphemeral(userId, packageName)) {
|
||||
return false;
|
||||
}
|
||||
return isAppIdleFiltered(packageName, getAppId(packageName), userId, elapsedRealtime);
|
||||
}
|
||||
|
||||
@@ -1353,11 +1386,14 @@ public class UsageStatsService extends SystemService implements
|
||||
return null;
|
||||
}
|
||||
|
||||
final boolean obfuscateInstantApps = shouldObfuscateInstantAppsForCaller(
|
||||
Binder.getCallingUid());
|
||||
|
||||
final int userId = UserHandle.getCallingUserId();
|
||||
final long token = Binder.clearCallingIdentity();
|
||||
try {
|
||||
final List<UsageStats> results = UsageStatsService.this.queryUsageStats(
|
||||
userId, bucketType, beginTime, endTime);
|
||||
userId, bucketType, beginTime, endTime, obfuscateInstantApps);
|
||||
if (results != null) {
|
||||
return new ParceledListSlice<>(results);
|
||||
}
|
||||
@@ -1395,10 +1431,14 @@ public class UsageStatsService extends SystemService implements
|
||||
return null;
|
||||
}
|
||||
|
||||
final boolean obfuscateInstantApps = shouldObfuscateInstantAppsForCaller(
|
||||
Binder.getCallingUid());
|
||||
|
||||
final int userId = UserHandle.getCallingUserId();
|
||||
final long token = Binder.clearCallingIdentity();
|
||||
try {
|
||||
return UsageStatsService.this.queryEvents(userId, beginTime, endTime);
|
||||
return UsageStatsService.this.queryEvents(userId, beginTime, endTime,
|
||||
obfuscateInstantApps);
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(token);
|
||||
}
|
||||
@@ -1412,10 +1452,12 @@ public class UsageStatsService extends SystemService implements
|
||||
} catch (RemoteException re) {
|
||||
throw re.rethrowFromSystemServer();
|
||||
}
|
||||
final boolean obfuscateInstantApps = shouldObfuscateInstantAppsForCaller(
|
||||
Binder.getCallingUid());
|
||||
final long token = Binder.clearCallingIdentity();
|
||||
try {
|
||||
return UsageStatsService.this.isAppIdleFilteredOrParoled(packageName, userId,
|
||||
SystemClock.elapsedRealtime());
|
||||
SystemClock.elapsedRealtime(), obfuscateInstantApps);
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(token);
|
||||
}
|
||||
@@ -1647,9 +1689,10 @@ public class UsageStatsService extends SystemService implements
|
||||
|
||||
@Override
|
||||
public List<UsageStats> queryUsageStatsForUser(
|
||||
int userId, int intervalType, long beginTime, long endTime) {
|
||||
int userId, int intervalType, long beginTime, long endTime,
|
||||
boolean obfuscateInstantApps) {
|
||||
return UsageStatsService.this.queryUsageStats(
|
||||
userId, intervalType, beginTime, endTime);
|
||||
userId, intervalType, beginTime, endTime, obfuscateInstantApps);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,10 @@ import android.app.usage.TimeSparseArray;
|
||||
import android.app.usage.UsageEvents;
|
||||
import android.app.usage.UsageStats;
|
||||
import android.content.res.Configuration;
|
||||
import android.text.TextUtils;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.Log;
|
||||
import android.util.LogWriter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.ProtocolException;
|
||||
@@ -35,6 +38,8 @@ import java.net.ProtocolException;
|
||||
* UsageStats reader/writer for version 1 of the XML format.
|
||||
*/
|
||||
final class UsageStatsXmlV1 {
|
||||
private static final String TAG = "UsageStatsXmlV1";
|
||||
|
||||
private static final String PACKAGES_TAG = "packages";
|
||||
private static final String PACKAGE_TAG = "package";
|
||||
|
||||
@@ -51,6 +56,7 @@ final class UsageStatsXmlV1 {
|
||||
|
||||
// Attributes
|
||||
private static final String PACKAGE_ATTR = "package";
|
||||
private static final String FLAGS_ATTR = "flags";
|
||||
private static final String CLASS_ATTR = "class";
|
||||
private static final String TOTAL_TIME_ACTIVE_ATTR = "timeActive";
|
||||
private static final String COUNT_ATTR = "count";
|
||||
@@ -70,7 +76,6 @@ final class UsageStatsXmlV1 {
|
||||
if (pkg == null) {
|
||||
throw new ProtocolException("no " + PACKAGE_ATTR + " attribute present");
|
||||
}
|
||||
|
||||
final UsageStats stats = statsOut.getOrCreateUsageStats(pkg);
|
||||
|
||||
// Apply the offset to the beginTime to find the absolute time.
|
||||
@@ -149,11 +154,12 @@ final class UsageStatsXmlV1 {
|
||||
if (packageName == null) {
|
||||
throw new ProtocolException("no " + PACKAGE_ATTR + " attribute present");
|
||||
}
|
||||
|
||||
final String className = XmlUtils.readStringAttribute(parser, CLASS_ATTR);
|
||||
|
||||
final UsageEvents.Event event = statsOut.buildEvent(packageName, className);
|
||||
|
||||
event.mFlags = XmlUtils.readIntAttribute(parser, FLAGS_ATTR, 0);
|
||||
|
||||
// Apply the offset to the beginTime to find the absolute time of this event.
|
||||
event.mTimeStamp = statsOut.beginTime + XmlUtils.readLongAttribute(parser, TIME_ATTR);
|
||||
|
||||
@@ -256,6 +262,7 @@ final class UsageStatsXmlV1 {
|
||||
if (event.mClass != null) {
|
||||
XmlUtils.writeStringAttribute(xml, CLASS_ATTR, event.mClass);
|
||||
}
|
||||
XmlUtils.writeIntAttribute(xml, FLAGS_ATTR, event.mFlags);
|
||||
XmlUtils.writeIntAttribute(xml, TYPE_ATTR, event.mEventType);
|
||||
|
||||
switch (event.mEventType) {
|
||||
|
||||
@@ -19,11 +19,8 @@ package com.android.server.usage;
|
||||
import android.app.usage.ConfigurationStats;
|
||||
import android.app.usage.TimeSparseArray;
|
||||
import android.app.usage.UsageEvents;
|
||||
import android.app.usage.UsageEvents.Event;
|
||||
import android.app.usage.UsageStats;
|
||||
import android.app.usage.UsageStatsManager;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.res.Configuration;
|
||||
import android.os.SystemClock;
|
||||
import android.content.Context;
|
||||
@@ -312,7 +309,8 @@ class UserUsageStatsService {
|
||||
return queryStats(bucketType, beginTime, endTime, sConfigStatsCombiner);
|
||||
}
|
||||
|
||||
UsageEvents queryEvents(final long beginTime, final long endTime) {
|
||||
UsageEvents queryEvents(final long beginTime, final long endTime,
|
||||
boolean obfuscateInstantApps) {
|
||||
final ArraySet<String> names = new ArraySet<>();
|
||||
List<UsageEvents.Event> results = queryStats(UsageStatsManager.INTERVAL_DAILY,
|
||||
beginTime, endTime, new StatCombiner<UsageEvents.Event>() {
|
||||
@@ -334,7 +332,10 @@ class UserUsageStatsService {
|
||||
return;
|
||||
}
|
||||
|
||||
final UsageEvents.Event event = stats.events.valueAt(i);
|
||||
UsageEvents.Event event = stats.events.valueAt(i);
|
||||
if (obfuscateInstantApps) {
|
||||
event = event.getObfuscatedIfInstantApp();
|
||||
}
|
||||
names.add(event.mPackage);
|
||||
if (event.mClass != null) {
|
||||
names.add(event.mClass);
|
||||
@@ -586,6 +587,7 @@ class UserUsageStatsService {
|
||||
if (event.mShortcutId != null) {
|
||||
pw.printPair("shortcutId", event.mShortcutId);
|
||||
}
|
||||
pw.printHexPair("flags", event.mFlags);
|
||||
pw.println();
|
||||
}
|
||||
pw.decreaseIndent();
|
||||
|
||||
@@ -8,6 +8,8 @@ LOCAL_SRC_FILES := $(call all-java-files-under, src)
|
||||
|
||||
LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4
|
||||
|
||||
LOCAL_CERTIFICATE := platform
|
||||
|
||||
LOCAL_PACKAGE_NAME := UsageStatsTest
|
||||
|
||||
include $(BUILD_PACKAGE)
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<!--
|
||||
Note: Add android:sharedUserId="android.uid.system" to the root element to simulate the system UID
|
||||
caller case.
|
||||
-->
|
||||
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.android.tests.usagestats">
|
||||
package="com.android.tests.usagestats"
|
||||
>
|
||||
|
||||
<uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" />
|
||||
|
||||
|
||||
@@ -2,4 +2,6 @@
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:id="@+id/log"
|
||||
android:title="View Log"/>
|
||||
<item android:id="@+id/call_is_app_inactive"
|
||||
android:title="Call isAppInactive()"/>
|
||||
</menu>
|
||||
|
||||
@@ -16,12 +16,16 @@
|
||||
|
||||
package com.android.tests.usagestats;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.app.ListActivity;
|
||||
import android.app.usage.UsageStats;
|
||||
import android.app.usage.UsageStatsManager;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.text.InputType;
|
||||
import android.text.TextUtils;
|
||||
import android.text.format.DateUtils;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
@@ -30,6 +34,7 @@ import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.BaseAdapter;
|
||||
import android.widget.EditText;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -69,6 +74,9 @@ public class UsageStatsActivity extends ListActivity {
|
||||
case R.id.log:
|
||||
startActivity(new Intent(this, UsageLogActivity.class));
|
||||
return true;
|
||||
case R.id.call_is_app_inactive:
|
||||
callIsAppInactive();
|
||||
return true;
|
||||
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
@@ -81,6 +89,41 @@ public class UsageStatsActivity extends ListActivity {
|
||||
updateAdapter();
|
||||
}
|
||||
|
||||
private void callIsAppInactive() {
|
||||
final AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||
builder.setTitle("Enter package name");
|
||||
final EditText input = new EditText(this);
|
||||
input.setInputType(InputType.TYPE_CLASS_TEXT);
|
||||
input.setHint("com.android.tests.usagestats");
|
||||
builder.setView(input);
|
||||
|
||||
builder.setPositiveButton("OK", new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
final String packageName = input.getText().toString().trim();
|
||||
if (!TextUtils.isEmpty(packageName)) {
|
||||
showInactive(packageName);
|
||||
}
|
||||
}
|
||||
});
|
||||
builder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
dialog.cancel();
|
||||
}
|
||||
});
|
||||
|
||||
builder.show();
|
||||
}
|
||||
|
||||
private void showInactive(String packageName) {
|
||||
final AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||
builder.setMessage(
|
||||
"isAppInactive(\"" + packageName + "\") = "
|
||||
+ (mUsageStatsManager.isAppInactive(packageName) ? "true" : "false"));
|
||||
builder.show();
|
||||
}
|
||||
|
||||
private void updateAdapter() {
|
||||
long now = System.currentTimeMillis();
|
||||
long beginTime = now - USAGE_STATS_PERIOD;
|
||||
|
||||
Reference in New Issue
Block a user