| /** |
| * 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 (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, 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 && timeLimitMs != 0L) { |
| 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, 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, PendingIntent callbackIntent, int userId) { |
| mAppTimeLimit.addAppUsageLimitObserver(callingUid, observerId, packages, timeLimitMs, |
| 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 reportExemptedSyncScheduled(String packageName, int userId) { |
| mAppStandby.postReportExemptedSyncScheduled(packageName, userId); |
| } |
| |
| @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); |
| } |
| } |
| } |