UsageStatsService.java revision 7eb599b267d00cbde891c0a87924f2f5086f4497
1/** 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 * use this file except in compliance with the License. You may obtain a copy 6 * of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 * License for the specific language governing permissions and limitations 14 * under the License. 15 */ 16 17package com.android.server.usage; 18 19import android.Manifest; 20import android.app.AppOpsManager; 21import android.app.usage.ConfigurationStats; 22import android.app.usage.IUsageStatsManager; 23import android.app.usage.UsageEvents; 24import android.app.usage.UsageStats; 25import android.app.usage.UsageStatsManagerInternal; 26import android.content.BroadcastReceiver; 27import android.content.ComponentName; 28import android.content.Context; 29import android.content.Intent; 30import android.content.IntentFilter; 31import android.content.pm.PackageManager; 32import android.content.pm.ParceledListSlice; 33import android.content.pm.UserInfo; 34import android.content.res.Configuration; 35import android.os.Binder; 36import android.os.Debug; 37import android.os.Environment; 38import android.os.Handler; 39import android.os.Looper; 40import android.os.Message; 41import android.os.Process; 42import android.os.RemoteException; 43import android.os.SystemClock; 44import android.os.UserHandle; 45import android.os.UserManager; 46import android.util.ArraySet; 47import android.util.Slog; 48import android.util.SparseArray; 49 50import com.android.internal.os.BackgroundThread; 51import com.android.server.SystemService; 52 53import java.io.File; 54import java.util.Arrays; 55import java.util.List; 56 57/** 58 * A service that collects, aggregates, and persists application usage data. 59 * This data can be queried by apps that have been granted permission by AppOps. 60 */ 61public class UsageStatsService extends SystemService implements 62 UserUsageStatsService.StatsUpdatedListener { 63 static final String TAG = "UsageStatsService"; 64 65 static final boolean DEBUG = false; 66 private static final long TEN_SECONDS = 10 * 1000; 67 private static final long TWENTY_MINUTES = 20 * 60 * 1000; 68 private static final long FLUSH_INTERVAL = DEBUG ? TEN_SECONDS : TWENTY_MINUTES; 69 private static final long TIME_CHANGE_THRESHOLD_MILLIS = 2 * 1000; // Two seconds. 70 71 // Handler message types. 72 static final int MSG_REPORT_EVENT = 0; 73 static final int MSG_FLUSH_TO_DISK = 1; 74 static final int MSG_REMOVE_USER = 2; 75 76 private final Object mLock = new Object(); 77 Handler mHandler; 78 AppOpsManager mAppOps; 79 UserManager mUserManager; 80 81 private final SparseArray<UserUsageStatsService> mUserState = new SparseArray<>(); 82 private File mUsageStatsDir; 83 long mRealTimeSnapshot; 84 long mSystemTimeSnapshot; 85 86 public UsageStatsService(Context context) { 87 super(context); 88 } 89 90 @Override 91 public void onStart() { 92 mAppOps = (AppOpsManager) getContext().getSystemService(Context.APP_OPS_SERVICE); 93 mUserManager = (UserManager) getContext().getSystemService(Context.USER_SERVICE); 94 mHandler = new H(BackgroundThread.get().getLooper()); 95 96 File systemDataDir = new File(Environment.getDataDirectory(), "system"); 97 mUsageStatsDir = new File(systemDataDir, "usagestats"); 98 mUsageStatsDir.mkdirs(); 99 if (!mUsageStatsDir.exists()) { 100 throw new IllegalStateException("Usage stats directory does not exist: " 101 + mUsageStatsDir.getAbsolutePath()); 102 } 103 104 getContext().registerReceiver(new UserRemovedReceiver(), 105 new IntentFilter(Intent.ACTION_USER_REMOVED)); 106 107 synchronized (mLock) { 108 cleanUpRemovedUsersLocked(); 109 } 110 111 mRealTimeSnapshot = SystemClock.elapsedRealtime(); 112 mSystemTimeSnapshot = System.currentTimeMillis(); 113 114 publishLocalService(UsageStatsManagerInternal.class, new LocalService()); 115 publishBinderService(Context.USAGE_STATS_SERVICE, new BinderService()); 116 } 117 118 private class UserRemovedReceiver extends BroadcastReceiver { 119 120 @Override 121 public void onReceive(Context context, Intent intent) { 122 if (intent != null && intent.getAction().equals(Intent.ACTION_USER_REMOVED)) { 123 final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1); 124 if (userId >= 0) { 125 mHandler.obtainMessage(MSG_REMOVE_USER, userId, 0).sendToTarget(); 126 } 127 } 128 } 129 } 130 131 @Override 132 public void onStatsUpdated() { 133 mHandler.sendEmptyMessageDelayed(MSG_FLUSH_TO_DISK, FLUSH_INTERVAL); 134 } 135 136 private void cleanUpRemovedUsersLocked() { 137 final List<UserInfo> users = mUserManager.getUsers(true); 138 if (users == null || users.size() == 0) { 139 throw new IllegalStateException("There can't be no users"); 140 } 141 142 ArraySet<String> toDelete = new ArraySet<>(); 143 String[] fileNames = mUsageStatsDir.list(); 144 if (fileNames == null) { 145 // No users to delete. 146 return; 147 } 148 149 toDelete.addAll(Arrays.asList(fileNames)); 150 151 final int userCount = users.size(); 152 for (int i = 0; i < userCount; i++) { 153 final UserInfo userInfo = users.get(i); 154 toDelete.remove(Integer.toString(userInfo.id)); 155 } 156 157 final int deleteCount = toDelete.size(); 158 for (int i = 0; i < deleteCount; i++) { 159 deleteRecursively(new File(mUsageStatsDir, toDelete.valueAt(i))); 160 } 161 } 162 163 private static void deleteRecursively(File f) { 164 File[] files = f.listFiles(); 165 if (files != null) { 166 for (File subFile : files) { 167 deleteRecursively(subFile); 168 } 169 } 170 171 if (!f.delete()) { 172 Slog.e(TAG, "Failed to delete " + f); 173 } 174 } 175 176 private UserUsageStatsService getUserDataAndInitializeIfNeededLocked(int userId, 177 long currentTimeMillis) { 178 UserUsageStatsService service = mUserState.get(userId); 179 if (service == null) { 180 service = new UserUsageStatsService(userId, 181 new File(mUsageStatsDir, Integer.toString(userId)), this); 182 service.init(currentTimeMillis); 183 mUserState.put(userId, service); 184 } 185 return service; 186 } 187 188 /** 189 * This should be the only way to get the time from the system. 190 */ 191 private long checkAndGetTimeLocked() { 192 final long actualSystemTime = System.currentTimeMillis(); 193 final long actualRealtime = SystemClock.elapsedRealtime(); 194 final long expectedSystemTime = (actualRealtime - mRealTimeSnapshot) + mSystemTimeSnapshot; 195 if (Math.abs(actualSystemTime - expectedSystemTime) > TIME_CHANGE_THRESHOLD_MILLIS) { 196 // The time has changed. 197 final int userCount = mUserState.size(); 198 for (int i = 0; i < userCount; i++) { 199 final UserUsageStatsService service = mUserState.valueAt(i); 200 service.onTimeChanged(expectedSystemTime, actualSystemTime); 201 } 202 mRealTimeSnapshot = actualRealtime; 203 mSystemTimeSnapshot = actualSystemTime; 204 } 205 return actualSystemTime; 206 } 207 208 /** 209 * Assuming the event's timestamp is measured in milliseconds since boot, 210 * convert it to a system wall time. 211 */ 212 private void convertToSystemTimeLocked(UsageEvents.Event event) { 213 event.mTimeStamp = Math.max(0, event.mTimeStamp - mRealTimeSnapshot) + mSystemTimeSnapshot; 214 } 215 216 /** 217 * Called by the Binder stub 218 */ 219 void shutdown() { 220 synchronized (mLock) { 221 mHandler.removeMessages(MSG_REPORT_EVENT); 222 flushToDiskLocked(); 223 } 224 } 225 226 /** 227 * Called by the Binder stub. 228 */ 229 void reportEvent(UsageEvents.Event event, int userId) { 230 synchronized (mLock) { 231 final long timeNow = checkAndGetTimeLocked(); 232 convertToSystemTimeLocked(event); 233 234 final UserUsageStatsService service = 235 getUserDataAndInitializeIfNeededLocked(userId, timeNow); 236 service.reportEvent(event); 237 } 238 } 239 240 /** 241 * Called by the Binder stub. 242 */ 243 void flushToDisk() { 244 synchronized (mLock) { 245 flushToDiskLocked(); 246 } 247 } 248 249 /** 250 * Called by the Binder stub. 251 */ 252 void removeUser(int userId) { 253 synchronized (mLock) { 254 Slog.i(TAG, "Removing user " + userId + " and all data."); 255 mUserState.remove(userId); 256 cleanUpRemovedUsersLocked(); 257 } 258 } 259 260 /** 261 * Called by the Binder stub. 262 */ 263 List<UsageStats> queryUsageStats(int userId, int bucketType, long beginTime, long endTime) { 264 synchronized (mLock) { 265 final long timeNow = checkAndGetTimeLocked(); 266 if (!validRange(timeNow, beginTime, endTime)) { 267 return null; 268 } 269 270 final UserUsageStatsService service = 271 getUserDataAndInitializeIfNeededLocked(userId, timeNow); 272 return service.queryUsageStats(bucketType, beginTime, endTime); 273 } 274 } 275 276 /** 277 * Called by the Binder stub. 278 */ 279 List<ConfigurationStats> queryConfigurationStats(int userId, int bucketType, long beginTime, 280 long endTime) { 281 synchronized (mLock) { 282 final long timeNow = checkAndGetTimeLocked(); 283 if (!validRange(timeNow, beginTime, endTime)) { 284 return null; 285 } 286 287 final UserUsageStatsService service = 288 getUserDataAndInitializeIfNeededLocked(userId, timeNow); 289 return service.queryConfigurationStats(bucketType, beginTime, endTime); 290 } 291 } 292 293 /** 294 * Called by the Binder stub. 295 */ 296 UsageEvents queryEvents(int userId, long beginTime, long endTime) { 297 synchronized (mLock) { 298 final long timeNow = checkAndGetTimeLocked(); 299 if (!validRange(timeNow, beginTime, endTime)) { 300 return null; 301 } 302 303 final UserUsageStatsService service = 304 getUserDataAndInitializeIfNeededLocked(userId, timeNow); 305 return service.queryEvents(beginTime, endTime); 306 } 307 } 308 309 private static boolean validRange(long currentTime, long beginTime, long endTime) { 310 return beginTime <= currentTime && beginTime < endTime; 311 } 312 313 private void flushToDiskLocked() { 314 final int userCount = mUserState.size(); 315 for (int i = 0; i < userCount; i++) { 316 UserUsageStatsService service = mUserState.valueAt(i); 317 service.persistActiveStats(); 318 } 319 320 mHandler.removeMessages(MSG_FLUSH_TO_DISK); 321 } 322 323 class H extends Handler { 324 public H(Looper looper) { 325 super(looper); 326 } 327 328 @Override 329 public void handleMessage(Message msg) { 330 switch (msg.what) { 331 case MSG_REPORT_EVENT: 332 reportEvent((UsageEvents.Event) msg.obj, msg.arg1); 333 break; 334 335 case MSG_FLUSH_TO_DISK: 336 flushToDisk(); 337 break; 338 339 case MSG_REMOVE_USER: 340 removeUser(msg.arg1); 341 break; 342 343 default: 344 super.handleMessage(msg); 345 break; 346 } 347 } 348 } 349 350 private class BinderService extends IUsageStatsManager.Stub { 351 352 private boolean hasPermission(String callingPackage) { 353 final int callingUid = Binder.getCallingUid(); 354 if (callingUid == Process.SYSTEM_UID) { 355 return true; 356 } 357 final int mode = mAppOps.checkOp(AppOpsManager.OP_GET_USAGE_STATS, 358 callingUid, callingPackage); 359 if (mode == AppOpsManager.MODE_DEFAULT) { 360 // The default behavior here is to check if PackageManager has given the app 361 // permission. 362 return getContext().checkCallingPermission(Manifest.permission.PACKAGE_USAGE_STATS) 363 == PackageManager.PERMISSION_GRANTED; 364 } 365 return mode == AppOpsManager.MODE_ALLOWED; 366 } 367 368 @Override 369 public ParceledListSlice<UsageStats> queryUsageStats(int bucketType, long beginTime, 370 long endTime, String callingPackage) { 371 if (!hasPermission(callingPackage)) { 372 return null; 373 } 374 375 final int userId = UserHandle.getCallingUserId(); 376 final long token = Binder.clearCallingIdentity(); 377 try { 378 final List<UsageStats> results = UsageStatsService.this.queryUsageStats( 379 userId, bucketType, beginTime, endTime); 380 if (results != null) { 381 return new ParceledListSlice<>(results); 382 } 383 } finally { 384 Binder.restoreCallingIdentity(token); 385 } 386 return null; 387 } 388 389 @Override 390 public ParceledListSlice<ConfigurationStats> queryConfigurationStats(int bucketType, 391 long beginTime, long endTime, String callingPackage) throws RemoteException { 392 if (!hasPermission(callingPackage)) { 393 return null; 394 } 395 396 final int userId = UserHandle.getCallingUserId(); 397 final long token = Binder.clearCallingIdentity(); 398 try { 399 final List<ConfigurationStats> results = 400 UsageStatsService.this.queryConfigurationStats(userId, bucketType, 401 beginTime, endTime); 402 if (results != null) { 403 return new ParceledListSlice<>(results); 404 } 405 } finally { 406 Binder.restoreCallingIdentity(token); 407 } 408 return null; 409 } 410 411 @Override 412 public UsageEvents queryEvents(long beginTime, long endTime, String callingPackage) { 413 if (!hasPermission(callingPackage)) { 414 return null; 415 } 416 417 final int userId = UserHandle.getCallingUserId(); 418 final long token = Binder.clearCallingIdentity(); 419 try { 420 return UsageStatsService.this.queryEvents(userId, beginTime, endTime); 421 } finally { 422 Binder.restoreCallingIdentity(token); 423 } 424 } 425 } 426 427 /** 428 * This local service implementation is primarily used by ActivityManagerService. 429 * ActivityManagerService will call these methods holding the 'am' lock, which means we 430 * shouldn't be doing any IO work or other long running tasks in these methods. 431 */ 432 private class LocalService extends UsageStatsManagerInternal { 433 434 @Override 435 public void reportEvent(ComponentName component, int userId, int eventType) { 436 if (component == null) { 437 Slog.w(TAG, "Event reported without a component name"); 438 return; 439 } 440 441 UsageEvents.Event event = new UsageEvents.Event(); 442 event.mPackage = component.getPackageName(); 443 event.mClass = component.getClassName(); 444 445 // This will later be converted to system time. 446 event.mTimeStamp = SystemClock.elapsedRealtime(); 447 448 event.mEventType = eventType; 449 mHandler.obtainMessage(MSG_REPORT_EVENT, userId, 0, event).sendToTarget(); 450 } 451 452 @Override 453 public void reportConfigurationChange(Configuration config, int userId) { 454 if (config == null) { 455 Slog.w(TAG, "Configuration event reported with a null config"); 456 return; 457 } 458 459 UsageEvents.Event event = new UsageEvents.Event(); 460 event.mPackage = "android"; 461 462 // This will later be converted to system time. 463 event.mTimeStamp = SystemClock.elapsedRealtime(); 464 465 event.mEventType = UsageEvents.Event.CONFIGURATION_CHANGE; 466 event.mConfiguration = new Configuration(config); 467 mHandler.obtainMessage(MSG_REPORT_EVENT, userId, 0, event).sendToTarget(); 468 } 469 470 @Override 471 public void prepareShutdown() { 472 // This method *WILL* do IO work, but we must block until it is finished or else 473 // we might not shutdown cleanly. This is ok to do with the 'am' lock held, because 474 // we are shutting down. 475 shutdown(); 476 } 477 } 478} 479