UserUsageStatsService.java revision 55717a613c23fc6b05cab86c3b0de283a027e9f6
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.app.usage.ConfigurationStats;
20import android.app.usage.TimeSparseArray;
21import android.app.usage.UsageEvents;
22import android.app.usage.UsageEvents.Event;
23import android.app.usage.UsageStats;
24import android.app.usage.UsageStatsManager;
25import android.content.pm.PackageInfo;
26import android.content.pm.PackageManager;
27import android.content.res.Configuration;
28import android.os.SystemClock;
29import android.content.Context;
30import android.text.format.DateUtils;
31import android.util.ArrayMap;
32import android.util.ArraySet;
33import android.util.Slog;
34
35import com.android.internal.util.IndentingPrintWriter;
36import com.android.server.usage.UsageStatsDatabase.StatCombiner;
37
38import java.io.File;
39import java.io.IOException;
40import java.text.SimpleDateFormat;
41import java.util.ArrayList;
42import java.util.Arrays;
43import java.util.List;
44
45/**
46 * A per-user UsageStatsService. All methods are meant to be called with the main lock held
47 * in UsageStatsService.
48 */
49class UserUsageStatsService {
50    private static final String TAG = "UsageStatsService";
51    private static final boolean DEBUG = UsageStatsService.DEBUG;
52    private static final SimpleDateFormat sDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
53    private static final int sDateFormatFlags =
54            DateUtils.FORMAT_SHOW_DATE
55            | DateUtils.FORMAT_SHOW_TIME
56            | DateUtils.FORMAT_SHOW_YEAR
57            | DateUtils.FORMAT_NUMERIC_DATE;
58
59    private final Context mContext;
60    private final UsageStatsDatabase mDatabase;
61    private final IntervalStats[] mCurrentStats;
62    private boolean mStatsChanged = false;
63    private final UnixCalendar mDailyExpiryDate;
64    private final StatsUpdatedListener mListener;
65    private final String mLogPrefix;
66    private final int mUserId;
67
68    interface StatsUpdatedListener {
69        void onStatsUpdated();
70    }
71
72    UserUsageStatsService(Context context, int userId, File usageStatsDir,
73            StatsUpdatedListener listener) {
74        mContext = context;
75        mDailyExpiryDate = new UnixCalendar(0);
76        mDatabase = new UsageStatsDatabase(usageStatsDir);
77        mCurrentStats = new IntervalStats[UsageStatsManager.INTERVAL_COUNT];
78        mListener = listener;
79        mLogPrefix = "User[" + Integer.toString(userId) + "] ";
80        mUserId = userId;
81    }
82
83    void init(final long currentTimeMillis) {
84        mDatabase.init(currentTimeMillis);
85
86        int nullCount = 0;
87        for (int i = 0; i < mCurrentStats.length; i++) {
88            mCurrentStats[i] = mDatabase.getLatestUsageStats(i);
89            if (mCurrentStats[i] == null) {
90                // Find out how many intervals we don't have data for.
91                // Ideally it should be all or none.
92                nullCount++;
93            }
94        }
95
96        if (nullCount > 0) {
97            if (nullCount != mCurrentStats.length) {
98                // This is weird, but we shouldn't fail if something like this
99                // happens.
100                Slog.w(TAG, mLogPrefix + "Some stats have no latest available");
101            } else {
102                // This must be first boot.
103            }
104
105            // By calling loadActiveStats, we will
106            // generate new stats for each bucket.
107            loadActiveStats(currentTimeMillis, false);
108        } else {
109            // Set up the expiry date to be one day from the latest daily stat.
110            // This may actually be today and we will rollover on the first event
111            // that is reported.
112            mDailyExpiryDate.setTimeInMillis(
113                    mCurrentStats[UsageStatsManager.INTERVAL_DAILY].beginTime);
114            mDailyExpiryDate.addDays(1);
115            mDailyExpiryDate.truncateToDay();
116            Slog.i(TAG, mLogPrefix + "Rollover scheduled @ " +
117                    sDateFormat.format(mDailyExpiryDate.getTimeInMillis()) +
118                    "(" + mDailyExpiryDate.getTimeInMillis() + ")");
119        }
120
121        // Now close off any events that were open at the time this was saved.
122        for (IntervalStats stat : mCurrentStats) {
123            final int pkgCount = stat.packageStats.size();
124            for (int i = 0; i < pkgCount; i++) {
125                UsageStats pkgStats = stat.packageStats.valueAt(i);
126                if (pkgStats.mLastEvent == UsageEvents.Event.MOVE_TO_FOREGROUND ||
127                        pkgStats.mLastEvent == UsageEvents.Event.CONTINUE_PREVIOUS_DAY) {
128                    stat.update(pkgStats.mPackageName, stat.lastTimeSaved,
129                            UsageEvents.Event.END_OF_DAY);
130                    notifyStatsChanged();
131                }
132            }
133
134            stat.updateConfigurationStats(null, stat.lastTimeSaved);
135        }
136
137        if (mDatabase.isNewUpdate()) {
138            initializeDefaultsForApps(currentTimeMillis, mDatabase.isFirstUpdate());
139        }
140    }
141
142    /**
143     * If any of the apps don't have a last-used entry, add one now.
144     * @param currentTimeMillis the current time
145     * @param firstUpdate if it is the first update, touch all installed apps, otherwise only
146     *        touch the system apps
147     */
148    private void initializeDefaultsForApps(long currentTimeMillis, boolean firstUpdate) {
149        PackageManager pm = mContext.getPackageManager();
150        List<PackageInfo> packages = pm.getInstalledPackages(0, mUserId);
151        final int packageCount = packages.size();
152        for (int i = 0; i < packageCount; i++) {
153            final PackageInfo pi = packages.get(i);
154            String packageName = pi.packageName;
155            if (pi.applicationInfo != null && (firstUpdate || pi.applicationInfo.isSystemApp())
156                    && getLastPackageAccessTime(packageName) == -1) {
157                for (IntervalStats stats : mCurrentStats) {
158                    stats.update(packageName, currentTimeMillis, Event.INTERACTION);
159                    mStatsChanged = true;
160                }
161            }
162        }
163        // Persist the new OTA-related access stats.
164        persistActiveStats();
165    }
166
167    void onTimeChanged(long oldTime, long newTime) {
168        persistActiveStats();
169        mDatabase.onTimeChanged(newTime - oldTime);
170        loadActiveStats(newTime, true);
171    }
172
173    void reportEvent(UsageEvents.Event event) {
174        if (DEBUG) {
175            Slog.d(TAG, mLogPrefix + "Got usage event for " + event.mPackage
176                    + "[" + event.mTimeStamp + "]: "
177                    + eventToString(event.mEventType));
178        }
179
180        if (event.mTimeStamp >= mDailyExpiryDate.getTimeInMillis()) {
181            // Need to rollover
182            rolloverStats(event.mTimeStamp);
183        }
184
185        final IntervalStats currentDailyStats = mCurrentStats[UsageStatsManager.INTERVAL_DAILY];
186
187        final Configuration newFullConfig = event.mConfiguration;
188        if (event.mEventType == UsageEvents.Event.CONFIGURATION_CHANGE &&
189                currentDailyStats.activeConfiguration != null) {
190            // Make the event configuration a delta.
191            event.mConfiguration = Configuration.generateDelta(
192                    currentDailyStats.activeConfiguration, newFullConfig);
193        }
194
195        // Add the event to the daily list.
196        if (currentDailyStats.events == null) {
197            currentDailyStats.events = new TimeSparseArray<>();
198        }
199        if (event.mEventType != UsageEvents.Event.INTERACTION) {
200            currentDailyStats.events.put(event.mTimeStamp, event);
201        }
202
203        for (IntervalStats stats : mCurrentStats) {
204            if (event.mEventType == UsageEvents.Event.CONFIGURATION_CHANGE) {
205                stats.updateConfigurationStats(newFullConfig, event.mTimeStamp);
206            } else {
207                stats.update(event.mPackage, event.mTimeStamp, event.mEventType);
208            }
209        }
210
211        notifyStatsChanged();
212    }
213
214    private static final StatCombiner<UsageStats> sUsageStatsCombiner =
215            new StatCombiner<UsageStats>() {
216                @Override
217                public void combine(IntervalStats stats, boolean mutable,
218                        List<UsageStats> accResult) {
219                    if (!mutable) {
220                        accResult.addAll(stats.packageStats.values());
221                        return;
222                    }
223
224                    final int statCount = stats.packageStats.size();
225                    for (int i = 0; i < statCount; i++) {
226                        accResult.add(new UsageStats(stats.packageStats.valueAt(i)));
227                    }
228                }
229            };
230
231    private static final StatCombiner<ConfigurationStats> sConfigStatsCombiner =
232            new StatCombiner<ConfigurationStats>() {
233                @Override
234                public void combine(IntervalStats stats, boolean mutable,
235                        List<ConfigurationStats> accResult) {
236                    if (!mutable) {
237                        accResult.addAll(stats.configurations.values());
238                        return;
239                    }
240
241                    final int configCount = stats.configurations.size();
242                    for (int i = 0; i < configCount; i++) {
243                        accResult.add(new ConfigurationStats(stats.configurations.valueAt(i)));
244                    }
245                }
246            };
247
248    /**
249     * Generic query method that selects the appropriate IntervalStats for the specified time range
250     * and bucket, then calls the {@link com.android.server.usage.UsageStatsDatabase.StatCombiner}
251     * provided to select the stats to use from the IntervalStats object.
252     */
253    private <T> List<T> queryStats(int intervalType, final long beginTime, final long endTime,
254            StatCombiner<T> combiner) {
255        if (intervalType == UsageStatsManager.INTERVAL_BEST) {
256            intervalType = mDatabase.findBestFitBucket(beginTime, endTime);
257            if (intervalType < 0) {
258                // Nothing saved to disk yet, so every stat is just as equal (no rollover has
259                // occurred.
260                intervalType = UsageStatsManager.INTERVAL_DAILY;
261            }
262        }
263
264        if (intervalType < 0 || intervalType >= mCurrentStats.length) {
265            if (DEBUG) {
266                Slog.d(TAG, mLogPrefix + "Bad intervalType used " + intervalType);
267            }
268            return null;
269        }
270
271        final IntervalStats currentStats = mCurrentStats[intervalType];
272
273        if (DEBUG) {
274            Slog.d(TAG, mLogPrefix + "SELECT * FROM " + intervalType + " WHERE beginTime >= "
275                    + beginTime + " AND endTime < " + endTime);
276        }
277
278        if (beginTime >= currentStats.endTime) {
279            if (DEBUG) {
280                Slog.d(TAG, mLogPrefix + "Requesting stats after " + beginTime + " but latest is "
281                        + currentStats.endTime);
282            }
283            // Nothing newer available.
284            return null;
285        }
286
287        // Truncate the endTime to just before the in-memory stats. Then, we'll append the
288        // in-memory stats to the results (if necessary) so as to avoid writing to disk too
289        // often.
290        final long truncatedEndTime = Math.min(currentStats.beginTime, endTime);
291
292        // Get the stats from disk.
293        List<T> results = mDatabase.queryUsageStats(intervalType, beginTime,
294                truncatedEndTime, combiner);
295        if (DEBUG) {
296            Slog.d(TAG, "Got " + (results != null ? results.size() : 0) + " results from disk");
297            Slog.d(TAG, "Current stats beginTime=" + currentStats.beginTime +
298                    " endTime=" + currentStats.endTime);
299        }
300
301        // Now check if the in-memory stats match the range and add them if they do.
302        if (beginTime < currentStats.endTime && endTime > currentStats.beginTime) {
303            if (DEBUG) {
304                Slog.d(TAG, mLogPrefix + "Returning in-memory stats");
305            }
306
307            if (results == null) {
308                results = new ArrayList<>();
309            }
310            combiner.combine(currentStats, true, results);
311        }
312
313        if (DEBUG) {
314            Slog.d(TAG, mLogPrefix + "Results: " + (results != null ? results.size() : 0));
315        }
316        return results;
317    }
318
319    List<UsageStats> queryUsageStats(int bucketType, long beginTime, long endTime) {
320        return queryStats(bucketType, beginTime, endTime, sUsageStatsCombiner);
321    }
322
323    List<ConfigurationStats> queryConfigurationStats(int bucketType, long beginTime, long endTime) {
324        return queryStats(bucketType, beginTime, endTime, sConfigStatsCombiner);
325    }
326
327    UsageEvents queryEvents(final long beginTime, final long endTime) {
328        final ArraySet<String> names = new ArraySet<>();
329        List<UsageEvents.Event> results = queryStats(UsageStatsManager.INTERVAL_DAILY,
330                beginTime, endTime, new StatCombiner<UsageEvents.Event>() {
331                    @Override
332                    public void combine(IntervalStats stats, boolean mutable,
333                            List<UsageEvents.Event> accumulatedResult) {
334                        if (stats.events == null) {
335                            return;
336                        }
337
338                        final int startIndex = stats.events.closestIndexOnOrAfter(beginTime);
339                        if (startIndex < 0) {
340                            return;
341                        }
342
343                        final int size = stats.events.size();
344                        for (int i = startIndex; i < size; i++) {
345                            if (stats.events.keyAt(i) >= endTime) {
346                                return;
347                            }
348
349                            final UsageEvents.Event event = stats.events.valueAt(i);
350                            names.add(event.mPackage);
351                            if (event.mClass != null) {
352                                names.add(event.mClass);
353                            }
354                            accumulatedResult.add(event);
355                        }
356                    }
357                });
358
359        if (results == null || results.isEmpty()) {
360            return null;
361        }
362
363        String[] table = names.toArray(new String[names.size()]);
364        Arrays.sort(table);
365        return new UsageEvents(results, table);
366    }
367
368    long getLastPackageAccessTime(String packageName) {
369        final IntervalStats yearly = mCurrentStats[UsageStatsManager.INTERVAL_YEARLY];
370        UsageStats packageUsage;
371        if ((packageUsage = yearly.packageStats.get(packageName)) == null) {
372            return -1;
373        } else {
374            return packageUsage.getLastTimeUsed();
375        }
376    }
377
378    void persistActiveStats() {
379        if (mStatsChanged) {
380            Slog.i(TAG, mLogPrefix + "Flushing usage stats to disk");
381            try {
382                for (int i = 0; i < mCurrentStats.length; i++) {
383                    mDatabase.putUsageStats(i, mCurrentStats[i]);
384                }
385                mStatsChanged = false;
386            } catch (IOException e) {
387                Slog.e(TAG, mLogPrefix + "Failed to persist active stats", e);
388            }
389        }
390    }
391
392    private void rolloverStats(final long currentTimeMillis) {
393        final long startTime = SystemClock.elapsedRealtime();
394        Slog.i(TAG, mLogPrefix + "Rolling over usage stats");
395
396        // Finish any ongoing events with an END_OF_DAY event. Make a note of which components
397        // need a new CONTINUE_PREVIOUS_DAY entry.
398        final Configuration previousConfig =
399                mCurrentStats[UsageStatsManager.INTERVAL_DAILY].activeConfiguration;
400        ArraySet<String> continuePreviousDay = new ArraySet<>();
401        for (IntervalStats stat : mCurrentStats) {
402            final int pkgCount = stat.packageStats.size();
403            for (int i = 0; i < pkgCount; i++) {
404                UsageStats pkgStats = stat.packageStats.valueAt(i);
405                if (pkgStats.mLastEvent == UsageEvents.Event.MOVE_TO_FOREGROUND ||
406                        pkgStats.mLastEvent == UsageEvents.Event.CONTINUE_PREVIOUS_DAY) {
407                    continuePreviousDay.add(pkgStats.mPackageName);
408                    stat.update(pkgStats.mPackageName, mDailyExpiryDate.getTimeInMillis() - 1,
409                            UsageEvents.Event.END_OF_DAY);
410                    notifyStatsChanged();
411                }
412            }
413
414            stat.updateConfigurationStats(null, mDailyExpiryDate.getTimeInMillis() - 1);
415        }
416
417        persistActiveStats();
418        mDatabase.prune(currentTimeMillis);
419        loadActiveStats(currentTimeMillis, false);
420
421        final int continueCount = continuePreviousDay.size();
422        for (int i = 0; i < continueCount; i++) {
423            String name = continuePreviousDay.valueAt(i);
424            final long beginTime = mCurrentStats[UsageStatsManager.INTERVAL_DAILY].beginTime;
425            for (IntervalStats stat : mCurrentStats) {
426                stat.update(name, beginTime, UsageEvents.Event.CONTINUE_PREVIOUS_DAY);
427                stat.updateConfigurationStats(previousConfig, beginTime);
428                notifyStatsChanged();
429            }
430        }
431        persistActiveStats();
432
433        final long totalTime = SystemClock.elapsedRealtime() - startTime;
434        Slog.i(TAG, mLogPrefix + "Rolling over usage stats complete. Took " + totalTime
435                + " milliseconds");
436    }
437
438    private void notifyStatsChanged() {
439        if (!mStatsChanged) {
440            mStatsChanged = true;
441            mListener.onStatsUpdated();
442        }
443    }
444
445    /**
446     * @param force To force all in-memory stats to be reloaded.
447     */
448    private void loadActiveStats(final long currentTimeMillis, boolean force) {
449        final UnixCalendar tempCal = mDailyExpiryDate;
450        for (int intervalType = 0; intervalType < mCurrentStats.length; intervalType++) {
451            tempCal.setTimeInMillis(currentTimeMillis);
452            UnixCalendar.truncateTo(tempCal, intervalType);
453
454            if (!force && mCurrentStats[intervalType] != null &&
455                    mCurrentStats[intervalType].beginTime == tempCal.getTimeInMillis()) {
456                // These are the same, no need to load them (in memory stats are always newer
457                // than persisted stats).
458                continue;
459            }
460
461            final long lastBeginTime = mDatabase.getLatestUsageStatsBeginTime(intervalType);
462            if (lastBeginTime >= tempCal.getTimeInMillis()) {
463                if (DEBUG) {
464                    Slog.d(TAG, mLogPrefix + "Loading existing stats @ " +
465                            sDateFormat.format(lastBeginTime) + "(" + lastBeginTime +
466                            ") for interval " + intervalType);
467                }
468                mCurrentStats[intervalType] = mDatabase.getLatestUsageStats(intervalType);
469            } else {
470                mCurrentStats[intervalType] = null;
471            }
472
473            if (mCurrentStats[intervalType] == null) {
474                if (DEBUG) {
475                    Slog.d(TAG, "Creating new stats @ " +
476                            sDateFormat.format(tempCal.getTimeInMillis()) + "(" +
477                            tempCal.getTimeInMillis() + ") for interval " + intervalType);
478
479                }
480                mCurrentStats[intervalType] = new IntervalStats();
481                mCurrentStats[intervalType].beginTime = tempCal.getTimeInMillis();
482                mCurrentStats[intervalType].endTime = currentTimeMillis;
483            }
484        }
485        mStatsChanged = false;
486        mDailyExpiryDate.setTimeInMillis(currentTimeMillis);
487        mDailyExpiryDate.addDays(1);
488        mDailyExpiryDate.truncateToDay();
489        Slog.i(TAG, mLogPrefix + "Rollover scheduled @ " +
490                sDateFormat.format(mDailyExpiryDate.getTimeInMillis()) + "(" +
491                tempCal.getTimeInMillis() + ")");
492    }
493
494    //
495    // -- DUMP related methods --
496    //
497
498    void checkin(final IndentingPrintWriter pw) {
499        mDatabase.checkinDailyFiles(new UsageStatsDatabase.CheckinAction() {
500            @Override
501            public boolean checkin(IntervalStats stats) {
502                printIntervalStats(pw, stats, false);
503                return true;
504            }
505        });
506    }
507
508    void dump(IndentingPrintWriter pw) {
509        // This is not a check-in, only dump in-memory stats.
510        for (int interval = 0; interval < mCurrentStats.length; interval++) {
511            pw.print("In-memory ");
512            pw.print(intervalToString(interval));
513            pw.println(" stats");
514            printIntervalStats(pw, mCurrentStats[interval], true);
515        }
516    }
517
518    private String formatDateTime(long dateTime, boolean pretty) {
519        if (pretty) {
520            return "\"" + DateUtils.formatDateTime(mContext, dateTime, sDateFormatFlags) + "\"";
521        }
522        return Long.toString(dateTime);
523    }
524
525    private String formatElapsedTime(long elapsedTime, boolean pretty) {
526        if (pretty) {
527            return "\"" + DateUtils.formatElapsedTime(elapsedTime / 1000) + "\"";
528        }
529        return Long.toString(elapsedTime);
530    }
531
532    void printIntervalStats(IndentingPrintWriter pw, IntervalStats stats, boolean prettyDates) {
533        if (prettyDates) {
534            pw.printPair("timeRange", "\"" + DateUtils.formatDateRange(mContext,
535                    stats.beginTime, stats.endTime, sDateFormatFlags) + "\"");
536        } else {
537            pw.printPair("beginTime", stats.beginTime);
538            pw.printPair("endTime", stats.endTime);
539        }
540        pw.println();
541        pw.increaseIndent();
542        pw.println("packages");
543        pw.increaseIndent();
544        final ArrayMap<String, UsageStats> pkgStats = stats.packageStats;
545        final int pkgCount = pkgStats.size();
546        for (int i = 0; i < pkgCount; i++) {
547            final UsageStats usageStats = pkgStats.valueAt(i);
548            pw.printPair("package", usageStats.mPackageName);
549            pw.printPair("totalTime", formatElapsedTime(usageStats.mTotalTimeInForeground, prettyDates));
550            pw.printPair("lastTime", formatDateTime(usageStats.mLastTimeUsed, prettyDates));
551            pw.println();
552        }
553        pw.decreaseIndent();
554
555        pw.println("configurations");
556        pw.increaseIndent();
557        final ArrayMap<Configuration, ConfigurationStats> configStats =
558                stats.configurations;
559        final int configCount = configStats.size();
560        for (int i = 0; i < configCount; i++) {
561            final ConfigurationStats config = configStats.valueAt(i);
562            pw.printPair("config", Configuration.resourceQualifierString(config.mConfiguration));
563            pw.printPair("totalTime", formatElapsedTime(config.mTotalTimeActive, prettyDates));
564            pw.printPair("lastTime", formatDateTime(config.mLastTimeActive, prettyDates));
565            pw.printPair("count", config.mActivationCount);
566            pw.println();
567        }
568        pw.decreaseIndent();
569
570        pw.println("events");
571        pw.increaseIndent();
572        final TimeSparseArray<UsageEvents.Event> events = stats.events;
573        final int eventCount = events != null ? events.size() : 0;
574        for (int i = 0; i < eventCount; i++) {
575            final UsageEvents.Event event = events.valueAt(i);
576            pw.printPair("time", formatDateTime(event.mTimeStamp, prettyDates));
577            pw.printPair("type", eventToString(event.mEventType));
578            pw.printPair("package", event.mPackage);
579            if (event.mClass != null) {
580                pw.printPair("class", event.mClass);
581            }
582            if (event.mConfiguration != null) {
583                pw.printPair("config", Configuration.resourceQualifierString(event.mConfiguration));
584            }
585            pw.println();
586        }
587        pw.decreaseIndent();
588        pw.decreaseIndent();
589    }
590
591    private static String intervalToString(int interval) {
592        switch (interval) {
593            case UsageStatsManager.INTERVAL_DAILY:
594                return "daily";
595            case UsageStatsManager.INTERVAL_WEEKLY:
596                return "weekly";
597            case UsageStatsManager.INTERVAL_MONTHLY:
598                return "monthly";
599            case UsageStatsManager.INTERVAL_YEARLY:
600                return "yearly";
601            default:
602                return "?";
603        }
604    }
605
606    private static String eventToString(int eventType) {
607        switch (eventType) {
608            case UsageEvents.Event.NONE:
609                return "NONE";
610            case UsageEvents.Event.MOVE_TO_BACKGROUND:
611                return "MOVE_TO_BACKGROUND";
612            case UsageEvents.Event.MOVE_TO_FOREGROUND:
613                return "MOVE_TO_FOREGROUND";
614            case UsageEvents.Event.END_OF_DAY:
615                return "END_OF_DAY";
616            case UsageEvents.Event.CONTINUE_PREVIOUS_DAY:
617                return "CONTINUE_PREVIOUS_DAY";
618            case UsageEvents.Event.CONFIGURATION_CHANGE:
619                return "CONFIGURATION_CHANGE";
620            case UsageEvents.Event.INTERACTION:
621                return "INTERACTION";
622            default:
623                return "UNKNOWN";
624        }
625    }
626}
627