blob: 65c1cefa5deabc3307c367ac10513f8f3e771a93 [file] [log] [blame]
/**
* Copyright (C) 2014 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 com.android.server.usage;
import android.Manifest;
import android.app.ActivityManager;
import android.app.AppOpsManager;
import android.app.IUidObserver;
import android.app.usage.AppStandby;
import android.app.usage.ConfigurationStats;
import android.app.usage.IUsageStatsManager;
import android.app.usage.UsageEvents;
import android.app.usage.AppStandby.StandbyBuckets;
import android.app.usage.UsageEvents.Event;
import android.app.usage.UsageStats;
import android.app.usage.UsageStatsManagerInternal;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.ParceledListSlice;
import android.content.pm.UserInfo;
import android.content.res.Configuration;
import android.os.Binder;
import android.os.Environment;
import android.os.FileUtils;
import android.os.Handler;
import android.os.IDeviceIdleController;
import android.os.Looper;
import android.os.Message;
import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.ArraySet;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseIntArray;
import com.android.internal.os.BackgroundThread;
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;
import java.io.FileDescriptor;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.List;
/**
* A service that collects, aggregates, and persists application usage data.
* This data can be queried by apps that have been granted permission by AppOps.
*/
public class UsageStatsService extends SystemService implements
UserUsageStatsService.StatsUpdatedListener {
static final String TAG = "UsageStatsService";
public static final boolean ENABLE_TIME_CHANGE_CORRECTION
= SystemProperties.getBoolean("persist.debug.time_correction", true);
static final boolean DEBUG = false; // Never submit with true
static final boolean COMPRESS_TIME = false;
private static final long TEN_SECONDS = 10 * 1000;
private static final long TWENTY_MINUTES = 20 * 60 * 1000;
private static final long FLUSH_INTERVAL = COMPRESS_TIME ? TEN_SECONDS : TWENTY_MINUTES;
private static final long TIME_CHANGE_THRESHOLD_MILLIS = 2 * 1000; // Two seconds.
private static final boolean ENABLE_KERNEL_UPDATES = true;
private static final File KERNEL_COUNTER_FILE = new File("/proc/uid_procstat/set");
// Handler message types.
static final int MSG_REPORT_EVENT = 0;
static final int MSG_FLUSH_TO_DISK = 1;
static final int MSG_REMOVE_USER = 2;
private final Object mLock = new Object();
Handler mHandler;
AppOpsManager mAppOps;
UserManager mUserManager;
PackageManager mPackageManager;
PackageManagerInternal mPackageManagerInternal;
IDeviceIdleController mDeviceIdleController;
private final SparseArray<UserUsageStatsService> mUserState = new SparseArray<>();
private final SparseIntArray mUidToKernelCounter = new SparseIntArray();
private File mUsageStatsDir;
long mRealTimeSnapshot;
long mSystemTimeSnapshot;
AppStandbyController mAppStandby;
public UsageStatsService(Context context) {
super(context);
}
@Override
public void onStart() {
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());
mAppStandby = new AppStandbyController(getContext(), BackgroundThread.get().getLooper());
File systemDataDir = new File(Environment.getDataDirectory(), "system");
mUsageStatsDir = new File(systemDataDir, "usagestats");
mUsageStatsDir.mkdirs();
if (!mUsageStatsDir.exists()) {
throw new IllegalStateException("Usage stats directory does not exist: "
+ mUsageStatsDir.getAbsolutePath());
}
IntentFilter filter = new IntentFilter(Intent.ACTION_USER_REMOVED);
filter.addAction(Intent.ACTION_USER_STARTED);
getContext().registerReceiverAsUser(new UserActionsReceiver(), UserHandle.ALL, filter,
null, mHandler);
synchronized (mLock) {
cleanUpRemovedUsersLocked();
}
mRealTimeSnapshot = SystemClock.elapsedRealtime();
mSystemTimeSnapshot = System.currentTimeMillis();
publishLocalService(UsageStatsManagerInternal.class, new LocalService());
publishBinderService(Context.USAGE_STATS_SERVICE, new BinderService());
}
@Override
public void onBootPhase(int phase) {
if (phase == PHASE_SYSTEM_SERVICES_READY) {
mAppStandby.onBootPhase(phase);
mDeviceIdleController = IDeviceIdleController.Stub.asInterface(
ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER));
if (ENABLE_KERNEL_UPDATES && KERNEL_COUNTER_FILE.exists()) {
try {
ActivityManager.getService().registerUidObserver(mUidObserver,
ActivityManager.UID_OBSERVER_PROCSTATE
| ActivityManager.UID_OBSERVER_GONE,
ActivityManager.PROCESS_STATE_UNKNOWN, null);
} catch (RemoteException e) {
throw new RuntimeException(e);
}
} else {
Slog.w(TAG, "Missing procfs interface: " + KERNEL_COUNTER_FILE);
}
}
}
private class UserActionsReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
final String action = intent.getAction();
if (Intent.ACTION_USER_REMOVED.equals(action)) {
if (userId >= 0) {
mHandler.obtainMessage(MSG_REMOVE_USER, userId, 0).sendToTarget();
}
} else if (Intent.ACTION_USER_STARTED.equals(action)) {
if (userId >=0) {
mAppStandby.postCheckIdleStates(userId);
}
}
}
}
private final IUidObserver mUidObserver = new IUidObserver.Stub() {
@Override
public void onUidStateChanged(int uid, int procState, long procStateSeq) {
final int newCounter = (procState <= ActivityManager.PROCESS_STATE_TOP) ? 0 : 1;
synchronized (mUidToKernelCounter) {
final int oldCounter = mUidToKernelCounter.get(uid, 0);
if (newCounter != oldCounter) {
mUidToKernelCounter.put(uid, newCounter);
try {
FileUtils.stringToFile(KERNEL_COUNTER_FILE, uid + " " + newCounter);
} catch (IOException e) {
Slog.w(TAG, "Failed to update counter set: " + e);
}
}
}
}
@Override
public void onUidIdle(int uid, boolean disabled) {
// Ignored
}
@Override
public void onUidGone(int uid, boolean disabled) {
onUidStateChanged(uid, ActivityManager.PROCESS_STATE_NONEXISTENT, 0);
}
@Override
public void onUidActive(int uid) {
// Ignored
}
@Override public void onUidCachedChanged(int uid, boolean cached) {
}
};
@Override
public void onStatsUpdated() {
mHandler.sendEmptyMessageDelayed(MSG_FLUSH_TO_DISK, FLUSH_INTERVAL);
}
@Override
public void onStatsReloaded() {
mAppStandby.postOneTimeCheckIdleStates();
}
@Override
public void onNewUpdate(int userId) {
mAppStandby.initializeDefaultsForSystemApps(userId);
}
private boolean shouldObfuscateInstantAppsForCaller(int callingUid, int userId) {
return !mPackageManagerInternal.canAccessInstantApps(callingUid, userId);
}
private void cleanUpRemovedUsersLocked() {
final List<UserInfo> users = mUserManager.getUsers(true);
if (users == null || users.size() == 0) {
throw new IllegalStateException("There can't be no users");
}
ArraySet<String> toDelete = new ArraySet<>();
String[] fileNames = mUsageStatsDir.list();
if (fileNames == null) {
// No users to delete.
return;
}
toDelete.addAll(Arrays.asList(fileNames));
final int userCount = users.size();
for (int i = 0; i < userCount; i++) {
final UserInfo userInfo = users.get(i);
toDelete.remove(Integer.toString(userInfo.id));
}
final int deleteCount = toDelete.size();
for (int i = 0; i < deleteCount; i++) {
deleteRecursively(new File(mUsageStatsDir, toDelete.valueAt(i)));
}
}
private static void deleteRecursively(File f) {
File[] files = f.listFiles();
if (files != null) {
for (File subFile : files) {
deleteRecursively(subFile);
}
}
if (!f.delete()) {
Slog.e(TAG, "Failed to delete " + f);
}
}
private UserUsageStatsService getUserDataAndInitializeIfNeededLocked(int userId,
long currentTimeMillis) {
UserUsageStatsService service = mUserState.get(userId);
if (service == null) {
service = new UserUsageStatsService(getContext(), userId,
new File(mUsageStatsDir, Integer.toString(userId)), this);
service.init(currentTimeMillis);
mUserState.put(userId, service);
}
return service;
}
/**
* This should be the only way to get the time from the system.
*/
private long checkAndGetTimeLocked() {
final long actualSystemTime = System.currentTimeMillis();
final long actualRealtime = SystemClock.elapsedRealtime();
final long expectedSystemTime = (actualRealtime - mRealTimeSnapshot) + mSystemTimeSnapshot;
final long diffSystemTime = actualSystemTime - expectedSystemTime;
if (Math.abs(diffSystemTime) > TIME_CHANGE_THRESHOLD_MILLIS
&& ENABLE_TIME_CHANGE_CORRECTION) {
// The time has changed.
Slog.i(TAG, "Time changed in UsageStats by " + (diffSystemTime / 1000) + " seconds");
final int userCount = mUserState.size();
for (int i = 0; i < userCount; i++) {
final UserUsageStatsService service = mUserState.valueAt(i);
service.onTimeChanged(expectedSystemTime, actualSystemTime);
}
mRealTimeSnapshot = actualRealtime;
mSystemTimeSnapshot = actualSystemTime;
}
return actualSystemTime;
}
/**
* Assuming the event's timestamp is measured in milliseconds since boot,
* convert it to a system wall time.
*/
private void convertToSystemTimeLocked(UsageEvents.Event event) {
event.mTimeStamp = Math.max(0, event.mTimeStamp - mRealTimeSnapshot) + mSystemTimeSnapshot;
}
/**
* Called by the Binder stub
*/
void shutdown() {
synchronized (mLock) {
mHandler.removeMessages(MSG_REPORT_EVENT);
flushToDiskLocked();
}
}
/**
* Called by the Binder stub.
*/
void reportEvent(UsageEvents.Event event, int userId) {
synchronized (mLock) {
final long timeNow = checkAndGetTimeLocked();
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);
mAppStandby.reportEvent(event, elapsedRealtime, userId);
}
}
/**
* Called by the Binder stub.
*/
void flushToDisk() {
synchronized (mLock) {
flushToDiskLocked();
}
}
/**
* Called by the Binder stub.
*/
void onUserRemoved(int userId) {
synchronized (mLock) {
Slog.i(TAG, "Removing user " + userId + " and all data.");
mUserState.remove(userId);
mAppStandby.onUserRemoved(userId);
cleanUpRemovedUsersLocked();
}
}
/**
* Called by the Binder stub.
*/
List<UsageStats> queryUsageStats(int userId, int bucketType, long beginTime, long endTime,
boolean obfuscateInstantApps) {
synchronized (mLock) {
final long timeNow = checkAndGetTimeLocked();
if (!validRange(timeNow, beginTime, endTime)) {
return null;
}
final UserUsageStatsService service =
getUserDataAndInitializeIfNeededLocked(userId, timeNow);
List<UsageStats> list = service.queryUsageStats(bucketType, beginTime, endTime);
if (list == null) {
return null;
}
// 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;
}
}
/**
* Called by the Binder stub.
*/
List<ConfigurationStats> queryConfigurationStats(int userId, int bucketType, long beginTime,
long endTime) {
synchronized (mLock) {
final long timeNow = checkAndGetTimeLocked();
if (!validRange(timeNow, beginTime, endTime)) {
return null;
}
final UserUsageStatsService service =
getUserDataAndInitializeIfNeededLocked(userId, timeNow);
return service.queryConfigurationStats(bucketType, beginTime, endTime);
}
}
/**
* Called by the Binder stub.
*/
UsageEvents queryEvents(int userId, long beginTime, long endTime,
boolean shouldObfuscateInstantApps) {
synchronized (mLock) {
final long timeNow = checkAndGetTimeLocked();
if (!validRange(timeNow, beginTime, endTime)) {
return null;
}
final UserUsageStatsService service =
getUserDataAndInitializeIfNeededLocked(userId, timeNow);
return service.queryEvents(beginTime, endTime, shouldObfuscateInstantApps);
}
}
private static boolean validRange(long currentTime, long beginTime, long endTime) {
return beginTime <= currentTime && beginTime < endTime;
}
private void flushToDiskLocked() {
final int userCount = mUserState.size();
for (int i = 0; i < userCount; i++) {
UserUsageStatsService service = mUserState.valueAt(i);
service.persistActiveStats();
mAppStandby.flushToDisk(mUserState.keyAt(i));
}
mAppStandby.flushDurationsToDisk();
mHandler.removeMessages(MSG_FLUSH_TO_DISK);
}
/**
* Called by the Binder stub.
*/
void dump(String[] args, PrintWriter pw) {
synchronized (mLock) {
IndentingPrintWriter idpw = new IndentingPrintWriter(pw, " ");
boolean checkin = false;
boolean history = false;
String pkg = null;
if (args != null) {
for (int i = 0; i < args.length; i++) {
String arg = args[i];
if ("--checkin".equals(arg)) {
checkin = true;
} else if ("--history".equals(arg)) {
history = true;
} else if ("history".equals(arg)) {
history = true;
break;
} else if ("flush".equals(arg)) {
flushToDiskLocked();
pw.println("Flushed stats to disk");
return;
} else {
// Anything else is a pkg to filter
pkg = arg;
break;
}
}
}
final int userCount = mUserState.size();
for (int i = 0; i < userCount; i++) {
int userId = mUserState.keyAt(i);
idpw.printPair("user", userId);
idpw.println();
idpw.increaseIndent();
if (checkin) {
mUserState.valueAt(i).checkin(idpw);
} else {
mUserState.valueAt(i).dump(idpw, pkg);
idpw.println();
if (history) {
mAppStandby.dumpHistory(idpw, userId);
}
}
mAppStandby.dumpUser(idpw, userId, pkg);
idpw.decreaseIndent();
}
if (pkg == null) {
pw.println();
mAppStandby.dumpState(args, pw);
}
}
}
class H extends Handler {
public H(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_REPORT_EVENT:
reportEvent((UsageEvents.Event) msg.obj, msg.arg1);
break;
case MSG_FLUSH_TO_DISK:
flushToDisk();
break;
case MSG_REMOVE_USER:
onUserRemoved(msg.arg1);
break;
default:
super.handleMessage(msg);
break;
}
}
}
private final class BinderService extends IUsageStatsManager.Stub {
private boolean hasPermission(String callingPackage) {
final int callingUid = Binder.getCallingUid();
if (callingUid == Process.SYSTEM_UID) {
return true;
}
final int mode = mAppOps.checkOp(AppOpsManager.OP_GET_USAGE_STATS,
callingUid, callingPackage);
if (mode == AppOpsManager.MODE_DEFAULT) {
// The default behavior here is to check if PackageManager has given the app
// permission.
return getContext().checkCallingPermission(Manifest.permission.PACKAGE_USAGE_STATS)
== PackageManager.PERMISSION_GRANTED;
}
return mode == AppOpsManager.MODE_ALLOWED;
}
@Override
public ParceledListSlice<UsageStats> queryUsageStats(int bucketType, long beginTime,
long endTime, String callingPackage) {
if (!hasPermission(callingPackage)) {
return null;
}
final boolean obfuscateInstantApps = shouldObfuscateInstantAppsForCaller(
Binder.getCallingUid(), UserHandle.getCallingUserId());
final int userId = UserHandle.getCallingUserId();
final long token = Binder.clearCallingIdentity();
try {
final List<UsageStats> results = UsageStatsService.this.queryUsageStats(
userId, bucketType, beginTime, endTime, obfuscateInstantApps);
if (results != null) {
return new ParceledListSlice<>(results);
}
} finally {
Binder.restoreCallingIdentity(token);
}
return null;
}
@Override
public ParceledListSlice<ConfigurationStats> queryConfigurationStats(int bucketType,
long beginTime, long endTime, String callingPackage) throws RemoteException {
if (!hasPermission(callingPackage)) {
return null;
}
final int userId = UserHandle.getCallingUserId();
final long token = Binder.clearCallingIdentity();
try {
final List<ConfigurationStats> results =
UsageStatsService.this.queryConfigurationStats(userId, bucketType,
beginTime, endTime);
if (results != null) {
return new ParceledListSlice<>(results);
}
} finally {
Binder.restoreCallingIdentity(token);
}
return null;
}
@Override
public UsageEvents queryEvents(long beginTime, long endTime, String callingPackage) {
if (!hasPermission(callingPackage)) {
return null;
}
final boolean obfuscateInstantApps = shouldObfuscateInstantAppsForCaller(
Binder.getCallingUid(), UserHandle.getCallingUserId());
final int userId = UserHandle.getCallingUserId();
final long token = Binder.clearCallingIdentity();
try {
return UsageStatsService.this.queryEvents(userId, beginTime, endTime,
obfuscateInstantApps);
} finally {
Binder.restoreCallingIdentity(token);
}
}
@Override
public boolean isAppInactive(String packageName, int userId) {
try {
userId = ActivityManager.getService().handleIncomingUser(Binder.getCallingPid(),
Binder.getCallingUid(), userId, false, true, "isAppInactive", null);
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
final boolean obfuscateInstantApps = shouldObfuscateInstantAppsForCaller(
Binder.getCallingUid(), userId);
final long token = Binder.clearCallingIdentity();
try {
return mAppStandby.isAppIdleFilteredOrParoled(
packageName, userId,
SystemClock.elapsedRealtime(), obfuscateInstantApps);
} finally {
Binder.restoreCallingIdentity(token);
}
}
@Override
public void setAppInactive(String packageName, boolean idle, int userId) {
final int callingUid = Binder.getCallingUid();
try {
userId = ActivityManager.getService().handleIncomingUser(
Binder.getCallingPid(), callingUid, userId, false, true,
"setAppInactive", null);
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
getContext().enforceCallingPermission(Manifest.permission.CHANGE_APP_IDLE_STATE,
"No permission to change app idle state");
final long token = Binder.clearCallingIdentity();
try {
final int appId = mAppStandby.getAppId(packageName);
if (appId < 0) return;
mAppStandby.setAppIdleAsync(packageName, idle, userId);
} finally {
Binder.restoreCallingIdentity(token);
}
}
@Override
public int getAppStandbyBucket(String packageName, String callingPackage, int userId) {
if (!hasPermission(callingPackage)) {
throw new SecurityException("Don't have permission to query app standby bucket");
}
final int callingUid = Binder.getCallingUid();
try {
userId = ActivityManager.getService().handleIncomingUser(
Binder.getCallingPid(), callingUid, userId, false, true,
"getAppStandbyBucket", null);
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
final boolean obfuscateInstantApps = shouldObfuscateInstantAppsForCaller(callingUid,
userId);
final long token = Binder.clearCallingIdentity();
try {
return mAppStandby.getAppStandbyBucket(packageName, userId,
SystemClock.elapsedRealtime(), obfuscateInstantApps);
} finally {
Binder.restoreCallingIdentity(token);
}
}
@Override
public void setAppStandbyBucket(String packageName,
int bucket, int userId) {
getContext().enforceCallingPermission(Manifest.permission.CHANGE_APP_IDLE_STATE,
"No permission to change app standby state");
final int callingUid = Binder.getCallingUid();
try {
userId = ActivityManager.getService().handleIncomingUser(
Binder.getCallingPid(), callingUid, userId, false, true,
"setAppStandbyBucket", null);
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
final long token = Binder.clearCallingIdentity();
try {
mAppStandby.setAppStandbyBucket(packageName, userId, bucket,
AppStandby.REASON_PREDICTED + ":" + callingUid,
SystemClock.elapsedRealtime());
} finally {
Binder.restoreCallingIdentity(token);
}
}
@Override
public void whitelistAppTemporarily(String packageName, long duration, int userId)
throws RemoteException {
StringBuilder reason = new StringBuilder(32);
reason.append("from:");
UserHandle.formatUid(reason, Binder.getCallingUid());
mDeviceIdleController.addPowerSaveTempWhitelistApp(packageName, duration, userId,
reason.toString());
}
@Override
public void onCarrierPrivilegedAppsChanged() {
if (DEBUG) {
Slog.i(TAG, "Carrier privileged apps changed");
}
getContext().enforceCallingOrSelfPermission(
android.Manifest.permission.BIND_CARRIER_SERVICES,
"onCarrierPrivilegedAppsChanged can only be called by privileged apps.");
mAppStandby.clearCarrierPrivilegedApps();
}
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpAndUsageStatsPermission(getContext(), TAG, pw)) return;
UsageStatsService.this.dump(args, pw);
}
@Override
public void reportChooserSelection(String packageName, int userId, String contentType,
String[] annotations, String action) {
if (packageName == null) {
Slog.w(TAG, "Event report user selecting a null package");
return;
}
UsageEvents.Event event = new UsageEvents.Event();
event.mPackage = packageName;
// This will later be converted to system time.
event.mTimeStamp = SystemClock.elapsedRealtime();
event.mEventType = Event.CHOOSER_ACTION;
event.mAction = action;
event.mContentType = contentType;
event.mContentAnnotations = annotations;
mHandler.obtainMessage(MSG_REPORT_EVENT, userId, 0, event).sendToTarget();
}
}
/**
* This local service implementation is primarily used by ActivityManagerService.
* ActivityManagerService will call these methods holding the 'am' lock, which means we
* shouldn't be doing any IO work or other long running tasks in these methods.
*/
private final class LocalService extends UsageStatsManagerInternal {
@Override
public void reportEvent(ComponentName component, int userId, int eventType) {
if (component == null) {
Slog.w(TAG, "Event reported without a component name");
return;
}
UsageEvents.Event event = new UsageEvents.Event();
event.mPackage = component.getPackageName();
event.mClass = component.getClassName();
// This will later be converted to system time.
event.mTimeStamp = SystemClock.elapsedRealtime();
event.mEventType = eventType;
mHandler.obtainMessage(MSG_REPORT_EVENT, userId, 0, event).sendToTarget();
}
@Override
public void reportEvent(String packageName, int userId, int eventType) {
if (packageName == null) {
Slog.w(TAG, "Event reported without a package name");
return;
}
UsageEvents.Event event = new UsageEvents.Event();
event.mPackage = packageName;
// This will later be converted to system time.
event.mTimeStamp = SystemClock.elapsedRealtime();
event.mEventType = eventType;
mHandler.obtainMessage(MSG_REPORT_EVENT, userId, 0, event).sendToTarget();
}
@Override
public void reportConfigurationChange(Configuration config, int userId) {
if (config == null) {
Slog.w(TAG, "Configuration event reported with a null config");
return;
}
UsageEvents.Event event = new UsageEvents.Event();
event.mPackage = "android";
// This will later be converted to system time.
event.mTimeStamp = SystemClock.elapsedRealtime();
event.mEventType = UsageEvents.Event.CONFIGURATION_CHANGE;
event.mConfiguration = new Configuration(config);
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) {
mAppStandby.postReportContentProviderUsage(name, packageName, userId);
}
@Override
public boolean isAppIdle(String packageName, int uidForAppId, int userId) {
return mAppStandby.isAppIdleFiltered(packageName, uidForAppId,
userId, SystemClock.elapsedRealtime());
}
@Override
@StandbyBuckets public int getAppStandbyBucket(String packageName, int userId,
long nowElapsed) {
return mAppStandby.getAppStandbyBucket(packageName, userId, nowElapsed, false);
}
@Override
public int[] getIdleUidsForUser(int userId) {
return mAppStandby.getIdleUidsForUser(userId);
}
@Override
public boolean isAppIdleParoleOn() {
return mAppStandby.isParoledOrCharging();
}
@Override
public void prepareShutdown() {
// This method *WILL* do IO work, but we must block until it is finished or else
// we might not shutdown cleanly. This is ok to do with the 'am' lock held, because
// we are shutting down.
shutdown();
}
@Override
public void addAppIdleStateChangeListener(AppIdleStateChangeListener listener) {
mAppStandby.addListener(listener);
listener.onParoleStateChanged(isAppIdleParoleOn());
}
@Override
public void removeAppIdleStateChangeListener(
AppIdleStateChangeListener listener) {
mAppStandby.removeListener(listener);
}
@Override
public byte[] getBackupPayload(int user, String key) {
// Check to ensure that only user 0's data is b/r for now
synchronized (mLock) {
if (user == UserHandle.USER_SYSTEM) {
final UserUsageStatsService userStats =
getUserDataAndInitializeIfNeededLocked(user, checkAndGetTimeLocked());
return userStats.getBackupPayload(key);
} else {
return null;
}
}
}
@Override
public void applyRestoredPayload(int user, String key, byte[] payload) {
synchronized (mLock) {
if (user == UserHandle.USER_SYSTEM) {
final UserUsageStatsService userStats =
getUserDataAndInitializeIfNeededLocked(user, checkAndGetTimeLocked());
userStats.applyRestoredPayload(key, payload);
}
}
}
@Override
public List<UsageStats> queryUsageStatsForUser(
int userId, int intervalType, long beginTime, long endTime,
boolean obfuscateInstantApps) {
return UsageStatsService.this.queryUsageStats(
userId, intervalType, beginTime, endTime, obfuscateInstantApps);
}
}
}