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