blob: 2298aa1dbf8359b92150ec77259da907f422adff [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 static android.app.usage.UsageEvents.Event.CHOOSER_ACTION;
import static android.app.usage.UsageEvents.Event.CONFIGURATION_CHANGE;
import static android.app.usage.UsageEvents.Event.DEVICE_SHUTDOWN;
import static android.app.usage.UsageEvents.Event.FLUSH_TO_DISK;
import static android.app.usage.UsageEvents.Event.NOTIFICATION_INTERRUPTION;
import static android.app.usage.UsageEvents.Event.SHORTCUT_INVOCATION;
import static android.app.usage.UsageStatsManager.USAGE_SOURCE_CURRENT_ACTIVITY;
import static android.app.usage.UsageStatsManager.USAGE_SOURCE_TASK_ROOT_ACTIVITY;
import android.Manifest;
import android.app.ActivityManager;
import android.app.AppOpsManager;
import android.app.IUidObserver;
import android.app.PendingIntent;
import android.app.admin.DeviceAdminInfo;
import android.app.admin.DevicePolicyManagerInternal;
import android.app.usage.AppStandbyInfo;
import android.app.usage.ConfigurationStats;
import android.app.usage.EventStats;
import android.app.usage.IUsageStatsManager;
import android.app.usage.UsageEvents;
import android.app.usage.UsageEvents.Event;
import android.app.usage.UsageStats;
import android.app.usage.UsageStatsManager;
import android.app.usage.UsageStatsManager.StandbyBuckets;
import android.app.usage.UsageStatsManager.UsageSource;
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.IBinder;
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.provider.Settings;
import android.util.ArraySet;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseIntArray;
import com.android.internal.content.PackageMonitor;
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;
import java.util.Set;
/**
* 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");
private static final char TOKEN_DELIMITER = '/';
// 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;
static final int MSG_UID_STATE_CHANGED = 3;
static final int MSG_REPORT_EVENT_TO_ALL_USERID = 4;
private final Object mLock = new Object();
Handler mHandler;
AppOpsManager mAppOps;
UserManager mUserManager;
PackageManager mPackageManager;
PackageManagerInternal mPackageManagerInternal;
PackageMonitor mPackageMonitor;
IDeviceIdleController mDeviceIdleController;
// Do not use directly. Call getDpmInternal() instead
DevicePolicyManagerInternal mDpmInternal;
private final SparseArray<UserUsageStatsService> mUserState = new SparseArray<>();
private final SparseIntArray mUidToKernelCounter = new SparseIntArray();
private File mUsageStatsDir;
long mRealTimeSnapshot;
long mSystemTimeSnapshot;
int mUsageSource;
/** Manages the standby state of apps. */
AppStandbyController mAppStandby;
/** Manages app time limit observers */
AppTimeLimitController mAppTimeLimit;
final SparseArray<ArraySet<String>> mUsageReporters = new SparseArray();
final SparseArray<ActivityData> mVisibleActivities = new SparseArray();
private static class ActivityData {
private final String mTaskRootPackage;
private final String mTaskRootClass;
private ActivityData(String taskRootPackage, String taskRootClass) {
mTaskRootPackage = taskRootPackage;
mTaskRootClass = taskRootClass;
}
}
private UsageStatsManagerInternal.AppIdleStateChangeListener mStandbyChangeListener =
new UsageStatsManagerInternal.AppIdleStateChangeListener() {
@Override
public void onAppIdleStateChanged(String packageName, int userId, boolean idle,
int bucket, int reason) {
Event event = new Event(Event.STANDBY_BUCKET_CHANGED,
SystemClock.elapsedRealtime());
event.mBucketAndReason = (bucket << 16) | (reason & 0xFFFF);
event.mPackage = packageName;
mHandler.obtainMessage(MSG_REPORT_EVENT, userId, 0, event).sendToTarget();
}
@Override
public void onParoleStateChanged(boolean isParoleOn) {
}
};
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());
mAppTimeLimit = new AppTimeLimitController(
new AppTimeLimitController.TimeLimitCallbackListener() {
@Override
public void onLimitReached(int observerId, int userId, long timeLimit,
long timeElapsed, PendingIntent callbackIntent) {
if (callbackIntent == null) return;
Intent intent = new Intent();
intent.putExtra(UsageStatsManager.EXTRA_OBSERVER_ID, observerId);
intent.putExtra(UsageStatsManager.EXTRA_TIME_LIMIT, timeLimit);
intent.putExtra(UsageStatsManager.EXTRA_TIME_USED, timeElapsed);
try {
callbackIntent.send(getContext(), 0, intent);
} catch (PendingIntent.CanceledException e) {
Slog.w(TAG, "Couldn't deliver callback: "
+ callbackIntent);
}
}
@Override
public void onSessionEnd(int observerId, int userId, long timeElapsed,
PendingIntent callbackIntent) {
if (callbackIntent == null) return;
Intent intent = new Intent();
intent.putExtra(UsageStatsManager.EXTRA_OBSERVER_ID, observerId);
intent.putExtra(UsageStatsManager.EXTRA_TIME_USED, timeElapsed);
try {
callbackIntent.send(getContext(), 0, intent);
} catch (PendingIntent.CanceledException e) {
Slog.w(TAG, "Couldn't deliver callback: "
+ callbackIntent);
}
}
}, mHandler.getLooper());
mAppStandby.addListener(mStandbyChangeListener);
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());
// Make sure we initialize the data, in case job scheduler needs it early.
getUserDataAndInitializeIfNeededLocked(UserHandle.USER_SYSTEM, mSystemTimeSnapshot);
}
@Override
public void onBootPhase(int phase) {
mAppStandby.onBootPhase(phase);
if (phase == PHASE_SYSTEM_SERVICES_READY) {
// initialize mDpmInternal
getDpmInternal();
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);
}
readUsageSourceSetting();
}
}
private DevicePolicyManagerInternal getDpmInternal() {
if (mDpmInternal == null) {
mDpmInternal = LocalServices.getService(DevicePolicyManagerInternal.class);
}
return mDpmInternal;
}
private void readUsageSourceSetting() {
synchronized (mLock) {
mUsageSource = Settings.Global.getInt(getContext().getContentResolver(),
Settings.Global.APP_TIME_LIMIT_USAGE_SOURCE, USAGE_SOURCE_TASK_ROOT_ACTIVITY);
}
}
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) {
mHandler.obtainMessage(MSG_UID_STATE_CHANGED, uid, procState).sendToTarget();
}
@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(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);
Event event = new Event(DEVICE_SHUTDOWN, SystemClock.elapsedRealtime());
event.mPackage = Event.DEVICE_EVENT_PACKAGE_NAME;
// orderly shutdown, the last event is DEVICE_SHUTDOWN.
reportEventToAllUserId(event);
flushToDiskLocked();
}
}
/**
* After power button is pressed for 3.5 seconds
* (as defined in {@link com.android.internal.R.integer#config_veryLongPressTimeout}),
* report DEVICE_SHUTDOWN event and persist the database. If the power button is pressed for 10
* seconds and the device is shutdown, the database is already persisted and we are not losing
* data.
* This method is called from PhoneWindowManager, do not synchronize on mLock otherwise
* PhoneWindowManager may be blocked.
*/
void prepareForPossibleShutdown() {
Event event = new Event(DEVICE_SHUTDOWN, SystemClock.elapsedRealtime());
event.mPackage = Event.DEVICE_EVENT_PACKAGE_NAME;
mHandler.obtainMessage(MSG_REPORT_EVENT_TO_ALL_USERID, event).sendToTarget();
mHandler.sendEmptyMessage(MSG_FLUSH_TO_DISK);
}
/**
* Called by the Binder stub.
*/
void reportEvent(Event event, int userId) {
synchronized (mLock) {
final long timeNow = checkAndGetTimeLocked();
final long elapsedRealtime = SystemClock.elapsedRealtime();
convertToSystemTimeLocked(event);
if (event.mPackage != null
&& mPackageManagerInternal.isPackageEphemeral(userId, event.mPackage)) {
event.mFlags |= Event.FLAG_IS_PACKAGE_INSTANT_APP;
}
switch (event.mEventType) {
case Event.ACTIVITY_RESUMED:
// check if this activity has already been resumed
if (mVisibleActivities.get(event.mInstanceId) != null) break;
mVisibleActivities.put(event.mInstanceId,
new ActivityData(event.mTaskRootPackage, event.mTaskRootClass));
try {
switch(mUsageSource) {
case USAGE_SOURCE_CURRENT_ACTIVITY:
mAppTimeLimit.noteUsageStart(event.mPackage, userId);
break;
case USAGE_SOURCE_TASK_ROOT_ACTIVITY:
default:
mAppTimeLimit.noteUsageStart(event.mTaskRootPackage, userId);
break;
}
} catch (IllegalArgumentException iae) {
Slog.e(TAG, "Failed to note usage start", iae);
}
break;
case Event.ACTIVITY_PAUSED:
if (event.mTaskRootPackage == null) {
// Task Root info is missing. Repair the event based on previous data
final ActivityData prevData = mVisibleActivities.get(event.mInstanceId);
if (prevData == null) {
Slog.w(TAG, "Unexpected activity event reported! (" + event.mPackage
+ "/" + event.mClass + " event : " + event.mEventType
+ " instanceId : " + event.mInstanceId + ")");
} else {
event.mTaskRootPackage = prevData.mTaskRootPackage;
event.mTaskRootClass = prevData.mTaskRootClass;
}
}
break;
case Event.ACTIVITY_DESTROYED:
// Treat activity destroys like activity stops.
event.mEventType = Event.ACTIVITY_STOPPED;
// Fallthrough
case Event.ACTIVITY_STOPPED:
final ActivityData prevData =
mVisibleActivities.removeReturnOld(event.mInstanceId);
if (prevData == null) {
// The activity stop was already handled.
return;
}
ArraySet<String> tokens;
synchronized (mUsageReporters) {
tokens = mUsageReporters.removeReturnOld(event.mInstanceId);
}
if (tokens != null) {
synchronized (tokens) {
final int size = tokens.size();
// Stop usage on behalf of a UsageReporter that stopped
for (int i = 0; i < size; i++) {
final String token = tokens.valueAt(i);
try {
mAppTimeLimit.noteUsageStop(
buildFullToken(event.mPackage, token), userId);
} catch (IllegalArgumentException iae) {
Slog.w(TAG, "Failed to stop usage for during reporter death: "
+ iae);
}
}
}
}
if (event.mTaskRootPackage == null) {
// Task Root info is missing. Repair the event based on previous data
event.mTaskRootPackage = prevData.mTaskRootPackage;
event.mTaskRootClass = prevData.mTaskRootClass;
}
try {
switch(mUsageSource) {
case USAGE_SOURCE_CURRENT_ACTIVITY:
mAppTimeLimit.noteUsageStop(event.mPackage, userId);
break;
case USAGE_SOURCE_TASK_ROOT_ACTIVITY:
default:
mAppTimeLimit.noteUsageStop(event.mTaskRootPackage, userId);
break;
}
} catch (IllegalArgumentException iae) {
Slog.w(TAG, "Failed to note usage stop", iae);
}
break;
}
final UserUsageStatsService service =
getUserDataAndInitializeIfNeededLocked(userId, timeNow);
service.reportEvent(event);
mAppStandby.reportEvent(event, elapsedRealtime, userId);
}
}
/**
* Some events like FLUSH_TO_DISK need to be sent to all userId.
* @param event
*/
void reportEventToAllUserId(Event event) {
synchronized (mLock) {
final int userCount = mUserState.size();
for (int i = 0; i < userCount; i++) {
Event copy = new Event(event);
reportEvent(copy, mUserState.keyAt(i));
}
}
}
/**
* Called by the Handler for message MSG_FLUSH_TO_DISK.
*/
void flushToDisk() {
synchronized (mLock) {
// Before flush to disk, report FLUSH_TO_DISK event to signal UsageStats to update app
// usage. In case of abrupt power shutdown like battery drain or cold temperature,
// all UsageStats has correct data up to last flush to disk.
// The FLUSH_TO_DISK event is an internal event, it will not show up in IntervalStats'
// EventList.
Event event = new Event(FLUSH_TO_DISK, SystemClock.elapsedRealtime());
reportEventToAllUserId(event);
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);
mAppTimeLimit.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.
*/
List<EventStats> queryEventStats(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.queryEventStats(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);
}
}
/**
* Called by the Binder stub.
*/
UsageEvents queryEventsForPackage(int userId, long beginTime, long endTime,
String packageName, boolean includeTaskRoot) {
synchronized (mLock) {
final long timeNow = checkAndGetTimeLocked();
if (!validRange(timeNow, beginTime, endTime)) {
return null;
}
final UserUsageStatsService service =
getUserDataAndInitializeIfNeededLocked(userId, timeNow);
return service.queryEventsForPackage(beginTime, endTime, packageName, includeTaskRoot);
}
}
private static boolean validRange(long currentTime, long beginTime, long endTime) {
return beginTime <= currentTime && beginTime < endTime;
}
private String buildFullToken(String packageName, String token) {
final StringBuilder sb = new StringBuilder(packageName.length() + token.length() + 1);
sb.append(packageName);
sb.append(TOKEN_DELIMITER);
sb.append(token);
return sb.toString();
}
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 compact = 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 ("-c".equals(arg)) {
compact = true;
} else if ("flush".equals(arg)) {
flushToDiskLocked();
pw.println("Flushed stats to disk");
return;
} else if ("is-app-standby-enabled".equals(arg)) {
pw.println(mAppStandby.mAppIdleEnabled);
return;
} else if ("apptimelimit".equals(arg)) {
if (i + 1 >= args.length) {
mAppTimeLimit.dump(null, pw);
} else {
final String[] remainingArgs =
Arrays.copyOfRange(args, i + 1, args.length);
mAppTimeLimit.dump(remainingArgs, pw);
}
return;
} else if ("file".equals(arg)) {
final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ");
if (i + 1 >= args.length) {
// dump everything for all users
final int numUsers = mUserState.size();
for (int user = 0; user < numUsers; user++) {
ipw.println("user=" + mUserState.keyAt(user));
ipw.increaseIndent();
mUserState.valueAt(user).dumpFile(ipw, null);
ipw.decreaseIndent();
}
} else {
final int user;
try {
user = Integer.valueOf(args[i + 1]);
} catch (NumberFormatException nfe) {
ipw.println("invalid user specified.");
return;
}
if (mUserState.indexOfKey(user) < 0) {
ipw.println("the specified user does not exist.");
return;
}
final String[] remainingArgs = Arrays.copyOfRange(
args, i + 2, args.length);
// dump everything for the specified user
mUserState.get(user).dumpFile(ipw, remainingArgs);
}
return;
} else if ("database-info".equals(arg)) {
final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ");
if (i + 1 >= args.length) {
// dump info for all users
final int numUsers = mUserState.size();
for (int user = 0; user < numUsers; user++) {
ipw.println("user=" + mUserState.keyAt(user));
ipw.increaseIndent();
mUserState.valueAt(user).dumpDatabaseInfo(ipw);
ipw.decreaseIndent();
}
} else {
final int user;
try {
user = Integer.valueOf(args[i + 1]);
} catch (NumberFormatException nfe) {
ipw.println("invalid user specified.");
return;
}
if (mUserState.indexOfKey(user) < 0) {
ipw.println("the specified user does not exist.");
return;
}
// dump info only for the specified user
mUserState.get(user).dumpDatabaseInfo(ipw);
}
return;
} else if (arg != null && !arg.startsWith("-")) {
// Anything else that doesn't start with '-' 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, compact);
idpw.println();
}
mAppStandby.dumpUser(idpw, userId, pkg);
idpw.decreaseIndent();
}
if (pkg == null) {
pw.println();
mAppStandby.dumpState(args, pw);
}
idpw.println();
idpw.printPair("Usage Source", UsageStatsManager.usageSourceToString(mUsageSource));
idpw.println();
mAppTimeLimit.dump(null, 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((Event) msg.obj, msg.arg1);
break;
case MSG_REPORT_EVENT_TO_ALL_USERID:
reportEventToAllUserId((Event) msg.obj);
break;
case MSG_FLUSH_TO_DISK:
flushToDisk();
break;
case MSG_REMOVE_USER:
onUserRemoved(msg.arg1);
break;
case MSG_UID_STATE_CHANGED: {
final int uid = msg.arg1;
final int procState = msg.arg2;
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);
}
}
}
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.noteOp(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;
}
private boolean hasObserverPermission() {
final int callingUid = Binder.getCallingUid();
DevicePolicyManagerInternal dpmInternal = getDpmInternal();
if (callingUid == Process.SYSTEM_UID
|| (dpmInternal != null
&& dpmInternal.isActiveAdminWithPolicy(callingUid,
DeviceAdminInfo.USES_POLICY_PROFILE_OWNER))) {
// Caller is the system or the profile owner, so proceed.
return true;
}
return getContext().checkCallingPermission(Manifest.permission.OBSERVE_APP_USAGE)
== PackageManager.PERMISSION_GRANTED;
}
private boolean hasPermissions(String callingPackage, String... permissions) {
final int callingUid = Binder.getCallingUid();
if (callingUid == Process.SYSTEM_UID) {
// Caller is the system, so proceed.
return true;
}
boolean hasPermissions = true;
final Context context = getContext();
for (int i = 0; i < permissions.length; i++) {
hasPermissions = hasPermissions && (context.checkCallingPermission(permissions[i])
== PackageManager.PERMISSION_GRANTED);
}
return hasPermissions;
}
private void checkCallerIsSystemOrSameApp(String pkg) {
if (isCallingUidSystem()) {
return;
}
checkCallerIsSameApp(pkg);
}
private void checkCallerIsSameApp(String pkg) {
final int callingUid = Binder.getCallingUid();
final int callingUserId = UserHandle.getUserId(callingUid);
if (mPackageManagerInternal.getPackageUid(pkg, /*flags=*/ 0,
callingUserId) != callingUid) {
throw new SecurityException("Calling uid " + callingUid + " cannot query events"
+ "for package " + pkg);
}
}
private boolean isCallingUidSystem() {
final int uid = UserHandle.getAppId(Binder.getCallingUid()); // ignores user
return uid == Process.SYSTEM_UID;
}
@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 ParceledListSlice<EventStats> queryEventStats(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<EventStats> results =
UsageStatsService.this.queryEventStats(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 UsageEvents queryEventsForPackage(long beginTime, long endTime,
String callingPackage) {
final int callingUid = Binder.getCallingUid();
final int callingUserId = UserHandle.getUserId(callingUid);
checkCallerIsSameApp(callingPackage);
final boolean includeTaskRoot = hasPermission(callingPackage);
final long token = Binder.clearCallingIdentity();
try {
return UsageStatsService.this.queryEventsForPackage(callingUserId, beginTime,
endTime, callingPackage, includeTaskRoot);
} finally {
Binder.restoreCallingIdentity(token);
}
}
@Override
public UsageEvents queryEventsForUser(long beginTime, long endTime, int userId,
String callingPackage) {
if (!hasPermission(callingPackage)) {
return null;
}
if (userId != UserHandle.getCallingUserId()) {
getContext().enforceCallingPermission(
Manifest.permission.INTERACT_ACROSS_USERS_FULL,
"No permission to query usage stats for this user");
}
final boolean obfuscateInstantApps = shouldObfuscateInstantAppsForCaller(
Binder.getCallingUid(), UserHandle.getCallingUserId());
final long token = Binder.clearCallingIdentity();
try {
return UsageStatsService.this.queryEvents(userId, beginTime, endTime,
obfuscateInstantApps);
} finally {
Binder.restoreCallingIdentity(token);
}
}
@Override
public UsageEvents queryEventsForPackageForUser(long beginTime, long endTime,
int userId, String pkg, String callingPackage) {
if (!hasPermission(callingPackage)) {
return null;
}
if (userId != UserHandle.getCallingUserId()) {
getContext().enforceCallingPermission(
Manifest.permission.INTERACT_ACROSS_USERS_FULL,
"No permission to query usage stats for this user");
}
checkCallerIsSystemOrSameApp(pkg);
final long token = Binder.clearCallingIdentity();
try {
return UsageStatsService.this.queryEventsForPackage(userId, beginTime,
endTime, pkg, true);
} finally {
Binder.restoreCallingIdentity(token);
}
}
@Override
public boolean isAppInactive(String packageName, int userId) {
try {
userId = ActivityManager.getService().handleIncomingUser(Binder.getCallingPid(),
Binder.getCallingUid(), userId, false, false, "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) {
final int callingUid = Binder.getCallingUid();
try {
userId = ActivityManager.getService().handleIncomingUser(
Binder.getCallingPid(), callingUid, userId, false, false,
"getAppStandbyBucket", null);
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
final int packageUid = mPackageManagerInternal.getPackageUid(packageName, 0, userId);
// If the calling app is asking about itself, continue, else check for permission.
if (packageUid != callingUid) {
if (!hasPermission(callingPackage)) {
throw new SecurityException(
"Don't have permission to query app standby bucket");
}
}
if (packageUid < 0) {
throw new IllegalArgumentException(
"Cannot get standby bucket for non existent package (" + packageName + ")");
}
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");
if (bucket < UsageStatsManager.STANDBY_BUCKET_ACTIVE
|| bucket > UsageStatsManager.STANDBY_BUCKET_NEVER) {
throw new IllegalArgumentException("Cannot set the standby bucket to " + bucket);
}
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 boolean shellCaller = callingUid == 0 || callingUid == Process.SHELL_UID;
final boolean systemCaller = UserHandle.isCore(callingUid);
final int reason = systemCaller
? UsageStatsManager.REASON_MAIN_FORCED
: UsageStatsManager.REASON_MAIN_PREDICTED;
final long token = Binder.clearCallingIdentity();
try {
final int packageUid = mPackageManagerInternal.getPackageUid(packageName,
PackageManager.MATCH_ANY_USER | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
| PackageManager.MATCH_DIRECT_BOOT_AWARE, userId);
// Caller cannot set their own standby state
if (packageUid == callingUid) {
throw new IllegalArgumentException("Cannot set your own standby bucket");
}
if (packageUid < 0) {
throw new IllegalArgumentException(
"Cannot set standby bucket for non existent package (" + packageName
+ ")");
}
mAppStandby.setAppStandbyBucket(packageName, userId, bucket, reason,
SystemClock.elapsedRealtime(), shellCaller);
} finally {
Binder.restoreCallingIdentity(token);
}
}
@Override
public ParceledListSlice<AppStandbyInfo> getAppStandbyBuckets(String callingPackageName,
int userId) {
final int callingUid = Binder.getCallingUid();
try {
userId = ActivityManager.getService().handleIncomingUser(
Binder.getCallingPid(), callingUid, userId, false, false,
"getAppStandbyBucket", null);
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
if (!hasPermission(callingPackageName)) {
throw new SecurityException(
"Don't have permission to query app standby bucket");
}
final long token = Binder.clearCallingIdentity();
try {
final List<AppStandbyInfo> standbyBucketList =
mAppStandby.getAppStandbyBuckets(userId);
return (standbyBucketList == null) ? ParceledListSlice.emptyList()
: new ParceledListSlice<>(standbyBucketList);
} finally {
Binder.restoreCallingIdentity(token);
}
}
@Override
public void setAppStandbyBuckets(ParceledListSlice appBuckets, 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 boolean shellCaller = callingUid == 0 || callingUid == Process.SHELL_UID;
final int reason = shellCaller
? UsageStatsManager.REASON_MAIN_FORCED
: UsageStatsManager.REASON_MAIN_PREDICTED;
final long token = Binder.clearCallingIdentity();
try {
final long elapsedRealtime = SystemClock.elapsedRealtime();
List<AppStandbyInfo> bucketList = appBuckets.getList();
for (AppStandbyInfo bucketInfo : bucketList) {
final String packageName = bucketInfo.mPackageName;
final int bucket = bucketInfo.mStandbyBucket;
if (bucket < UsageStatsManager.STANDBY_BUCKET_ACTIVE
|| bucket > UsageStatsManager.STANDBY_BUCKET_NEVER) {
throw new IllegalArgumentException(
"Cannot set the standby bucket to " + bucket);
}
// Caller cannot set their own standby state
if (mPackageManagerInternal.getPackageUid(packageName,
PackageManager.MATCH_ANY_USER, userId) == callingUid) {
throw new IllegalArgumentException("Cannot set your own standby bucket");
}
mAppStandby.setAppStandbyBucket(packageName, userId, bucket, reason,
elapsedRealtime, shellCaller);
}
} 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;
}
Event event = new Event(CHOOSER_ACTION, SystemClock.elapsedRealtime());
event.mPackage = packageName;
event.mAction = action;
event.mContentType = contentType;
event.mContentAnnotations = annotations;
mHandler.obtainMessage(MSG_REPORT_EVENT, userId, 0, event).sendToTarget();
}
@Override
public void registerAppUsageObserver(int observerId,
String[] packages, long timeLimitMs, PendingIntent
callbackIntent, String callingPackage) {
if (!hasObserverPermission()) {
throw new SecurityException("Caller doesn't have OBSERVE_APP_USAGE permission");
}
if (packages == null || packages.length == 0) {
throw new IllegalArgumentException("Must specify at least one package");
}
if (callbackIntent == null) {
throw new NullPointerException("callbackIntent can't be null");
}
final int callingUid = Binder.getCallingUid();
final int userId = UserHandle.getUserId(callingUid);
final long token = Binder.clearCallingIdentity();
try {
UsageStatsService.this.registerAppUsageObserver(callingUid, observerId,
packages, timeLimitMs, callbackIntent, userId);
} finally {
Binder.restoreCallingIdentity(token);
}
}
@Override
public void unregisterAppUsageObserver(int observerId, String callingPackage) {
if (!hasObserverPermission()) {
throw new SecurityException("Caller doesn't have OBSERVE_APP_USAGE permission");
}
final int callingUid = Binder.getCallingUid();
final int userId = UserHandle.getUserId(callingUid);
final long token = Binder.clearCallingIdentity();
try {
UsageStatsService.this.unregisterAppUsageObserver(callingUid, observerId, userId);
} finally {
Binder.restoreCallingIdentity(token);
}
}
@Override
public void registerUsageSessionObserver(int sessionObserverId, String[] observed,
long timeLimitMs, long sessionThresholdTimeMs,
PendingIntent limitReachedCallbackIntent, PendingIntent sessionEndCallbackIntent,
String callingPackage) {
if (!hasObserverPermission()) {
throw new SecurityException("Caller doesn't have OBSERVE_APP_USAGE permission");
}
if (observed == null || observed.length == 0) {
throw new IllegalArgumentException("Must specify at least one observed entity");
}
if (limitReachedCallbackIntent == null) {
throw new NullPointerException("limitReachedCallbackIntent can't be null");
}
final int callingUid = Binder.getCallingUid();
final int userId = UserHandle.getUserId(callingUid);
final long token = Binder.clearCallingIdentity();
try {
UsageStatsService.this.registerUsageSessionObserver(callingUid, sessionObserverId,
observed, timeLimitMs, sessionThresholdTimeMs, limitReachedCallbackIntent,
sessionEndCallbackIntent, userId);
} finally {
Binder.restoreCallingIdentity(token);
}
}
@Override
public void unregisterUsageSessionObserver(int sessionObserverId, String callingPackage) {
if (!hasObserverPermission()) {
throw new SecurityException("Caller doesn't have OBSERVE_APP_USAGE permission");
}
final int callingUid = Binder.getCallingUid();
final int userId = UserHandle.getUserId(callingUid);
final long token = Binder.clearCallingIdentity();
try {
UsageStatsService.this.unregisterUsageSessionObserver(callingUid, sessionObserverId,
userId);
} finally {
Binder.restoreCallingIdentity(token);
}
}
@Override
public void registerAppUsageLimitObserver(int observerId, String[] packages,
long timeLimitMs, long timeUsedMs, PendingIntent callbackIntent,
String callingPackage) {
if (!hasPermissions(callingPackage,
Manifest.permission.SUSPEND_APPS, Manifest.permission.OBSERVE_APP_USAGE)) {
throw new SecurityException("Caller doesn't have both SUSPEND_APPS and "
+ "OBSERVE_APP_USAGE permissions");
}
if (packages == null || packages.length == 0) {
throw new IllegalArgumentException("Must specify at least one package");
}
if (callbackIntent == null && timeUsedMs < timeLimitMs) {
throw new NullPointerException("callbackIntent can't be null");
}
final int callingUid = Binder.getCallingUid();
final int userId = UserHandle.getUserId(callingUid);
final long token = Binder.clearCallingIdentity();
try {
UsageStatsService.this.registerAppUsageLimitObserver(callingUid, observerId,
packages, timeLimitMs, timeUsedMs, callbackIntent, userId);
} finally {
Binder.restoreCallingIdentity(token);
}
}
@Override
public void unregisterAppUsageLimitObserver(int observerId, String callingPackage) {
if (!hasPermissions(callingPackage,
Manifest.permission.SUSPEND_APPS, Manifest.permission.OBSERVE_APP_USAGE)) {
throw new SecurityException("Caller doesn't have both SUSPEND_APPS and "
+ "OBSERVE_APP_USAGE permissions");
}
final int callingUid = Binder.getCallingUid();
final int userId = UserHandle.getUserId(callingUid);
final long token = Binder.clearCallingIdentity();
try {
UsageStatsService.this.unregisterAppUsageLimitObserver(
callingUid, observerId, userId);
} finally {
Binder.restoreCallingIdentity(token);
}
}
@Override
public void reportUsageStart(IBinder activity, String token, String callingPackage) {
reportPastUsageStart(activity, token, 0, callingPackage);
}
@Override
public void reportPastUsageStart(IBinder activity, String token, long timeAgoMs,
String callingPackage) {
final int callingUid = Binder.getCallingUid();
final int userId = UserHandle.getUserId(callingUid);
final long binderToken = Binder.clearCallingIdentity();
try {
ArraySet<String> tokens;
synchronized (mUsageReporters) {
tokens = mUsageReporters.get(activity.hashCode());
if (tokens == null) {
tokens = new ArraySet();
mUsageReporters.put(activity.hashCode(), tokens);
}
}
synchronized (tokens) {
if (!tokens.add(token)) {
throw new IllegalArgumentException(token + " for " + callingPackage
+ " is already reported as started for this activity");
}
}
mAppTimeLimit.noteUsageStart(buildFullToken(callingPackage, token),
userId, timeAgoMs);
} finally {
Binder.restoreCallingIdentity(binderToken);
}
}
@Override
public void reportUsageStop(IBinder activity, String token, String callingPackage) {
final int callingUid = Binder.getCallingUid();
final int userId = UserHandle.getUserId(callingUid);
final long binderToken = Binder.clearCallingIdentity();
try {
ArraySet<String> tokens;
synchronized (mUsageReporters) {
tokens = mUsageReporters.get(activity.hashCode());
if (tokens == null) {
throw new IllegalArgumentException(
"Unknown reporter trying to stop token " + token + " for "
+ callingPackage);
}
}
synchronized (tokens) {
if (!tokens.remove(token)) {
throw new IllegalArgumentException(token + " for " + callingPackage
+ " is already reported as stopped for this activity");
}
}
mAppTimeLimit.noteUsageStop(buildFullToken(callingPackage, token), userId);
} finally {
Binder.restoreCallingIdentity(binderToken);
}
}
@Override
public @UsageSource int getUsageSource() {
if (!hasObserverPermission()) {
throw new SecurityException("Caller doesn't have OBSERVE_APP_USAGE permission");
}
synchronized (mLock) {
return mUsageSource;
}
}
@Override
public void forceUsageSourceSettingRead() {
readUsageSourceSetting();
}
}
void registerAppUsageObserver(int callingUid, int observerId, String[] packages,
long timeLimitMs, PendingIntent callbackIntent, int userId) {
mAppTimeLimit.addAppUsageObserver(callingUid, observerId, packages, timeLimitMs,
callbackIntent,
userId);
}
void unregisterAppUsageObserver(int callingUid, int observerId, int userId) {
mAppTimeLimit.removeAppUsageObserver(callingUid, observerId, userId);
}
void registerUsageSessionObserver(int callingUid, int observerId, String[] observed,
long timeLimitMs, long sessionThresholdTime, PendingIntent limitReachedCallbackIntent,
PendingIntent sessionEndCallbackIntent, int userId) {
mAppTimeLimit.addUsageSessionObserver(callingUid, observerId, observed, timeLimitMs,
sessionThresholdTime, limitReachedCallbackIntent, sessionEndCallbackIntent, userId);
}
void unregisterUsageSessionObserver(int callingUid, int sessionObserverId, int userId) {
mAppTimeLimit.removeUsageSessionObserver(callingUid, sessionObserverId, userId);
}
void registerAppUsageLimitObserver(int callingUid, int observerId, String[] packages,
long timeLimitMs, long timeUsedMs, PendingIntent callbackIntent, int userId) {
mAppTimeLimit.addAppUsageLimitObserver(callingUid, observerId, packages,
timeLimitMs, timeUsedMs, callbackIntent, userId);
}
void unregisterAppUsageLimitObserver(int callingUid, int observerId, int userId) {
mAppTimeLimit.removeAppUsageLimitObserver(callingUid, observerId, userId);
}
/**
* 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,
int instanceId, ComponentName taskRoot) {
if (component == null) {
Slog.w(TAG, "Event reported without a component name");
return;
}
Event event = new Event(eventType, SystemClock.elapsedRealtime());
event.mPackage = component.getPackageName();
event.mClass = component.getClassName();
event.mInstanceId = instanceId;
if (taskRoot == null) {
event.mTaskRootPackage = null;
event.mTaskRootClass = null;
} else {
event.mTaskRootPackage = taskRoot.getPackageName();
event.mTaskRootClass = taskRoot.getClassName();
}
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, eventType:" + eventType);
return;
}
Event event = new Event(eventType, SystemClock.elapsedRealtime());
event.mPackage = packageName;
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;
}
Event event = new Event(CONFIGURATION_CHANGE, SystemClock.elapsedRealtime());
event.mPackage = "android";
event.mConfiguration = new Configuration(config);
mHandler.obtainMessage(MSG_REPORT_EVENT, userId, 0, event).sendToTarget();
}
@Override
public void reportInterruptiveNotification(String packageName, String channelId,
int userId) {
if (packageName == null || channelId == null) {
Slog.w(TAG, "Event reported without a package name or a channel ID");
return;
}
Event event = new Event(NOTIFICATION_INTERRUPTION, SystemClock.elapsedRealtime());
event.mPackage = packageName.intern();
event.mNotificationChannelId = channelId.intern();
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;
}
Event event = new Event(SHORTCUT_INVOCATION, SystemClock.elapsedRealtime());
event.mPackage = packageName.intern();
event.mShortcutId = shortcutId.intern();
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.
UsageStatsService.this.shutdown();
}
@Override
public void prepareForPossibleShutdown() {
UsageStatsService.this.prepareForPossibleShutdown();
}
@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);
}
@Override
public void setLastJobRunTime(String packageName, int userId, long elapsedRealtime) {
mAppStandby.setLastJobRunTime(packageName, userId, elapsedRealtime);
}
@Override
public long getTimeSinceLastJobRun(String packageName, int userId) {
return mAppStandby.getTimeSinceLastJobRun(packageName, userId);
}
@Override
public void reportAppJobState(String packageName, int userId,
int numDeferredJobs, long timeSinceLastJobRun) {
}
@Override
public void onActiveAdminAdded(String packageName, int userId) {
mAppStandby.addActiveDeviceAdmin(packageName, userId);
}
@Override
public void setActiveAdminApps(Set<String> packageNames, int userId) {
mAppStandby.setActiveAdminApps(packageNames, userId);
}
@Override
public void onAdminDataAvailable() {
mAppStandby.onAdminDataAvailable();
}
@Override
public void reportSyncScheduled(String packageName, int userId, boolean exempted) {
mAppStandby.postReportSyncScheduled(packageName, userId, exempted);
}
@Override
public void reportExemptedSyncStart(String packageName, int userId) {
mAppStandby.postReportExemptedSyncStart(packageName, userId);
}
@Override
public AppUsageLimitData getAppUsageLimit(String packageName, UserHandle user) {
return mAppTimeLimit.getAppUsageLimit(packageName, user);
}
}
}