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