blob: 8feed7fdb785c3a2f5263c059656ef18ad1a46b2 [file] [log] [blame]
Adam Lesinski35168002014-07-21 15:25:30 -07001/**
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
Hui Yu03d12402018-12-06 18:00:37 -080018import static android.app.usage.UsageEvents.Event.ACTIVITY_PAUSED;
19import static android.app.usage.UsageEvents.Event.ACTIVITY_RESUMED;
20import static android.app.usage.UsageEvents.Event.ACTIVITY_STOPPED;
21import static android.app.usage.UsageEvents.Event.CONFIGURATION_CHANGE;
22import static android.app.usage.UsageEvents.Event.CONTINUE_PREVIOUS_DAY;
23import static android.app.usage.UsageEvents.Event.CONTINUING_FOREGROUND_SERVICE;
Hui Yub1d243a2018-12-13 12:02:00 -080024import static android.app.usage.UsageEvents.Event.DEVICE_SHUTDOWN;
Hui Yu03d12402018-12-06 18:00:37 -080025import static android.app.usage.UsageEvents.Event.END_OF_DAY;
26import static android.app.usage.UsageEvents.Event.FLUSH_TO_DISK;
27import static android.app.usage.UsageEvents.Event.FOREGROUND_SERVICE_START;
28import static android.app.usage.UsageEvents.Event.FOREGROUND_SERVICE_STOP;
29import static android.app.usage.UsageEvents.Event.KEYGUARD_HIDDEN;
30import static android.app.usage.UsageEvents.Event.KEYGUARD_SHOWN;
31import static android.app.usage.UsageEvents.Event.NOTIFICATION_INTERRUPTION;
32import static android.app.usage.UsageEvents.Event.ROLLOVER_FOREGROUND_SERVICE;
33import static android.app.usage.UsageEvents.Event.SCREEN_INTERACTIVE;
34import static android.app.usage.UsageEvents.Event.SCREEN_NON_INTERACTIVE;
35import static android.app.usage.UsageEvents.Event.SHORTCUT_INVOCATION;
36import static android.app.usage.UsageEvents.Event.STANDBY_BUCKET_CHANGED;
37import static android.app.usage.UsageEvents.Event.SYSTEM_INTERACTION;
38
Adam Lesinski7f61e962014-09-02 16:43:52 -070039import android.app.usage.ConfigurationStats;
Suprabh Shukla60aa35b2018-04-24 18:52:46 -070040import android.app.usage.EventList;
Dianne Hackbornced54392018-02-26 13:07:42 -080041import android.app.usage.EventStats;
Hui Yu03d12402018-12-06 18:00:37 -080042import android.app.usage.UsageEvents.Event;
Adam Lesinski35168002014-07-21 15:25:30 -070043import android.app.usage.UsageStats;
Adam Lesinski7f61e962014-09-02 16:43:52 -070044import android.content.res.Configuration;
Adam Lesinski35168002014-07-21 15:25:30 -070045import android.util.ArrayMap;
Adam Lesinski9d960752014-08-25 14:48:12 -070046import android.util.ArraySet;
Michael Wachenschwanzc8c26362018-09-07 14:59:25 -070047import android.util.proto.ProtoInputStream;
Adam Lesinski35168002014-07-21 15:25:30 -070048
Hui Yue361a232018-10-04 15:05:21 -070049import com.android.internal.annotations.VisibleForTesting;
50
Michael Wachenschwanzc8c26362018-09-07 14:59:25 -070051import java.io.IOException;
Dianne Hackbornced54392018-02-26 13:07:42 -080052import java.util.List;
53
Michael Wachenschwanze717e0c2018-06-19 14:44:35 -070054public class IntervalStats {
Hui Yue361a232018-10-04 15:05:21 -070055 public static final int CURRENT_MAJOR_VERSION = 1;
56 public static final int CURRENT_MINOR_VERSION = 1;
57 public int majorVersion = CURRENT_MAJOR_VERSION;
58 public int minorVersion = CURRENT_MINOR_VERSION;
Adam Lesinski35168002014-07-21 15:25:30 -070059 public long beginTime;
60 public long endTime;
61 public long lastTimeSaved;
Dianne Hackborn3378aa92018-03-30 17:43:49 -070062 public final EventTracker interactiveTracker = new EventTracker();
63 public final EventTracker nonInteractiveTracker = new EventTracker();
64 public final EventTracker keyguardShownTracker = new EventTracker();
65 public final EventTracker keyguardHiddenTracker = new EventTracker();
Adam Lesinski37a46b42014-09-05 15:38:05 -070066 public final ArrayMap<String, UsageStats> packageStats = new ArrayMap<>();
Adam Lesinski7f61e962014-09-02 16:43:52 -070067 public final ArrayMap<Configuration, ConfigurationStats> configurations = new ArrayMap<>();
68 public Configuration activeConfiguration;
Hui Yub1d243a2018-12-13 12:02:00 -080069 public EventList events = new EventList();
Adam Lesinski35168002014-07-21 15:25:30 -070070
Adam Lesinski9d960752014-08-25 14:48:12 -070071 // A string cache. This is important as when we're parsing XML files, we don't want to
72 // keep hundreds of strings that have the same contents. We will read the string
73 // and only keep it if it's not in the cache. The GC will take care of the
74 // strings that had identical copies in the cache.
Michael Wachenschwanzc90bc152018-09-10 15:17:57 -070075 public final ArraySet<String> mStringCache = new ArraySet<>();
Adam Lesinski35168002014-07-21 15:25:30 -070076
Dianne Hackborn3378aa92018-03-30 17:43:49 -070077 public static final class EventTracker {
78 public long curStartTime;
79 public long lastEventTime;
80 public long duration;
81 public int count;
82
83 public void commitTime(long timeStamp) {
84 if (curStartTime != 0) {
85 duration += timeStamp - duration;
86 curStartTime = 0;
87 }
88 }
89
90 public void update(long timeStamp) {
91 if (curStartTime == 0) {
92 // If we aren't already running, time to bump the count.
93 count++;
94 }
95 commitTime(timeStamp);
96 curStartTime = timeStamp;
97 lastEventTime = timeStamp;
98 }
99
100 void addToEventStats(List<EventStats> out, int event, long beginTime, long endTime) {
101 if (count != 0 || duration != 0) {
102 EventStats ev = new EventStats();
103 ev.mEventType = event;
104 ev.mCount = count;
105 ev.mTotalTime = duration;
106 ev.mLastEventTime = lastEventTime;
107 ev.mBeginTimeStamp = beginTime;
108 ev.mEndTimeStamp = endTime;
109 out.add(ev);
110 }
111 }
112
113 }
114
Hui Yub1d243a2018-12-13 12:02:00 -0800115 public IntervalStats() {
116 }
117
Adam Lesinski7f61e962014-09-02 16:43:52 -0700118 /**
119 * Gets the UsageStats object for the given package, or creates one and adds it internally.
120 */
Adam Lesinski35168002014-07-21 15:25:30 -0700121 UsageStats getOrCreateUsageStats(String packageName) {
Adam Lesinski37a46b42014-09-05 15:38:05 -0700122 UsageStats usageStats = packageStats.get(packageName);
Adam Lesinski35168002014-07-21 15:25:30 -0700123 if (usageStats == null) {
124 usageStats = new UsageStats();
Adam Lesinski7f61e962014-09-02 16:43:52 -0700125 usageStats.mPackageName = getCachedStringRef(packageName);
Adam Lesinski35168002014-07-21 15:25:30 -0700126 usageStats.mBeginTimeStamp = beginTime;
127 usageStats.mEndTimeStamp = endTime;
Adam Lesinski37a46b42014-09-05 15:38:05 -0700128 packageStats.put(usageStats.mPackageName, usageStats);
Adam Lesinski35168002014-07-21 15:25:30 -0700129 }
130 return usageStats;
131 }
132
Adam Lesinski7f61e962014-09-02 16:43:52 -0700133 /**
134 * Gets the ConfigurationStats object for the given configuration, or creates one and adds it
135 * internally.
136 */
137 ConfigurationStats getOrCreateConfigurationStats(Configuration config) {
138 ConfigurationStats configStats = configurations.get(config);
139 if (configStats == null) {
140 configStats = new ConfigurationStats();
141 configStats.mBeginTimeStamp = beginTime;
142 configStats.mEndTimeStamp = endTime;
143 configStats.mConfiguration = config;
144 configurations.put(config, configStats);
145 }
146 return configStats;
147 }
148
149 /**
150 * Builds a UsageEvents.Event, but does not add it internally.
151 */
Hui Yu03d12402018-12-06 18:00:37 -0800152 Event buildEvent(String packageName, String className) {
153 Event event = new Event();
Adam Lesinski7f61e962014-09-02 16:43:52 -0700154 event.mPackage = getCachedStringRef(packageName);
155 if (className != null) {
156 event.mClass = getCachedStringRef(className);
157 }
158 return event;
159 }
160
Michael Wachenschwanzc8c26362018-09-07 14:59:25 -0700161 /**
162 * Builds a UsageEvents.Event from a proto, but does not add it internally.
163 * Built here to take advantage of the cached String Refs
164 */
Hui Yu03d12402018-12-06 18:00:37 -0800165 Event buildEvent(ProtoInputStream parser, List<String> stringPool)
Michael Wachenschwanzc90bc152018-09-10 15:17:57 -0700166 throws IOException {
Hui Yu03d12402018-12-06 18:00:37 -0800167 final Event event = new Event();
Michael Wachenschwanzc8c26362018-09-07 14:59:25 -0700168 while (true) {
169 switch (parser.nextField()) {
170 case (int) IntervalStatsProto.Event.PACKAGE:
171 event.mPackage = getCachedStringRef(
172 parser.readString(IntervalStatsProto.Event.PACKAGE));
173 break;
Michael Wachenschwanzc90bc152018-09-10 15:17:57 -0700174 case (int) IntervalStatsProto.Event.PACKAGE_INDEX:
175 event.mPackage = getCachedStringRef(stringPool.get(
176 parser.readInt(IntervalStatsProto.Event.PACKAGE_INDEX) - 1));
177 break;
Michael Wachenschwanzc8c26362018-09-07 14:59:25 -0700178 case (int) IntervalStatsProto.Event.CLASS:
179 event.mClass = getCachedStringRef(
180 parser.readString(IntervalStatsProto.Event.CLASS));
181 break;
Michael Wachenschwanzc90bc152018-09-10 15:17:57 -0700182 case (int) IntervalStatsProto.Event.CLASS_INDEX:
183 event.mClass = getCachedStringRef(stringPool.get(
184 parser.readInt(IntervalStatsProto.Event.CLASS_INDEX) - 1));
185 break;
Michael Wachenschwanzc8c26362018-09-07 14:59:25 -0700186 case (int) IntervalStatsProto.Event.TIME_MS:
187 event.mTimeStamp = beginTime + parser.readLong(
188 IntervalStatsProto.Event.TIME_MS);
189 break;
190 case (int) IntervalStatsProto.Event.FLAGS:
191 event.mFlags = parser.readInt(IntervalStatsProto.Event.FLAGS);
192 break;
193 case (int) IntervalStatsProto.Event.TYPE:
194 event.mEventType = parser.readInt(IntervalStatsProto.Event.TYPE);
195 break;
196 case (int) IntervalStatsProto.Event.CONFIG:
197 event.mConfiguration = new Configuration();
198 event.mConfiguration.readFromProto(parser, IntervalStatsProto.Event.CONFIG);
199 break;
200 case (int) IntervalStatsProto.Event.SHORTCUT_ID:
201 event.mShortcutId = parser.readString(
202 IntervalStatsProto.Event.SHORTCUT_ID).intern();
203 break;
204 case (int) IntervalStatsProto.Event.STANDBY_BUCKET:
205 event.mBucketAndReason = parser.readInt(
206 IntervalStatsProto.Event.STANDBY_BUCKET);
207 break;
208 case (int) IntervalStatsProto.Event.NOTIFICATION_CHANNEL:
209 event.mNotificationChannelId = parser.readString(
210 IntervalStatsProto.Event.NOTIFICATION_CHANNEL);
211 break;
Michael Wachenschwanzc90bc152018-09-10 15:17:57 -0700212 case (int) IntervalStatsProto.Event.NOTIFICATION_CHANNEL_INDEX:
213 event.mNotificationChannelId = getCachedStringRef(stringPool.get(
214 parser.readInt(IntervalStatsProto.Event.NOTIFICATION_CHANNEL_INDEX)
215 - 1));
216 break;
Hui Yu03d12402018-12-06 18:00:37 -0800217 case (int) IntervalStatsProto.Event.INSTANCE_ID:
218 event.mInstanceId = parser.readInt(IntervalStatsProto.Event.INSTANCE_ID);
219 break;
Michael Wachenschwanz0b4ab1f2019-01-07 13:59:10 -0800220 case (int) IntervalStatsProto.Event.TASK_ROOT_PACKAGE_INDEX:
221 event.mTaskRootPackage = getCachedStringRef(stringPool.get(
222 parser.readInt(IntervalStatsProto.Event.TASK_ROOT_PACKAGE_INDEX) - 1));
223 break;
224 case (int) IntervalStatsProto.Event.TASK_ROOT_CLASS_INDEX:
225 event.mTaskRootClass = getCachedStringRef(stringPool.get(
226 parser.readInt(IntervalStatsProto.Event.TASK_ROOT_CLASS_INDEX) - 1));
227 break;
Michael Wachenschwanzc8c26362018-09-07 14:59:25 -0700228 case ProtoInputStream.NO_MORE_FIELDS:
229 // Handle default values for certain events types
230 switch (event.mEventType) {
Hui Yu03d12402018-12-06 18:00:37 -0800231 case CONFIGURATION_CHANGE:
Michael Wachenschwanzc8c26362018-09-07 14:59:25 -0700232 if (event.mConfiguration == null) {
233 event.mConfiguration = new Configuration();
234 }
235 break;
Hui Yu03d12402018-12-06 18:00:37 -0800236 case SHORTCUT_INVOCATION:
Michael Wachenschwanzc8c26362018-09-07 14:59:25 -0700237 if (event.mShortcutId == null) {
238 event.mShortcutId = "";
239 }
240 break;
Hui Yu03d12402018-12-06 18:00:37 -0800241 case NOTIFICATION_INTERRUPTION:
Michael Wachenschwanzc8c26362018-09-07 14:59:25 -0700242 if (event.mNotificationChannelId == null) {
243 event.mNotificationChannelId = "";
244 }
245 break;
246 }
247 if (event.mTimeStamp == 0) {
248 //mTimestamp not set, assume default value 0 plus beginTime
249 event.mTimeStamp = beginTime;
250 }
251 return event;
252 }
253 }
254 }
255
Adam Lesinski978a1ed2015-03-02 11:37:24 -0800256 private boolean isStatefulEvent(int eventType) {
257 switch (eventType) {
Hui Yu03d12402018-12-06 18:00:37 -0800258 case ACTIVITY_RESUMED:
259 case ACTIVITY_PAUSED:
260 case ACTIVITY_STOPPED:
261 case FOREGROUND_SERVICE_START:
262 case FOREGROUND_SERVICE_STOP:
263 case END_OF_DAY:
264 case ROLLOVER_FOREGROUND_SERVICE:
265 case CONTINUE_PREVIOUS_DAY:
266 case CONTINUING_FOREGROUND_SERVICE:
Hui Yub1d243a2018-12-13 12:02:00 -0800267 case DEVICE_SHUTDOWN:
Adam Lesinski978a1ed2015-03-02 11:37:24 -0800268 return true;
269 }
270 return false;
271 }
272
Amith Yamasanibfc4bf52018-01-19 06:55:08 -0800273 /**
274 * Returns whether the event type is one caused by user visible
275 * interaction. Excludes those that are internally generated.
Amith Yamasanibfc4bf52018-01-19 06:55:08 -0800276 */
277 private boolean isUserVisibleEvent(int eventType) {
Hui Yu03d12402018-12-06 18:00:37 -0800278 return eventType != SYSTEM_INTERACTION
279 && eventType != STANDBY_BUCKET_CHANGED;
Amith Yamasanibfc4bf52018-01-19 06:55:08 -0800280 }
281
Michael Wachenschwanze717e0c2018-06-19 14:44:35 -0700282 /**
Hui Yu03d12402018-12-06 18:00:37 -0800283 * Update the IntervalStats by a activity or foreground service event.
284 * @param packageName package name of this event. Is null if event targets to all packages.
285 * @param className class name of a activity or foreground service, could be null to if this
286 * is sent to all activities/services in this package.
287 * @param timeStamp Epoch timestamp in milliseconds.
288 * @param eventType event type as in {@link Event}
289 * @param instanceId if className is an activity, the hashCode of ActivityRecord's appToken.
290 * if className is not an activity, instanceId is not used.
Michael Wachenschwanze717e0c2018-06-19 14:44:35 -0700291 * @hide
292 */
293 @VisibleForTesting
Hui Yu03d12402018-12-06 18:00:37 -0800294 public void update(String packageName, String className, long timeStamp, int eventType,
295 int instanceId) {
Hui Yub1d243a2018-12-13 12:02:00 -0800296 if (eventType == DEVICE_SHUTDOWN
297 || eventType == FLUSH_TO_DISK) {
298 // DEVICE_SHUTDOWN and FLUSH_TO_DISK are sent to all packages.
Hui Yu03d12402018-12-06 18:00:37 -0800299 final int size = packageStats.size();
300 for (int i = 0; i < size; i++) {
301 UsageStats usageStats = packageStats.valueAt(i);
302 usageStats.update(null, timeStamp, eventType, instanceId);
303 }
Hui Yu03d12402018-12-06 18:00:37 -0800304 } else {
305 UsageStats usageStats = getOrCreateUsageStats(packageName);
306 usageStats.update(className, timeStamp, eventType, instanceId);
307 }
Adam Lesinski7f61e962014-09-02 16:43:52 -0700308 endTime = timeStamp;
309 }
310
Michael Wachenschwanzc90bc152018-09-10 15:17:57 -0700311 /**
312 * @hide
313 */
314 @VisibleForTesting
Hui Yu03d12402018-12-06 18:00:37 -0800315 public void addEvent(Event event) {
Michael Wachenschwanzc90bc152018-09-10 15:17:57 -0700316 // Cache common use strings
317 event.mPackage = getCachedStringRef(event.mPackage);
318 if (event.mClass != null) {
319 event.mClass = getCachedStringRef(event.mClass);
320 }
Michael Wachenschwanz0b4ab1f2019-01-07 13:59:10 -0800321 if (event.mTaskRootPackage != null) {
322 event.mTaskRootPackage = getCachedStringRef(event.mTaskRootPackage);
323 }
324 if (event.mTaskRootClass != null) {
325 event.mTaskRootClass = getCachedStringRef(event.mTaskRootClass);
326 }
Hui Yu03d12402018-12-06 18:00:37 -0800327 if (event.mEventType == NOTIFICATION_INTERRUPTION) {
Michael Wachenschwanzc90bc152018-09-10 15:17:57 -0700328 event.mNotificationChannelId = getCachedStringRef(event.mNotificationChannelId);
329 }
330 events.insert(event);
331 }
332
Kang Li53b43142016-11-14 14:38:25 -0800333 void updateChooserCounts(String packageName, String category, String action) {
334 UsageStats usageStats = getOrCreateUsageStats(packageName);
335 if (usageStats.mChooserCounts == null) {
336 usageStats.mChooserCounts = new ArrayMap<>();
337 }
338 ArrayMap<String, Integer> chooserCounts;
339 final int idx = usageStats.mChooserCounts.indexOfKey(action);
340 if (idx < 0) {
341 chooserCounts = new ArrayMap<>();
342 usageStats.mChooserCounts.put(action, chooserCounts);
343 } else {
344 chooserCounts = usageStats.mChooserCounts.valueAt(idx);
345 }
346 int currentCount = chooserCounts.getOrDefault(category, 0);
347 chooserCounts.put(category, currentCount + 1);
348 }
349
Adam Lesinski7f61e962014-09-02 16:43:52 -0700350 void updateConfigurationStats(Configuration config, long timeStamp) {
351 if (activeConfiguration != null) {
352 ConfigurationStats activeStats = configurations.get(activeConfiguration);
353 activeStats.mTotalTimeActive += timeStamp - activeStats.mLastTimeActive;
354 activeStats.mLastTimeActive = timeStamp - 1;
355 }
356
357 if (config != null) {
358 ConfigurationStats configStats = getOrCreateConfigurationStats(config);
359 configStats.mLastTimeActive = timeStamp;
360 configStats.mActivationCount += 1;
361 activeConfiguration = configStats.mConfiguration;
362 }
363
Adam Lesinski35168002014-07-21 15:25:30 -0700364 endTime = timeStamp;
365 }
366
Amith Yamasanibc813eb2018-03-20 19:37:46 -0700367 void incrementAppLaunchCount(String packageName) {
368 UsageStats usageStats = getOrCreateUsageStats(packageName);
369 usageStats.mAppLaunchCount += 1;
370 }
371
Dianne Hackbornced54392018-02-26 13:07:42 -0800372 void commitTime(long timeStamp) {
Dianne Hackborn3378aa92018-03-30 17:43:49 -0700373 interactiveTracker.commitTime(timeStamp);
374 nonInteractiveTracker.commitTime(timeStamp);
375 keyguardShownTracker.commitTime(timeStamp);
376 keyguardHiddenTracker.commitTime(timeStamp);
Dianne Hackbornced54392018-02-26 13:07:42 -0800377 }
378
379 void updateScreenInteractive(long timeStamp) {
Dianne Hackborn3378aa92018-03-30 17:43:49 -0700380 interactiveTracker.update(timeStamp);
381 nonInteractiveTracker.commitTime(timeStamp);
Dianne Hackbornced54392018-02-26 13:07:42 -0800382 }
383
384 void updateScreenNonInteractive(long timeStamp) {
Dianne Hackborn3378aa92018-03-30 17:43:49 -0700385 nonInteractiveTracker.update(timeStamp);
386 interactiveTracker.commitTime(timeStamp);
Dianne Hackbornced54392018-02-26 13:07:42 -0800387 }
388
Dianne Hackborn3378aa92018-03-30 17:43:49 -0700389 void updateKeyguardShown(long timeStamp) {
390 keyguardShownTracker.update(timeStamp);
391 keyguardHiddenTracker.commitTime(timeStamp);
392 }
393
394 void updateKeyguardHidden(long timeStamp) {
395 keyguardHiddenTracker.update(timeStamp);
396 keyguardShownTracker.commitTime(timeStamp);
Dianne Hackbornced54392018-02-26 13:07:42 -0800397 }
398
399 void addEventStatsTo(List<EventStats> out) {
Hui Yu03d12402018-12-06 18:00:37 -0800400 interactiveTracker.addToEventStats(out, SCREEN_INTERACTIVE,
Dianne Hackborn3378aa92018-03-30 17:43:49 -0700401 beginTime, endTime);
Hui Yu03d12402018-12-06 18:00:37 -0800402 nonInteractiveTracker.addToEventStats(out, SCREEN_NON_INTERACTIVE,
Dianne Hackborn3378aa92018-03-30 17:43:49 -0700403 beginTime, endTime);
Hui Yu03d12402018-12-06 18:00:37 -0800404 keyguardShownTracker.addToEventStats(out, KEYGUARD_SHOWN,
Dianne Hackborn3378aa92018-03-30 17:43:49 -0700405 beginTime, endTime);
Hui Yu03d12402018-12-06 18:00:37 -0800406 keyguardHiddenTracker.addToEventStats(out, KEYGUARD_HIDDEN,
Dianne Hackborn3378aa92018-03-30 17:43:49 -0700407 beginTime, endTime);
Dianne Hackbornced54392018-02-26 13:07:42 -0800408 }
409
Adam Lesinski9d960752014-08-25 14:48:12 -0700410 private String getCachedStringRef(String str) {
411 final int index = mStringCache.indexOf(str);
412 if (index < 0) {
413 mStringCache.add(str);
414 return str;
Adam Lesinski35168002014-07-21 15:25:30 -0700415 }
Adam Lesinski9d960752014-08-25 14:48:12 -0700416 return mStringCache.valueAt(index);
417 }
Hui Yue361a232018-10-04 15:05:21 -0700418
419 /**
420 * When an IntervalStats object is deserialized, if the object's version number
421 * is lower than current version number, optionally perform a upgrade.
422 */
423 void upgradeIfNeeded() {
424 // We only uprade on majorVersion change, no need to upgrade on minorVersion change.
425 if (!(majorVersion < CURRENT_MAJOR_VERSION)) {
426 return;
427 }
428 /*
429 Optional upgrade code here.
430 */
431 majorVersion = CURRENT_MAJOR_VERSION;
432 }
Adam Lesinski35168002014-07-21 15:25:30 -0700433}