UsageStatsService.java revision 8cd28b57ed732656d002d97879e15c5695b54fff
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.RemoteException; 42import android.os.SystemClock; 43import android.os.UserHandle; 44import android.os.UserManager; 45import android.util.ArraySet; 46import android.util.Slog; 47import android.util.SparseArray; 48 49import com.android.internal.os.BackgroundThread; 50import com.android.server.SystemService; 51 52import java.io.File; 53import java.util.Arrays; 54import java.util.List; 55 56/** 57 * A service that collects, aggregates, and persists application usage data. 58 * This data can be queried by apps that have been granted permission by AppOps. 59 */ 60public class UsageStatsService extends SystemService implements 61 UserUsageStatsService.StatsUpdatedListener { 62 static final String TAG = "UsageStatsService"; 63 64 static final boolean DEBUG = false; 65 private static final long TEN_SECONDS = 10 * 1000; 66 private static final long TWENTY_MINUTES = 20 * 60 * 1000; 67 private static final long FLUSH_INTERVAL = DEBUG ? TEN_SECONDS : TWENTY_MINUTES; 68 69 // Handler message types. 70 static final int MSG_REPORT_EVENT = 0; 71 static final int MSG_FLUSH_TO_DISK = 1; 72 static final int MSG_REMOVE_USER = 2; 73 74 private final Object mLock = new Object(); 75 Handler mHandler; 76 AppOpsManager mAppOps; 77 UserManager mUserManager; 78 79 private final SparseArray<UserUsageStatsService> mUserState = new SparseArray<>(); 80 private File mUsageStatsDir; 81 long mRealTimeSnapshot; 82 long mSystemTimeSnapshot; 83 84 public UsageStatsService(Context context) { 85 super(context); 86 } 87 88 @Override 89 public void onStart() { 90 mAppOps = (AppOpsManager) getContext().getSystemService(Context.APP_OPS_SERVICE); 91 mUserManager = (UserManager) getContext().getSystemService(Context.USER_SERVICE); 92 mHandler = new H(BackgroundThread.get().getLooper()); 93 94 File systemDataDir = new File(Environment.getDataDirectory(), "system"); 95 mUsageStatsDir = new File(systemDataDir, "usagestats"); 96 mUsageStatsDir.mkdirs(); 97 if (!mUsageStatsDir.exists()) { 98 throw new IllegalStateException("Usage stats directory does not exist: " 99 + mUsageStatsDir.getAbsolutePath()); 100 } 101 102 getContext().registerReceiver(new UserRemovedReceiver(), 103 new IntentFilter(Intent.ACTION_USER_REMOVED)); 104 105 synchronized (mLock) { 106 cleanUpRemovedUsersLocked(); 107 } 108 109 mRealTimeSnapshot = SystemClock.elapsedRealtime(); 110 mSystemTimeSnapshot = System.currentTimeMillis(); 111 112 publishLocalService(UsageStatsManagerInternal.class, new LocalService()); 113 publishBinderService(Context.USAGE_STATS_SERVICE, new BinderService()); 114 } 115 116 private class UserRemovedReceiver extends BroadcastReceiver { 117 118 @Override 119 public void onReceive(Context context, Intent intent) { 120 if (intent != null && intent.getAction().equals(Intent.ACTION_USER_REMOVED)) { 121 final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1); 122 if (userId >= 0) { 123 mHandler.obtainMessage(MSG_REMOVE_USER, userId, 0).sendToTarget(); 124 } 125 } 126 } 127 } 128 129 @Override 130 public void onStatsUpdated() { 131 mHandler.sendEmptyMessageDelayed(MSG_FLUSH_TO_DISK, FLUSH_INTERVAL); 132 } 133 134 private void cleanUpRemovedUsersLocked() { 135 final List<UserInfo> users = mUserManager.getUsers(true); 136 if (users == null || users.size() == 0) { 137 throw new IllegalStateException("There can't be no users"); 138 } 139 140 ArraySet<String> toDelete = new ArraySet<>(); 141 String[] fileNames = mUsageStatsDir.list(); 142 if (fileNames == null) { 143 // No users to delete. 144 return; 145 } 146 147 toDelete.addAll(Arrays.asList(fileNames)); 148 149 final int userCount = users.size(); 150 for (int i = 0; i < userCount; i++) { 151 final UserInfo userInfo = users.get(i); 152 toDelete.remove(Integer.toString(userInfo.id)); 153 } 154 155 final int deleteCount = toDelete.size(); 156 for (int i = 0; i < deleteCount; i++) { 157 deleteRecursively(new File(mUsageStatsDir, toDelete.valueAt(i))); 158 } 159 } 160 161 private static void deleteRecursively(File f) { 162 File[] files = f.listFiles(); 163 if (files != null) { 164 for (File subFile : files) { 165 deleteRecursively(subFile); 166 } 167 } 168 169 if (!f.delete()) { 170 Slog.e(TAG, "Failed to delete " + f); 171 } 172 } 173 174 private UserUsageStatsService getUserDataAndInitializeIfNeededLocked(int userId) { 175 UserUsageStatsService service = mUserState.get(userId); 176 if (service == null) { 177 service = new UserUsageStatsService(userId, 178 new File(mUsageStatsDir, Integer.toString(userId)), this); 179 service.init(); 180 mUserState.put(userId, service); 181 } 182 return service; 183 } 184 185 /** 186 * Called by the Binder stub 187 */ 188 void shutdown() { 189 synchronized (mLock) { 190 mHandler.removeMessages(MSG_REPORT_EVENT); 191 flushToDiskLocked(); 192 } 193 } 194 195 /** 196 * Called by the Binder stub. 197 */ 198 void reportEvent(UsageEvents.Event event, int userId) { 199 synchronized (mLock) { 200 final UserUsageStatsService service = getUserDataAndInitializeIfNeededLocked(userId); 201 service.reportEvent(event); 202 } 203 } 204 205 /** 206 * Called by the Binder stub. 207 */ 208 void flushToDisk() { 209 synchronized (mLock) { 210 flushToDiskLocked(); 211 } 212 } 213 214 /** 215 * Called by the Binder stub. 216 */ 217 void removeUser(int userId) { 218 synchronized (mLock) { 219 Slog.i(TAG, "Removing user " + userId + " and all data."); 220 mUserState.remove(userId); 221 cleanUpRemovedUsersLocked(); 222 } 223 } 224 225 /** 226 * Called by the Binder stub. 227 */ 228 List<UsageStats> queryUsageStats(int userId, int bucketType, long beginTime, long endTime) { 229 if (!validRange(beginTime, endTime)) { 230 return null; 231 } 232 233 synchronized (mLock) { 234 UserUsageStatsService service = getUserDataAndInitializeIfNeededLocked(userId); 235 return service.queryUsageStats(bucketType, beginTime, endTime); 236 } 237 } 238 239 /** 240 * Called by the Binder stub. 241 */ 242 List<ConfigurationStats> queryConfigurationStats(int userId, int bucketType, long beginTime, 243 long endTime) { 244 if (!validRange(beginTime, endTime)) { 245 return null; 246 } 247 248 synchronized (mLock) { 249 UserUsageStatsService service = getUserDataAndInitializeIfNeededLocked(userId); 250 return service.queryConfigurationStats(bucketType, beginTime, endTime); 251 } 252 } 253 254 /** 255 * Called by the Binder stub. 256 */ 257 UsageEvents queryEvents(int userId, long beginTime, long endTime) { 258 if (!validRange(beginTime, endTime)) { 259 return null; 260 } 261 262 synchronized (mLock) { 263 UserUsageStatsService service = getUserDataAndInitializeIfNeededLocked(userId); 264 return service.queryEvents(beginTime, endTime); 265 } 266 } 267 268 private static boolean validRange(long beginTime, long endTime) { 269 final long timeNow = System.currentTimeMillis(); 270 return beginTime <= timeNow && beginTime < endTime; 271 } 272 273 private void flushToDiskLocked() { 274 final int userCount = mUserState.size(); 275 for (int i = 0; i < userCount; i++) { 276 UserUsageStatsService service = mUserState.valueAt(i); 277 service.persistActiveStats(); 278 } 279 280 mHandler.removeMessages(MSG_FLUSH_TO_DISK); 281 } 282 283 class H extends Handler { 284 public H(Looper looper) { 285 super(looper); 286 } 287 288 @Override 289 public void handleMessage(Message msg) { 290 switch (msg.what) { 291 case MSG_REPORT_EVENT: 292 reportEvent((UsageEvents.Event) msg.obj, msg.arg1); 293 break; 294 295 case MSG_FLUSH_TO_DISK: 296 flushToDisk(); 297 break; 298 299 case MSG_REMOVE_USER: 300 removeUser(msg.arg1); 301 break; 302 303 default: 304 super.handleMessage(msg); 305 break; 306 } 307 } 308 } 309 310 private class BinderService extends IUsageStatsManager.Stub { 311 312 private boolean hasPermission(String callingPackage) { 313 final int mode = mAppOps.checkOp(AppOpsManager.OP_GET_USAGE_STATS, 314 Binder.getCallingUid(), callingPackage); 315 if (mode == AppOpsManager.MODE_DEFAULT) { 316 // The default behavior here is to check if PackageManager has given the app 317 // permission. 318 return getContext().checkCallingPermission(Manifest.permission.PACKAGE_USAGE_STATS) 319 == PackageManager.PERMISSION_GRANTED; 320 } 321 return mode == AppOpsManager.MODE_ALLOWED; 322 } 323 324 @Override 325 public ParceledListSlice<UsageStats> queryUsageStats(int bucketType, long beginTime, 326 long endTime, String callingPackage) { 327 if (!hasPermission(callingPackage)) { 328 return null; 329 } 330 331 final int userId = UserHandle.getCallingUserId(); 332 final long token = Binder.clearCallingIdentity(); 333 try { 334 final List<UsageStats> results = UsageStatsService.this.queryUsageStats( 335 userId, bucketType, beginTime, endTime); 336 if (results != null) { 337 return new ParceledListSlice<>(results); 338 } 339 } finally { 340 Binder.restoreCallingIdentity(token); 341 } 342 return null; 343 } 344 345 @Override 346 public ParceledListSlice<ConfigurationStats> queryConfigurationStats(int bucketType, 347 long beginTime, long endTime, String callingPackage) throws RemoteException { 348 if (!hasPermission(callingPackage)) { 349 return null; 350 } 351 352 final int userId = UserHandle.getCallingUserId(); 353 final long token = Binder.clearCallingIdentity(); 354 try { 355 final List<ConfigurationStats> results = 356 UsageStatsService.this.queryConfigurationStats(userId, bucketType, 357 beginTime, endTime); 358 if (results != null) { 359 return new ParceledListSlice<>(results); 360 } 361 } finally { 362 Binder.restoreCallingIdentity(token); 363 } 364 return null; 365 } 366 367 @Override 368 public UsageEvents queryEvents(long beginTime, long endTime, String callingPackage) { 369 if (!hasPermission(callingPackage)) { 370 return null; 371 } 372 373 final int userId = UserHandle.getCallingUserId(); 374 final long token = Binder.clearCallingIdentity(); 375 try { 376 return UsageStatsService.this.queryEvents(userId, beginTime, endTime); 377 } finally { 378 Binder.restoreCallingIdentity(token); 379 } 380 } 381 } 382 383 /** 384 * This local service implementation is primarily used by ActivityManagerService. 385 * ActivityManagerService will call these methods holding the 'am' lock, which means we 386 * shouldn't be doing any IO work or other long running tasks in these methods. 387 */ 388 private class LocalService extends UsageStatsManagerInternal { 389 390 /** 391 * The system may have its time change, so at least make sure the events 392 * are monotonic in order. 393 */ 394 private long computeMonotonicSystemTime(long realTime) { 395 return (realTime - mRealTimeSnapshot) + mSystemTimeSnapshot; 396 } 397 398 @Override 399 public void reportEvent(ComponentName component, int userId, int eventType) { 400 if (component == null) { 401 Slog.w(TAG, "Event reported without a component name"); 402 return; 403 } 404 405 UsageEvents.Event event = new UsageEvents.Event(); 406 event.mPackage = component.getPackageName(); 407 event.mClass = component.getClassName(); 408 event.mTimeStamp = computeMonotonicSystemTime(SystemClock.elapsedRealtime()); 409 event.mEventType = eventType; 410 mHandler.obtainMessage(MSG_REPORT_EVENT, userId, 0, event).sendToTarget(); 411 } 412 413 @Override 414 public void reportConfigurationChange(Configuration config, int userId) { 415 if (config == null) { 416 Slog.w(TAG, "Configuration event reported with a null config"); 417 return; 418 } 419 420 UsageEvents.Event event = new UsageEvents.Event(); 421 event.mPackage = "android"; 422 event.mTimeStamp = computeMonotonicSystemTime(SystemClock.elapsedRealtime()); 423 event.mEventType = UsageEvents.Event.CONFIGURATION_CHANGE; 424 event.mConfiguration = new Configuration(config); 425 mHandler.obtainMessage(MSG_REPORT_EVENT, userId, 0, event).sendToTarget(); 426 } 427 428 @Override 429 public void prepareShutdown() { 430 // This method *WILL* do IO work, but we must block until it is finished or else 431 // we might not shutdown cleanly. This is ok to do with the 'am' lock held, because 432 // we are shutting down. 433 shutdown(); 434 } 435 } 436} 437