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