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 */
16package com.android.server.usage;
17
18import android.app.usage.ConfigurationStats;
19import android.app.usage.TimeSparseArray;
20import android.app.usage.UsageEvents;
21import android.app.usage.UsageStats;
22import android.content.res.Configuration;
23import android.util.ArrayMap;
24import android.util.ArraySet;
25
26class IntervalStats {
27    public long beginTime;
28    public long endTime;
29    public long lastTimeSaved;
30    public final ArrayMap<String, UsageStats> packageStats = new ArrayMap<>();
31    public final ArrayMap<Configuration, ConfigurationStats> configurations = new ArrayMap<>();
32    public Configuration activeConfiguration;
33    public TimeSparseArray<UsageEvents.Event> events;
34
35    // A string cache. This is important as when we're parsing XML files, we don't want to
36    // keep hundreds of strings that have the same contents. We will read the string
37    // and only keep it if it's not in the cache. The GC will take care of the
38    // strings that had identical copies in the cache.
39    private final ArraySet<String> mStringCache = new ArraySet<>();
40
41    /**
42     * Gets the UsageStats object for the given package, or creates one and adds it internally.
43     */
44    UsageStats getOrCreateUsageStats(String packageName) {
45        UsageStats usageStats = packageStats.get(packageName);
46        if (usageStats == null) {
47            usageStats = new UsageStats();
48            usageStats.mPackageName = getCachedStringRef(packageName);
49            usageStats.mBeginTimeStamp = beginTime;
50            usageStats.mEndTimeStamp = endTime;
51            packageStats.put(usageStats.mPackageName, usageStats);
52        }
53        return usageStats;
54    }
55
56    /**
57     * Gets the ConfigurationStats object for the given configuration, or creates one and adds it
58     * internally.
59     */
60    ConfigurationStats getOrCreateConfigurationStats(Configuration config) {
61        ConfigurationStats configStats = configurations.get(config);
62        if (configStats == null) {
63            configStats = new ConfigurationStats();
64            configStats.mBeginTimeStamp = beginTime;
65            configStats.mEndTimeStamp = endTime;
66            configStats.mConfiguration = config;
67            configurations.put(config, configStats);
68        }
69        return configStats;
70    }
71
72    /**
73     * Builds a UsageEvents.Event, but does not add it internally.
74     */
75    UsageEvents.Event buildEvent(String packageName, String className) {
76        UsageEvents.Event event = new UsageEvents.Event();
77        event.mPackage = getCachedStringRef(packageName);
78        if (className != null) {
79            event.mClass = getCachedStringRef(className);
80        }
81        return event;
82    }
83
84    private boolean isStatefulEvent(int eventType) {
85        switch (eventType) {
86            case UsageEvents.Event.MOVE_TO_FOREGROUND:
87            case UsageEvents.Event.MOVE_TO_BACKGROUND:
88            case UsageEvents.Event.END_OF_DAY:
89            case UsageEvents.Event.CONTINUE_PREVIOUS_DAY:
90                return true;
91        }
92        return false;
93    }
94
95    void update(String packageName, long timeStamp, int eventType) {
96        UsageStats usageStats = getOrCreateUsageStats(packageName);
97
98        // TODO(adamlesinski): Ensure that we recover from incorrect event sequences
99        // like double MOVE_TO_BACKGROUND, etc.
100        if (eventType == UsageEvents.Event.MOVE_TO_BACKGROUND ||
101                eventType == UsageEvents.Event.END_OF_DAY) {
102            if (usageStats.mLastEvent == UsageEvents.Event.MOVE_TO_FOREGROUND ||
103                    usageStats.mLastEvent == UsageEvents.Event.CONTINUE_PREVIOUS_DAY) {
104                usageStats.mTotalTimeInForeground += timeStamp - usageStats.mLastTimeUsed;
105            }
106        }
107
108        if (isStatefulEvent(eventType)) {
109            usageStats.mLastEvent = eventType;
110        }
111
112        if (eventType != UsageEvents.Event.SYSTEM_INTERACTION) {
113            usageStats.mLastTimeUsed = timeStamp;
114        }
115        usageStats.mEndTimeStamp = timeStamp;
116
117        if (eventType == UsageEvents.Event.MOVE_TO_FOREGROUND) {
118            usageStats.mLaunchCount += 1;
119        }
120
121        endTime = timeStamp;
122    }
123
124    void updateConfigurationStats(Configuration config, long timeStamp) {
125        if (activeConfiguration != null) {
126            ConfigurationStats activeStats = configurations.get(activeConfiguration);
127            activeStats.mTotalTimeActive += timeStamp - activeStats.mLastTimeActive;
128            activeStats.mLastTimeActive = timeStamp - 1;
129        }
130
131        if (config != null) {
132            ConfigurationStats configStats = getOrCreateConfigurationStats(config);
133            configStats.mLastTimeActive = timeStamp;
134            configStats.mActivationCount += 1;
135            activeConfiguration = configStats.mConfiguration;
136        }
137
138        endTime = timeStamp;
139    }
140
141    private String getCachedStringRef(String str) {
142        final int index = mStringCache.indexOf(str);
143        if (index < 0) {
144            mStringCache.add(str);
145            return str;
146        }
147        return mStringCache.valueAt(index);
148    }
149}
150