Restrict access to instant app data in usage stats
- Events are obfuscated based on whether the app was instant or not at
the time each event was logged.
- UsageStats are obfuscated based on whether each app is instant or
not at the moment.
Bug 38202133
Test: Manual test using UsageStatsTest and instant apps
Change-Id: I3c74309196b88d010d317cb0dd6749bf4624e876
diff --git a/core/java/android/app/usage/UsageEvents.java b/core/java/android/app/usage/UsageEvents.java
index ce8b05a..0d7a941 100644
--- a/core/java/android/app/usage/UsageEvents.java
+++ b/core/java/android/app/usage/UsageEvents.java
@@ -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 @@
*/
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 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 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 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.
diff --git a/core/java/android/app/usage/UsageStats.java b/core/java/android/app/usage/UsageStats.java
index 0874095..7eef85c 100644
--- a/core/java/android/app/usage/UsageStats.java
+++ b/core/java/android/app/usage/UsageStats.java
@@ -85,6 +85,17 @@
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;
}
diff --git a/core/java/android/app/usage/UsageStatsManagerInternal.java b/core/java/android/app/usage/UsageStatsManagerInternal.java
index 08595dd..dbaace2 100644
--- a/core/java/android/app/usage/UsageStatsManagerInternal.java
+++ b/core/java/android/app/usage/UsageStatsManagerInternal.java
@@ -127,7 +127,12 @@
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);
}
diff --git a/core/java/android/content/pm/PackageManagerInternal.java b/core/java/android/content/pm/PackageManagerInternal.java
index 426f3cf..87e6a84 100644
--- a/core/java/android/content/pm/PackageManagerInternal.java
+++ b/core/java/android/content/pm/PackageManagerInternal.java
@@ -341,4 +341,7 @@
* 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);
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index d171f6e..acd9cfa 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -3480,6 +3480,31 @@
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 @@
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 @@
} 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;
@@ -23840,6 +23862,11 @@
return getUidTargetSdkVersionLockedLPr(uid);
}
}
+
+ @Override
+ public boolean canAccessInstantApps(int callingUid) {
+ return PackageManagerService.this.canAccessInstantApps(callingUid);
+ }
}
@Override
diff --git a/services/core/java/com/android/server/storage/CacheQuotaStrategy.java b/services/core/java/com/android/server/storage/CacheQuotaStrategy.java
index 82dd9ac..7a35bf7 100644
--- a/services/core/java/com/android/server/storage/CacheQuotaStrategy.java
+++ b/services/core/java/com/android/server/storage/CacheQuotaStrategy.java
@@ -177,7 +177,7 @@
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;
}
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 4ba457d..0de3c7c 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -39,6 +39,7 @@
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.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 @@
AppOpsManager mAppOps;
UserManager mUserManager;
PackageManager mPackageManager;
+ PackageManagerInternal mPackageManagerInternal;
AppWidgetManager mAppWidgetManager;
IDeviceIdleController mDeviceIdleController;
private DisplayManager mDisplayManager;
@@ -179,6 +182,7 @@
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 @@
}
}
+ 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 @@
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 @@
/**
* 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 @@
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 @@
/**
* 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 @@
final UserUsageStatsService service =
getUserDataAndInitializeIfNeededLocked(userId, timeNow);
- return service.queryEvents(beginTime, endTime);
+ return service.queryEvents(beginTime, endTime, shouldObfuscateInstantApps);
}
}
@@ -884,10 +912,15 @@
}
}
- 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 @@
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 @@
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 @@
} 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 @@
@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);
}
}
}
diff --git a/services/usage/java/com/android/server/usage/UsageStatsXmlV1.java b/services/usage/java/com/android/server/usage/UsageStatsXmlV1.java
index 96f3305..cc53a9c 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsXmlV1.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsXmlV1.java
@@ -26,7 +26,10 @@
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 @@
* 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 @@
// 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 @@
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 @@
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 @@
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) {
diff --git a/services/usage/java/com/android/server/usage/UserUsageStatsService.java b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
index 8d335a5..0abbb82 100644
--- a/services/usage/java/com/android/server/usage/UserUsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
@@ -19,11 +19,8 @@
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 @@
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 @@
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 @@
if (event.mShortcutId != null) {
pw.printPair("shortcutId", event.mShortcutId);
}
+ pw.printHexPair("flags", event.mFlags);
pw.println();
}
pw.decreaseIndent();
diff --git a/tests/UsageStatsTest/Android.mk b/tests/UsageStatsTest/Android.mk
index 5f7467a..6b5c999 100644
--- a/tests/UsageStatsTest/Android.mk
+++ b/tests/UsageStatsTest/Android.mk
@@ -8,6 +8,8 @@
LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4
+LOCAL_CERTIFICATE := platform
+
LOCAL_PACKAGE_NAME := UsageStatsTest
include $(BUILD_PACKAGE)
diff --git a/tests/UsageStatsTest/AndroidManifest.xml b/tests/UsageStatsTest/AndroidManifest.xml
index 589674a..c27be7b 100644
--- a/tests/UsageStatsTest/AndroidManifest.xml
+++ b/tests/UsageStatsTest/AndroidManifest.xml
@@ -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" />
diff --git a/tests/UsageStatsTest/res/menu/main.xml b/tests/UsageStatsTest/res/menu/main.xml
index e781058..4ccbc81 100644
--- a/tests/UsageStatsTest/res/menu/main.xml
+++ b/tests/UsageStatsTest/res/menu/main.xml
@@ -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>
diff --git a/tests/UsageStatsTest/src/com/android/tests/usagestats/UsageStatsActivity.java b/tests/UsageStatsTest/src/com/android/tests/usagestats/UsageStatsActivity.java
index c08c1a3..9429d9b 100644
--- a/tests/UsageStatsTest/src/com/android/tests/usagestats/UsageStatsActivity.java
+++ b/tests/UsageStatsTest/src/com/android/tests/usagestats/UsageStatsActivity.java
@@ -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.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 @@
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 @@
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;