Catch exceptions in UsageStatsService on bad data.

Instead of throwing exceptions from UsageStatsService and potentially
crashing the system server, catch exception on reading/writing bad data
and log the exception.

Also ensure proper handling of deobfuscating bad data.

Bug: 140459061
Bug: 135484470
Test: atest IntervalStatsTests
Test: atest UsageStatsDatabaseTest
Change-Id: I17d6ad5e377a2636ff42c9b422fe6ddf1201fc08
This commit is contained in:
Varun Shah
2019-09-17 13:48:38 -07:00
parent e1ba9cde53
commit 358d500623
8 changed files with 544 additions and 156 deletions

View File

@@ -78,6 +78,21 @@ public class EventList {
mEvents.add(insertIndex, event);
}
/**
* Removes the event at the given index.
*
* @param index the index of the event to remove
* @return the event removed, or {@code null} if the index was out of bounds
*/
public UsageEvents.Event remove(int index) {
try {
return mEvents.remove(index);
} catch (IndexOutOfBoundsException e) {
// catch and handle the exception here instead of throwing it to the client
return null;
}
}
/**
* Finds the index of the first event whose timestamp is greater than or equal to the given
* timestamp.

View File

@@ -0,0 +1,191 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.usage;
import static android.app.usage.UsageEvents.Event.MAX_EVENT_TYPE;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertTrue;
import android.app.usage.UsageEvents;
import android.content.res.Configuration;
import android.test.suitebuilder.annotation.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.Locale;
@RunWith(AndroidJUnit4.class)
@SmallTest
public class IntervalStatsTests {
private static final int NUMBER_OF_PACKAGES = 7;
private static final int NUMBER_OF_EVENTS_PER_PACKAGE = 200;
private static final int NUMBER_OF_EVENTS = NUMBER_OF_PACKAGES * NUMBER_OF_EVENTS_PER_PACKAGE;
private long mEndTime = 0;
private void populateIntervalStats(IntervalStats intervalStats) {
final int timeProgression = 23;
long time = System.currentTimeMillis() - (NUMBER_OF_EVENTS * timeProgression);
intervalStats.majorVersion = 7;
intervalStats.minorVersion = 8;
intervalStats.beginTime = time;
intervalStats.interactiveTracker.count = 2;
intervalStats.interactiveTracker.duration = 111111;
intervalStats.nonInteractiveTracker.count = 3;
intervalStats.nonInteractiveTracker.duration = 222222;
intervalStats.keyguardShownTracker.count = 4;
intervalStats.keyguardShownTracker.duration = 333333;
intervalStats.keyguardHiddenTracker.count = 5;
intervalStats.keyguardHiddenTracker.duration = 4444444;
for (int i = 0; i < NUMBER_OF_EVENTS; i++) {
UsageEvents.Event event = new UsageEvents.Event();
final int packageInt = ((i / 3) % NUMBER_OF_PACKAGES); // clusters of 3 events
event.mPackage = "fake.package.name" + packageInt;
if (packageInt == 3) {
// Third app is an instant app
event.mFlags |= UsageEvents.Event.FLAG_IS_PACKAGE_INSTANT_APP;
}
final int instanceId = i % 11;
event.mClass = ".fake.class.name" + instanceId;
event.mTimeStamp = time;
event.mEventType = i % (MAX_EVENT_TYPE + 1); //"random" event type
event.mInstanceId = instanceId;
final int rootPackageInt = (i % 5); // 5 "apps" start each task
event.mTaskRootPackage = "fake.package.name" + rootPackageInt;
final int rootClassInt = i % 6;
event.mTaskRootClass = ".fake.class.name" + rootClassInt;
switch (event.mEventType) {
case UsageEvents.Event.CONFIGURATION_CHANGE:
event.mConfiguration = new Configuration(); //empty config
break;
case UsageEvents.Event.SHORTCUT_INVOCATION:
event.mShortcutId = "shortcut" + (i % 8); //"random" shortcut
break;
case UsageEvents.Event.STANDBY_BUCKET_CHANGED:
//"random" bucket and reason
event.mBucketAndReason = (((i % 5 + 1) * 10) << 16) & (i % 5 + 1) << 8;
break;
case UsageEvents.Event.NOTIFICATION_INTERRUPTION:
event.mNotificationChannelId = "channel" + (i % 5); //"random" channel
break;
}
intervalStats.addEvent(event);
intervalStats.update(event.mPackage, event.mClass, event.mTimeStamp, event.mEventType,
event.mInstanceId);
time += timeProgression; // Arbitrary progression of time
}
mEndTime = time;
final Configuration config1 = new Configuration();
config1.fontScale = 3.3f;
config1.mcc = 4;
intervalStats.getOrCreateConfigurationStats(config1);
final Configuration config2 = new Configuration();
config2.mnc = 5;
config2.setLocale(new Locale("en", "US"));
intervalStats.getOrCreateConfigurationStats(config2);
intervalStats.activeConfiguration = config2;
}
@Test
public void testObfuscation() {
final IntervalStats intervalStats = new IntervalStats();
populateIntervalStats(intervalStats);
final PackagesTokenData packagesTokenData = new PackagesTokenData();
intervalStats.obfuscateData(packagesTokenData);
// data is populated with 7 different "apps"
assertEquals(packagesTokenData.tokensToPackagesMap.size(), NUMBER_OF_PACKAGES);
assertEquals(packagesTokenData.packagesToTokensMap.size(), NUMBER_OF_PACKAGES);
assertEquals(packagesTokenData.counter, NUMBER_OF_PACKAGES + 1);
assertEquals(intervalStats.events.size(), NUMBER_OF_EVENTS);
assertEquals(intervalStats.packageStats.size(), NUMBER_OF_PACKAGES);
}
@Test
public void testDeobfuscation() {
final IntervalStats intervalStats = new IntervalStats();
populateIntervalStats(intervalStats);
final PackagesTokenData packagesTokenData = new PackagesTokenData();
intervalStats.obfuscateData(packagesTokenData);
intervalStats.deobfuscateData(packagesTokenData);
// ensure deobfuscation doesn't update any of the mappings data
assertEquals(packagesTokenData.tokensToPackagesMap.size(), NUMBER_OF_PACKAGES);
assertEquals(packagesTokenData.packagesToTokensMap.size(), NUMBER_OF_PACKAGES);
assertEquals(packagesTokenData.counter, NUMBER_OF_PACKAGES + 1);
// ensure deobfuscation didn't remove any events or usage stats
assertEquals(intervalStats.events.size(), NUMBER_OF_EVENTS);
assertEquals(intervalStats.packageStats.size(), NUMBER_OF_PACKAGES);
}
@Test
public void testBadDataOnDeobfuscation() {
final IntervalStats intervalStats = new IntervalStats();
populateIntervalStats(intervalStats);
final PackagesTokenData packagesTokenData = new PackagesTokenData();
intervalStats.obfuscateData(packagesTokenData);
intervalStats.packageStats.clear();
// remove the mapping for token 2
packagesTokenData.tokensToPackagesMap.remove(2);
intervalStats.deobfuscateData(packagesTokenData);
// deobfuscation should have removed all events mapped to package token 2
assertEquals(intervalStats.events.size(),
NUMBER_OF_EVENTS - NUMBER_OF_EVENTS_PER_PACKAGE - 1);
assertEquals(intervalStats.packageStats.size(), NUMBER_OF_PACKAGES - 1);
}
@Test
public void testBadPackageDataOnDeobfuscation() {
final IntervalStats intervalStats = new IntervalStats();
populateIntervalStats(intervalStats);
final PackagesTokenData packagesTokenData = new PackagesTokenData();
intervalStats.obfuscateData(packagesTokenData);
intervalStats.packageStats.clear();
// remove mapping number 2 within package 3 (random)
packagesTokenData.tokensToPackagesMap.valueAt(3).remove(2);
intervalStats.deobfuscateData(packagesTokenData);
// deobfuscation should not have removed all events for a package - however, it's possible
// that some events were removed because of how shortcut and notification events are handled
assertTrue(intervalStats.events.size() > NUMBER_OF_EVENTS - NUMBER_OF_EVENTS_PER_PACKAGE);
assertEquals(intervalStats.packageStats.size(), NUMBER_OF_PACKAGES);
}
}

View File

@@ -45,6 +45,7 @@ import android.content.res.Configuration;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseIntArray;
import android.util.proto.ProtoInputStream;
@@ -55,6 +56,8 @@ import java.io.IOException;
import java.util.List;
public class IntervalStats {
private static final String TAG = "IntervalStats";
public static final int CURRENT_MAJOR_VERSION = 1;
public static final int CURRENT_MINOR_VERSION = 1;
public int majorVersion = CURRENT_MAJOR_VERSION;
@@ -453,6 +456,10 @@ public class IntervalStats {
final UsageStats usageStats = packageStatsObfuscated.valueAt(statsIndex);
usageStats.mPackageName = packagesTokenData.getString(packageToken,
PackagesTokenData.PACKAGE_NAME_INDEX);
if (usageStats.mPackageName == null) {
Slog.e(TAG, "Unable to parse usage stats package " + packageToken);
continue;
}
// Update chooser counts
final int chooserActionsSize = usageStats.mChooserCountsObfuscated.size();
@@ -460,6 +467,11 @@ public class IntervalStats {
final ArrayMap<String, Integer> categoryCountsMap = new ArrayMap<>();
final int actionToken = usageStats.mChooserCountsObfuscated.keyAt(actionIndex);
final String action = packagesTokenData.getString(packageToken, actionToken);
if (action == null) {
Slog.i(TAG, "Unable to parse chooser action " + actionToken
+ " for package " + packageToken);
continue;
}
final SparseIntArray categoryCounts =
usageStats.mChooserCountsObfuscated.valueAt(actionIndex);
final int categoriesSize = categoryCounts.size();
@@ -467,6 +479,11 @@ public class IntervalStats {
final int categoryToken = categoryCounts.keyAt(categoryIndex);
final String category = packagesTokenData.getString(packageToken,
categoryToken);
if (category == null) {
Slog.i(TAG, "Unable to parse chooser category " + categoryToken
+ " for package " + packageToken);
continue;
}
categoryCountsMap.put(category, categoryCounts.valueAt(categoryIndex));
}
usageStats.mChooserCounts.put(action, categoryCountsMap);
@@ -481,22 +498,39 @@ public class IntervalStats {
* shortcut or notification channel tokens.
*/
private void deobfuscateEvents(PackagesTokenData packagesTokenData) {
final int eventsSize = this.events.size();
for (int i = 0; i < eventsSize; i++) {
for (int i = this.events.size() - 1; i >= 0; i--) {
final Event event = this.events.get(i);
final int packageToken = event.mPackageToken;
event.mPackage = packagesTokenData.getString(packageToken,
PackagesTokenData.PACKAGE_NAME_INDEX);
if (event.mPackage == null) {
Slog.e(TAG, "Unable to parse event package " + packageToken);
this.events.remove(i);
continue;
}
if (event.mClassToken != PackagesTokenData.UNASSIGNED_TOKEN) {
event.mClass = packagesTokenData.getString(packageToken, event.mClassToken);
if (event.mClass == null) {
Slog.i(TAG, "Unable to parse class " + event.mClassToken
+ " for package " + packageToken);
}
}
if (event.mTaskRootPackageToken != PackagesTokenData.UNASSIGNED_TOKEN) {
event.mTaskRootPackage = packagesTokenData.getString(packageToken,
event.mTaskRootPackageToken);
if (event.mTaskRootPackage == null) {
Slog.i(TAG, "Unable to parse task root package " + event.mTaskRootPackageToken
+ " for package " + packageToken);
}
}
if (event.mTaskRootClassToken != PackagesTokenData.UNASSIGNED_TOKEN) {
event.mTaskRootClass = packagesTokenData.getString(packageToken,
event.mTaskRootClassToken);
if (event.mTaskRootClass == null) {
Slog.i(TAG, "Unable to parse task root class " + event.mTaskRootClassToken
+ " for package " + packageToken);
}
}
switch (event.mEventType) {
case CONFIGURATION_CHANGE:
@@ -507,10 +541,23 @@ public class IntervalStats {
case SHORTCUT_INVOCATION:
event.mShortcutId = packagesTokenData.getString(packageToken,
event.mShortcutIdToken);
if (event.mShortcutId == null) {
Slog.e(TAG, "Unable to parse shortcut " + event.mShortcutIdToken
+ " for package " + packageToken);
this.events.remove(i);
continue;
}
break;
case NOTIFICATION_INTERRUPTION:
event.mNotificationChannelId = packagesTokenData.getString(packageToken,
event.mNotificationChannelIdToken);
if (event.mNotificationChannelId == null) {
Slog.e(TAG, "Unable to parse notification channel "
+ event.mNotificationChannelIdToken + " for package "
+ packageToken);
this.events.remove(i);
continue;
}
break;
}
}

View File

@@ -16,6 +16,7 @@
package com.android.server.usage;
import android.util.ArrayMap;
import android.util.Slog;
import android.util.SparseArray;
import java.util.ArrayList;
@@ -107,9 +108,17 @@ public final class PackagesTokenData {
*
* @param packageToken the package token for which this token belongs to
* @param token the token whose string needs to be fetched
* @return the string representing the given token
* @return the string representing the given token or {@code null} if not found
*/
public String getString(int packageToken, int token) {
return tokensToPackagesMap.get(packageToken).get(token);
try {
return tokensToPackagesMap.get(packageToken).get(token);
} catch (NullPointerException npe) {
Slog.e("PackagesTokenData",
"Unable to find tokenized strings for package " + packageToken, npe);
return null;
} catch (IndexOutOfBoundsException e) {
return null;
}
}
}

View File

@@ -869,11 +869,19 @@ public class UsageStatsDatabase {
UsageStatsXml.write(out, stats);
break;
case 4:
UsageStatsProto.write(out, stats);
try {
UsageStatsProto.write(out, stats);
} catch (IOException | IllegalArgumentException e) {
Slog.e(TAG, "Unable to write interval stats to proto.", e);
}
break;
case 5:
stats.obfuscateData(packagesTokenData);
UsageStatsProtoV2.write(out, stats);
try {
UsageStatsProtoV2.write(out, stats);
} catch (IOException | IllegalArgumentException e) {
Slog.e(TAG, "Unable to write interval stats to proto.", e);
}
break;
default:
throw new RuntimeException(
@@ -920,10 +928,18 @@ public class UsageStatsDatabase {
UsageStatsXml.read(in, statsOut);
break;
case 4:
UsageStatsProto.read(in, statsOut);
try {
UsageStatsProto.read(in, statsOut);
} catch (IOException e) {
Slog.e(TAG, "Unable to read interval stats from proto.", e);
}
break;
case 5:
UsageStatsProtoV2.read(in, statsOut);
try {
UsageStatsProtoV2.read(in, statsOut);
} catch (IOException e) {
Slog.e(TAG, "Unable to read interval stats from proto.", e);
}
statsOut.deobfuscateData(packagesTokenData);
break;
default:
@@ -974,6 +990,8 @@ public class UsageStatsDatabase {
UsageStatsProtoV2.writeObfuscatedData(fos, mPackagesTokenData);
file.finishWrite(fos);
fos = null;
} catch (IOException | IllegalArgumentException e) {
Slog.e(TAG, "Unable to write obfuscated data to proto.", e);
} finally {
file.failWrite(fos);
}

View File

@@ -120,10 +120,14 @@ final class UsageStatsProto {
IntervalStatsProto.UsageStats.APP_LAUNCH_COUNT);
break;
case (int) IntervalStatsProto.UsageStats.CHOOSER_ACTIONS:
final long chooserToken = proto.start(
IntervalStatsProto.UsageStats.CHOOSER_ACTIONS);
loadChooserCounts(proto, stats);
proto.end(chooserToken);
try {
final long chooserToken = proto.start(
IntervalStatsProto.UsageStats.CHOOSER_ACTIONS);
loadChooserCounts(proto, stats);
proto.end(chooserToken);
} catch (IOException e) {
Slog.e(TAG, "Unable to read chooser counts for " + stats.mPackageName, e);
}
break;
case (int) IntervalStatsProto.UsageStats.LAST_TIME_SERVICE_USED_MS:
// Time attributes stored is an offset of the beginTime.
@@ -153,20 +157,24 @@ final class UsageStatsProto {
}
private static void loadCountAndTime(ProtoInputStream proto, long fieldId,
IntervalStats.EventTracker tracker) throws IOException {
final long token = proto.start(fieldId);
while (true) {
switch (proto.nextField()) {
case (int) IntervalStatsProto.CountAndTime.COUNT:
tracker.count = proto.readInt(IntervalStatsProto.CountAndTime.COUNT);
break;
case (int) IntervalStatsProto.CountAndTime.TIME_MS:
tracker.duration = proto.readLong(IntervalStatsProto.CountAndTime.TIME_MS);
break;
case ProtoInputStream.NO_MORE_FIELDS:
proto.end(token);
return;
IntervalStats.EventTracker tracker) {
try {
final long token = proto.start(fieldId);
while (true) {
switch (proto.nextField()) {
case (int) IntervalStatsProto.CountAndTime.COUNT:
tracker.count = proto.readInt(IntervalStatsProto.CountAndTime.COUNT);
break;
case (int) IntervalStatsProto.CountAndTime.TIME_MS:
tracker.duration = proto.readLong(IntervalStatsProto.CountAndTime.TIME_MS);
break;
case ProtoInputStream.NO_MORE_FIELDS:
proto.end(token);
return;
}
}
} catch (IOException e) {
Slog.e(TAG, "Unable to read event tracker " + fieldId, e);
}
}
@@ -306,7 +314,7 @@ final class UsageStatsProto {
}
private static void writeStringPool(ProtoOutputStream proto, final IntervalStats stats)
throws IOException {
throws IllegalArgumentException {
final long token = proto.start(IntervalStatsProto.STRINGPOOL);
final int size = stats.mStringCache.size();
proto.write(IntervalStatsProto.StringPool.SIZE, size);
@@ -317,7 +325,8 @@ final class UsageStatsProto {
}
private static void writeUsageStats(ProtoOutputStream proto, long fieldId,
final IntervalStats stats, final UsageStats usageStats) throws IOException {
final IntervalStats stats, final UsageStats usageStats)
throws IllegalArgumentException {
final long token = proto.start(fieldId);
// Write the package name first, so loadUsageStats can avoid creating an extra object
final int packageIndex = stats.mStringCache.indexOf(usageStats.mPackageName);
@@ -347,12 +356,16 @@ final class UsageStatsProto {
proto.write(IntervalStatsProto.UsageStats.TOTAL_TIME_VISIBLE_MS,
usageStats.mTotalTimeVisible);
proto.write(IntervalStatsProto.UsageStats.APP_LAUNCH_COUNT, usageStats.mAppLaunchCount);
writeChooserCounts(proto, usageStats);
try {
writeChooserCounts(proto, usageStats);
} catch (IllegalArgumentException e) {
Slog.e(TAG, "Unable to write chooser counts for " + usageStats.mPackageName, e);
}
proto.end(token);
}
private static void writeCountAndTime(ProtoOutputStream proto, long fieldId, int count,
long time) throws IOException {
long time) throws IllegalArgumentException {
final long token = proto.start(fieldId);
proto.write(IntervalStatsProto.CountAndTime.COUNT, count);
proto.write(IntervalStatsProto.CountAndTime.TIME_MS, time);
@@ -361,7 +374,7 @@ final class UsageStatsProto {
private static void writeChooserCounts(ProtoOutputStream proto, final UsageStats usageStats)
throws IOException {
throws IllegalArgumentException {
if (usageStats == null || usageStats.mChooserCounts == null
|| usageStats.mChooserCounts.keySet().isEmpty()) {
return;
@@ -381,7 +394,7 @@ final class UsageStatsProto {
}
private static void writeCountsForAction(ProtoOutputStream proto,
ArrayMap<String, Integer> counts) throws IOException {
ArrayMap<String, Integer> counts) throws IllegalArgumentException {
final int countsSize = counts.size();
for (int i = 0; i < countsSize; i++) {
String key = counts.keyAt(i);
@@ -397,7 +410,7 @@ final class UsageStatsProto {
private static void writeConfigStats(ProtoOutputStream proto, long fieldId,
final IntervalStats stats, final ConfigurationStats configStats, boolean isActive)
throws IOException {
throws IllegalArgumentException {
final long token = proto.start(fieldId);
configStats.mConfiguration.writeToProto(proto, IntervalStatsProto.Configuration.CONFIG);
proto.write(IntervalStatsProto.Configuration.LAST_TIME_ACTIVE_MS,
@@ -410,7 +423,7 @@ final class UsageStatsProto {
}
private static void writeEvent(ProtoOutputStream proto, long fieldId, final IntervalStats stats,
final UsageEvents.Event event) throws IOException {
final UsageEvents.Event event) throws IllegalArgumentException {
final long token = proto.start(fieldId);
final int packageIndex = stats.mStringCache.indexOf(event.mPackage);
if (packageIndex >= 0) {
@@ -541,17 +554,33 @@ final class UsageStatsProto {
statsOut.keyguardHiddenTracker);
break;
case (int) IntervalStatsProto.STRINGPOOL:
stringPool = readStringPool(proto);
statsOut.mStringCache.addAll(stringPool);
try {
stringPool = readStringPool(proto);
statsOut.mStringCache.addAll(stringPool);
} catch (IOException e) {
Slog.e(TAG, "Unable to read string pool from proto.", e);
}
break;
case (int) IntervalStatsProto.PACKAGES:
loadUsageStats(proto, IntervalStatsProto.PACKAGES, statsOut, stringPool);
try {
loadUsageStats(proto, IntervalStatsProto.PACKAGES, statsOut, stringPool);
} catch (IOException e) {
Slog.e(TAG, "Unable to read some usage stats from proto.", e);
}
break;
case (int) IntervalStatsProto.CONFIGURATIONS:
loadConfigStats(proto, IntervalStatsProto.CONFIGURATIONS, statsOut);
try {
loadConfigStats(proto, IntervalStatsProto.CONFIGURATIONS, statsOut);
} catch (IOException e) {
Slog.e(TAG, "Unable to read some configuration stats from proto.", e);
}
break;
case (int) IntervalStatsProto.EVENT_LOG:
loadEvent(proto, IntervalStatsProto.EVENT_LOG, statsOut, stringPool);
try {
loadEvent(proto, IntervalStatsProto.EVENT_LOG, statsOut, stringPool);
} catch (IOException e) {
Slog.e(TAG, "Unable to read some events from proto.", e);
}
break;
case ProtoInputStream.NO_MORE_FIELDS:
if (statsOut.endTime == 0) {
@@ -570,37 +599,58 @@ final class UsageStatsProto {
* @param proto The serializer to which to write the packageStats data.
* @param stats The stats object to write to the XML file.
*/
public static void write(OutputStream out, IntervalStats stats) throws IOException {
public static void write(OutputStream out, IntervalStats stats)
throws IOException, IllegalArgumentException {
final ProtoOutputStream proto = new ProtoOutputStream(out);
proto.write(IntervalStatsProto.END_TIME_MS, stats.endTime - stats.beginTime);
proto.write(IntervalStatsProto.MAJOR_VERSION, stats.majorVersion);
proto.write(IntervalStatsProto.MINOR_VERSION, stats.minorVersion);
// String pool should be written before the rest of the usage stats
writeStringPool(proto, stats);
try {
writeStringPool(proto, stats);
} catch (IllegalArgumentException e) {
Slog.e(TAG, "Unable to write string pool to proto.", e);
}
writeCountAndTime(proto, IntervalStatsProto.INTERACTIVE, stats.interactiveTracker.count,
stats.interactiveTracker.duration);
writeCountAndTime(proto, IntervalStatsProto.NON_INTERACTIVE,
stats.nonInteractiveTracker.count, stats.nonInteractiveTracker.duration);
writeCountAndTime(proto, IntervalStatsProto.KEYGUARD_SHOWN,
stats.keyguardShownTracker.count, stats.keyguardShownTracker.duration);
writeCountAndTime(proto, IntervalStatsProto.KEYGUARD_HIDDEN,
stats.keyguardHiddenTracker.count, stats.keyguardHiddenTracker.duration);
try {
writeCountAndTime(proto, IntervalStatsProto.INTERACTIVE, stats.interactiveTracker.count,
stats.interactiveTracker.duration);
writeCountAndTime(proto, IntervalStatsProto.NON_INTERACTIVE,
stats.nonInteractiveTracker.count, stats.nonInteractiveTracker.duration);
writeCountAndTime(proto, IntervalStatsProto.KEYGUARD_SHOWN,
stats.keyguardShownTracker.count, stats.keyguardShownTracker.duration);
writeCountAndTime(proto, IntervalStatsProto.KEYGUARD_HIDDEN,
stats.keyguardHiddenTracker.count, stats.keyguardHiddenTracker.duration);
} catch (IllegalArgumentException e) {
Slog.e(TAG, "Unable to write some interval stats trackers to proto.", e);
}
final int statsCount = stats.packageStats.size();
for (int i = 0; i < statsCount; i++) {
writeUsageStats(proto, IntervalStatsProto.PACKAGES, stats,
stats.packageStats.valueAt(i));
try {
writeUsageStats(proto, IntervalStatsProto.PACKAGES, stats,
stats.packageStats.valueAt(i));
} catch (IllegalArgumentException e) {
Slog.e(TAG, "Unable to write some usage stats to proto.", e);
}
}
final int configCount = stats.configurations.size();
for (int i = 0; i < configCount; i++) {
boolean active = stats.activeConfiguration.equals(stats.configurations.keyAt(i));
writeConfigStats(proto, IntervalStatsProto.CONFIGURATIONS, stats,
stats.configurations.valueAt(i), active);
try {
writeConfigStats(proto, IntervalStatsProto.CONFIGURATIONS, stats,
stats.configurations.valueAt(i), active);
} catch (IllegalArgumentException e) {
Slog.e(TAG, "Unable to write some configuration stats to proto.", e);
}
}
final int eventCount = stats.events.size();
for (int i = 0; i < eventCount; i++) {
writeEvent(proto, IntervalStatsProto.EVENT_LOG, stats, stats.events.get(i));
try {
writeEvent(proto, IntervalStatsProto.EVENT_LOG, stats, stats.events.get(i));
} catch (IllegalArgumentException e) {
Slog.e(TAG, "Unable to write some events to proto.", e);
}
}
proto.flush();

View File

@@ -19,6 +19,7 @@ import android.app.usage.ConfigurationStats;
import android.app.usage.UsageEvents;
import android.app.usage.UsageStats;
import android.content.res.Configuration;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseIntArray;
import android.util.proto.ProtoInputStream;
@@ -62,9 +63,13 @@ final class UsageStatsProtoV2 {
UsageStatsObfuscatedProto.APP_LAUNCH_COUNT);
break;
case (int) UsageStatsObfuscatedProto.CHOOSER_ACTIONS:
final long token = proto.start(UsageStatsObfuscatedProto.CHOOSER_ACTIONS);
loadChooserCounts(proto, stats);
proto.end(token);
try {
final long token = proto.start(UsageStatsObfuscatedProto.CHOOSER_ACTIONS);
loadChooserCounts(proto, stats);
proto.end(token);
} catch (IOException e) {
Slog.e(TAG, "Unable to read chooser counts for " + stats.mPackageToken);
}
break;
case (int) UsageStatsObfuscatedProto.LAST_TIME_SERVICE_USED_MS:
stats.mLastTimeForegroundServiceUsed = beginTime + proto.readLong(
@@ -93,21 +98,26 @@ final class UsageStatsProtoV2 {
}
private static void loadCountAndTime(ProtoInputStream proto, long fieldId,
IntervalStats.EventTracker tracker) throws IOException {
final long token = proto.start(fieldId);
while (true) {
switch (proto.nextField()) {
case (int) IntervalStatsObfuscatedProto.CountAndTime.COUNT:
tracker.count = proto.readInt(IntervalStatsObfuscatedProto.CountAndTime.COUNT);
break;
case (int) IntervalStatsObfuscatedProto.CountAndTime.TIME_MS:
tracker.duration = proto.readLong(
IntervalStatsObfuscatedProto.CountAndTime.TIME_MS);
break;
case ProtoInputStream.NO_MORE_FIELDS:
proto.end(token);
return;
IntervalStats.EventTracker tracker) {
try {
final long token = proto.start(fieldId);
while (true) {
switch (proto.nextField()) {
case (int) IntervalStatsObfuscatedProto.CountAndTime.COUNT:
tracker.count = proto.readInt(
IntervalStatsObfuscatedProto.CountAndTime.COUNT);
break;
case (int) IntervalStatsObfuscatedProto.CountAndTime.TIME_MS:
tracker.duration = proto.readLong(
IntervalStatsObfuscatedProto.CountAndTime.TIME_MS);
break;
case ProtoInputStream.NO_MORE_FIELDS:
proto.end(token);
return;
}
}
} catch (IOException e) {
Slog.e(TAG, "Unable to read event tracker " + fieldId, e);
}
}
@@ -278,7 +288,7 @@ final class UsageStatsProtoV2 {
}
private static void writeUsageStats(ProtoOutputStream proto, final long beginTime,
final UsageStats stats) throws IOException {
final UsageStats stats) throws IllegalArgumentException {
// Time attributes stored as an offset of the beginTime.
proto.write(UsageStatsObfuscatedProto.PACKAGE_TOKEN, stats.mPackageToken + 1);
proto.write(UsageStatsObfuscatedProto.LAST_TIME_ACTIVE_MS, stats.mLastTimeUsed - beginTime);
@@ -291,11 +301,15 @@ final class UsageStatsProtoV2 {
stats.mLastTimeVisible - beginTime);
proto.write(UsageStatsObfuscatedProto.TOTAL_TIME_VISIBLE_MS, stats.mTotalTimeVisible);
proto.write(UsageStatsObfuscatedProto.APP_LAUNCH_COUNT, stats.mAppLaunchCount);
writeChooserCounts(proto, stats);
try {
writeChooserCounts(proto, stats);
} catch (IllegalArgumentException e) {
Slog.e(TAG, "Unable to write chooser counts for " + stats.mPackageName, e);
}
}
private static void writeCountAndTime(ProtoOutputStream proto, long fieldId, int count,
long time) throws IOException {
long time) throws IllegalArgumentException {
final long token = proto.start(fieldId);
proto.write(IntervalStatsObfuscatedProto.CountAndTime.COUNT, count);
proto.write(IntervalStatsObfuscatedProto.CountAndTime.TIME_MS, time);
@@ -303,7 +317,7 @@ final class UsageStatsProtoV2 {
}
private static void writeChooserCounts(ProtoOutputStream proto, final UsageStats stats)
throws IOException {
throws IllegalArgumentException {
if (stats == null || stats.mChooserCountsObfuscated.size() == 0) {
return;
}
@@ -322,7 +336,7 @@ final class UsageStatsProtoV2 {
}
private static void writeCountsForAction(ProtoOutputStream proto, SparseIntArray counts)
throws IOException {
throws IllegalArgumentException {
final int countsSize = counts.size();
for (int i = 0; i < countsSize; i++) {
final int category = counts.keyAt(i);
@@ -339,7 +353,8 @@ final class UsageStatsProtoV2 {
}
private static void writeConfigStats(ProtoOutputStream proto, final long statsBeginTime,
final ConfigurationStats configStats, boolean isActive) throws IOException {
final ConfigurationStats configStats, boolean isActive)
throws IllegalArgumentException {
configStats.mConfiguration.writeToProto(proto,
IntervalStatsObfuscatedProto.Configuration.CONFIG);
proto.write(IntervalStatsObfuscatedProto.Configuration.LAST_TIME_ACTIVE_MS,
@@ -351,7 +366,7 @@ final class UsageStatsProtoV2 {
}
private static void writeEvent(ProtoOutputStream proto, final long statsBeginTime,
final UsageEvents.Event event) throws IOException {
final UsageEvents.Event event) throws IllegalArgumentException {
proto.write(EventObfuscatedProto.PACKAGE_TOKEN, event.mPackageToken + 1);
if (event.mClassToken != PackagesTokenData.UNASSIGNED_TOKEN) {
proto.write(EventObfuscatedProto.CLASS_TOKEN, event.mClassToken + 1);
@@ -429,25 +444,39 @@ final class UsageStatsProtoV2 {
stats.keyguardHiddenTracker);
break;
case (int) IntervalStatsObfuscatedProto.PACKAGES:
final long packagesToken = proto.start(IntervalStatsObfuscatedProto.PACKAGES);
UsageStats usageStats = parseUsageStats(proto, stats.beginTime);
if (usageStats.mPackageToken != PackagesTokenData.UNASSIGNED_TOKEN) {
stats.packageStatsObfuscated.put(usageStats.mPackageToken, usageStats);
try {
final long packagesToken = proto.start(
IntervalStatsObfuscatedProto.PACKAGES);
UsageStats usageStats = parseUsageStats(proto, stats.beginTime);
if (usageStats.mPackageToken != PackagesTokenData.UNASSIGNED_TOKEN) {
stats.packageStatsObfuscated.put(usageStats.mPackageToken, usageStats);
}
proto.end(packagesToken);
} catch (IOException e) {
Slog.e(TAG, "Unable to read some usage stats from proto.", e);
}
proto.end(packagesToken);
break;
case (int) IntervalStatsObfuscatedProto.CONFIGURATIONS:
final long configsToken = proto.start(
IntervalStatsObfuscatedProto.CONFIGURATIONS);
loadConfigStats(proto, stats);
proto.end(configsToken);
try {
final long configsToken = proto.start(
IntervalStatsObfuscatedProto.CONFIGURATIONS);
loadConfigStats(proto, stats);
proto.end(configsToken);
} catch (IOException e) {
Slog.e(TAG, "Unable to read some configuration stats from proto.", e);
}
break;
case (int) IntervalStatsObfuscatedProto.EVENT_LOG:
final long eventsToken = proto.start(IntervalStatsObfuscatedProto.EVENT_LOG);
UsageEvents.Event event = parseEvent(proto, stats.beginTime);
proto.end(eventsToken);
if (event != null) {
stats.events.insert(event);
try {
final long eventsToken = proto.start(
IntervalStatsObfuscatedProto.EVENT_LOG);
UsageEvents.Event event = parseEvent(proto, stats.beginTime);
proto.end(eventsToken);
if (event != null) {
stats.events.insert(event);
}
} catch (IOException e) {
Slog.e(TAG, "Unable to read some events from proto.", e);
}
break;
case ProtoInputStream.NO_MORE_FIELDS:
@@ -466,39 +495,56 @@ final class UsageStatsProtoV2 {
* @param out the output stream to which to write the interval stats data.
* @param stats the interval stats object to write to the proto file.
*/
public static void write(OutputStream out, IntervalStats stats) throws IOException {
public static void write(OutputStream out, IntervalStats stats)
throws IOException, IllegalArgumentException {
final ProtoOutputStream proto = new ProtoOutputStream(out);
proto.write(IntervalStatsObfuscatedProto.END_TIME_MS, stats.endTime - stats.beginTime);
proto.write(IntervalStatsObfuscatedProto.MAJOR_VERSION, stats.majorVersion);
proto.write(IntervalStatsObfuscatedProto.MINOR_VERSION, stats.minorVersion);
writeCountAndTime(proto, IntervalStatsObfuscatedProto.INTERACTIVE,
stats.interactiveTracker.count, stats.interactiveTracker.duration);
writeCountAndTime(proto, IntervalStatsObfuscatedProto.NON_INTERACTIVE,
stats.nonInteractiveTracker.count, stats.nonInteractiveTracker.duration);
writeCountAndTime(proto, IntervalStatsObfuscatedProto.KEYGUARD_SHOWN,
stats.keyguardShownTracker.count, stats.keyguardShownTracker.duration);
writeCountAndTime(proto, IntervalStatsObfuscatedProto.KEYGUARD_HIDDEN,
stats.keyguardHiddenTracker.count, stats.keyguardHiddenTracker.duration);
try {
writeCountAndTime(proto, IntervalStatsObfuscatedProto.INTERACTIVE,
stats.interactiveTracker.count, stats.interactiveTracker.duration);
writeCountAndTime(proto, IntervalStatsObfuscatedProto.NON_INTERACTIVE,
stats.nonInteractiveTracker.count, stats.nonInteractiveTracker.duration);
writeCountAndTime(proto, IntervalStatsObfuscatedProto.KEYGUARD_SHOWN,
stats.keyguardShownTracker.count, stats.keyguardShownTracker.duration);
writeCountAndTime(proto, IntervalStatsObfuscatedProto.KEYGUARD_HIDDEN,
stats.keyguardHiddenTracker.count, stats.keyguardHiddenTracker.duration);
} catch (IllegalArgumentException e) {
Slog.e(TAG, "Unable to write some interval stats trackers to proto.", e);
}
final int statsCount = stats.packageStatsObfuscated.size();
for (int i = 0; i < statsCount; i++) {
final long token = proto.start(IntervalStatsObfuscatedProto.PACKAGES);
writeUsageStats(proto, stats.beginTime, stats.packageStatsObfuscated.valueAt(i));
proto.end(token);
try {
final long token = proto.start(IntervalStatsObfuscatedProto.PACKAGES);
writeUsageStats(proto, stats.beginTime, stats.packageStatsObfuscated.valueAt(i));
proto.end(token);
} catch (IllegalArgumentException e) {
Slog.e(TAG, "Unable to write some usage stats to proto.", e);
}
}
final int configCount = stats.configurations.size();
for (int i = 0; i < configCount; i++) {
boolean active = stats.activeConfiguration.equals(stats.configurations.keyAt(i));
final long token = proto.start(IntervalStatsObfuscatedProto.CONFIGURATIONS);
writeConfigStats(proto, stats.beginTime, stats.configurations.valueAt(i), active);
proto.end(token);
try {
final long token = proto.start(IntervalStatsObfuscatedProto.CONFIGURATIONS);
writeConfigStats(proto, stats.beginTime, stats.configurations.valueAt(i), active);
proto.end(token);
} catch (IllegalArgumentException e) {
Slog.e(TAG, "Unable to write some configuration stats to proto.", e);
}
}
final int eventCount = stats.events.size();
for (int i = 0; i < eventCount; i++) {
final long token = proto.start(IntervalStatsObfuscatedProto.EVENT_LOG);
writeEvent(proto, stats.beginTime, stats.events.get(i));
proto.end(token);
try {
final long token = proto.start(IntervalStatsObfuscatedProto.EVENT_LOG);
writeEvent(proto, stats.beginTime, stats.events.get(i));
proto.end(token);
} catch (IllegalArgumentException e) {
Slog.e(TAG, "Unable to write some events to proto.", e);
}
}
proto.flush();
@@ -559,7 +605,7 @@ final class UsageStatsProtoV2 {
* @param packagesTokenData the packages data object holding the data to write.
*/
static void writeObfuscatedData(OutputStream out, PackagesTokenData packagesTokenData)
throws IOException {
throws IOException, IllegalArgumentException {
final ProtoOutputStream proto = new ProtoOutputStream(out);
proto.write(ObfuscatedPackagesProto.COUNTER, packagesTokenData.counter);
@@ -660,11 +706,15 @@ final class UsageStatsProtoV2 {
while (true) {
switch (proto.nextField()) {
case (int) IntervalStatsObfuscatedProto.PENDING_EVENTS:
final long token = proto.start(IntervalStatsObfuscatedProto.PENDING_EVENTS);
UsageEvents.Event event = parsePendingEvent(proto);
proto.end(token);
if (event != null) {
events.add(event);
try {
final long token = proto.start(IntervalStatsObfuscatedProto.PENDING_EVENTS);
UsageEvents.Event event = parsePendingEvent(proto);
proto.end(token);
if (event != null) {
events.add(event);
}
} catch (IOException e) {
Slog.e(TAG, "Unable to parse some pending events from proto.", e);
}
break;
case ProtoInputStream.NO_MORE_FIELDS:
@@ -673,6 +723,47 @@ final class UsageStatsProtoV2 {
}
}
private static void writePendingEvent(ProtoOutputStream proto, UsageEvents.Event event)
throws IllegalArgumentException {
proto.write(PendingEventProto.PACKAGE_NAME, event.mPackage);
if (event.mClass != null) {
proto.write(PendingEventProto.CLASS_NAME, event.mClass);
}
proto.write(PendingEventProto.TIME_MS, event.mTimeStamp);
proto.write(PendingEventProto.FLAGS, event.mFlags);
proto.write(PendingEventProto.TYPE, event.mEventType);
proto.write(PendingEventProto.INSTANCE_ID, event.mInstanceId);
if (event.mTaskRootPackage != null) {
proto.write(PendingEventProto.TASK_ROOT_PACKAGE, event.mTaskRootPackage);
}
if (event.mTaskRootClass != null) {
proto.write(PendingEventProto.TASK_ROOT_CLASS, event.mTaskRootClass);
}
switch (event.mEventType) {
case UsageEvents.Event.CONFIGURATION_CHANGE:
if (event.mConfiguration != null) {
event.mConfiguration.writeToProto(proto, PendingEventProto.CONFIG);
}
break;
case UsageEvents.Event.STANDBY_BUCKET_CHANGED:
if (event.mBucketAndReason != 0) {
proto.write(PendingEventProto.STANDBY_BUCKET, event.mBucketAndReason);
}
break;
case UsageEvents.Event.SHORTCUT_INVOCATION:
if (event.mShortcutId != null) {
proto.write(PendingEventProto.SHORTCUT_ID, event.mShortcutId);
}
break;
case UsageEvents.Event.NOTIFICATION_INTERRUPTION:
if (event.mNotificationChannelId != null) {
proto.write(PendingEventProto.NOTIFICATION_CHANNEL_ID,
event.mNotificationChannelId);
}
break;
}
}
/**
* Writes the pending events to a ProtoBuf file.
*
@@ -680,50 +771,17 @@ final class UsageStatsProtoV2 {
* @param events the list of pending events.
*/
static void writePendingEvents(OutputStream out, LinkedList<UsageEvents.Event> events)
throws IOException {
throws IOException, IllegalArgumentException {
final ProtoOutputStream proto = new ProtoOutputStream(out);
final int eventCount = events.size();
for (int i = 0; i < eventCount; i++) {
final long token = proto.start(IntervalStatsObfuscatedProto.PENDING_EVENTS);
final UsageEvents.Event event = events.get(i);
proto.write(PendingEventProto.PACKAGE_NAME, event.mPackage);
if (event.mClass != null) {
proto.write(PendingEventProto.CLASS_NAME, event.mClass);
try {
final long token = proto.start(IntervalStatsObfuscatedProto.PENDING_EVENTS);
writePendingEvent(proto, events.get(i));
proto.end(token);
} catch (IllegalArgumentException e) {
Slog.e(TAG, "Unable to write some pending events to proto.", e);
}
proto.write(PendingEventProto.TIME_MS, event.mTimeStamp);
proto.write(PendingEventProto.FLAGS, event.mFlags);
proto.write(PendingEventProto.TYPE, event.mEventType);
proto.write(PendingEventProto.INSTANCE_ID, event.mInstanceId);
if (event.mTaskRootPackage != null) {
proto.write(PendingEventProto.TASK_ROOT_PACKAGE, event.mTaskRootPackage);
}
if (event.mTaskRootClass != null) {
proto.write(PendingEventProto.TASK_ROOT_CLASS, event.mTaskRootClass);
}
switch (event.mEventType) {
case UsageEvents.Event.CONFIGURATION_CHANGE:
if (event.mConfiguration != null) {
event.mConfiguration.writeToProto(proto, PendingEventProto.CONFIG);
}
break;
case UsageEvents.Event.STANDBY_BUCKET_CHANGED:
if (event.mBucketAndReason != 0) {
proto.write(PendingEventProto.STANDBY_BUCKET, event.mBucketAndReason);
}
break;
case UsageEvents.Event.SHORTCUT_INVOCATION:
if (event.mShortcutId != null) {
proto.write(PendingEventProto.SHORTCUT_ID, event.mShortcutId);
}
break;
case UsageEvents.Event.NOTIFICATION_INTERRUPTION:
if (event.mNotificationChannelId != null) {
proto.write(PendingEventProto.NOTIFICATION_CHANNEL_ID,
event.mNotificationChannelId);
}
break;
}
proto.end(token);
}
proto.flush();
}

View File

@@ -654,7 +654,7 @@ public class UsageStatsService extends SystemService implements
af.finishWrite(fos);
fos = null;
pendingEvents.clear();
} catch (IOException e) {
} catch (IOException | IllegalArgumentException e) {
Slog.e(TAG, "Failed to write " + pendingEventsFile.getAbsolutePath()
+ " for user " + userId);
} finally {