diff options
| author | 2019-09-17 13:48:38 -0700 | |
|---|---|---|
| committer | 2019-10-08 21:24:15 -0700 | |
| commit | 358d5006234b1e5f0feb95f72ef42e592d28e661 (patch) | |
| tree | 36966604501cff3b3f776af1aa7bb2c0005f6d67 | |
| parent | e1ba9cde53546caca0befe65611bac10f7f67622 (diff) | |
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
8 files changed, 544 insertions, 156 deletions
diff --git a/core/java/android/app/usage/EventList.java b/core/java/android/app/usage/EventList.java index 8c0340585573..afdcbe6d164f 100644 --- a/core/java/android/app/usage/EventList.java +++ b/core/java/android/app/usage/EventList.java @@ -79,6 +79,21 @@ public class EventList { } /** + * 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. * diff --git a/services/tests/servicestests/src/com/android/server/usage/IntervalStatsTests.java b/services/tests/servicestests/src/com/android/server/usage/IntervalStatsTests.java new file mode 100644 index 000000000000..f1b2ef811885 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/usage/IntervalStatsTests.java @@ -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); + } +} diff --git a/services/usage/java/com/android/server/usage/IntervalStats.java b/services/usage/java/com/android/server/usage/IntervalStats.java index 02e48928a7ba..7ea669d1f0a2 100644 --- a/services/usage/java/com/android/server/usage/IntervalStats.java +++ b/services/usage/java/com/android/server/usage/IntervalStats.java @@ -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; } } diff --git a/services/usage/java/com/android/server/usage/PackagesTokenData.java b/services/usage/java/com/android/server/usage/PackagesTokenData.java index 8e4c639a54e4..3beee678d7ff 100644 --- a/services/usage/java/com/android/server/usage/PackagesTokenData.java +++ b/services/usage/java/com/android/server/usage/PackagesTokenData.java @@ -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; + } } } diff --git a/services/usage/java/com/android/server/usage/UsageStatsDatabase.java b/services/usage/java/com/android/server/usage/UsageStatsDatabase.java index d29b77c78e1c..5c785f76d4db 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsDatabase.java +++ b/services/usage/java/com/android/server/usage/UsageStatsDatabase.java @@ -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); } diff --git a/services/usage/java/com/android/server/usage/UsageStatsProto.java b/services/usage/java/com/android/server/usage/UsageStatsProto.java index 53ab230ef4a1..5d1f730694ef 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsProto.java +++ b/services/usage/java/com/android/server/usage/UsageStatsProto.java @@ -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(); diff --git a/services/usage/java/com/android/server/usage/UsageStatsProtoV2.java b/services/usage/java/com/android/server/usage/UsageStatsProtoV2.java index badb3eef714e..7d8e430e5416 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsProtoV2.java +++ b/services/usage/java/com/android/server/usage/UsageStatsProtoV2.java @@ -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); - } - 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; + 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.end(token); } proto.flush(); } diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java index 8e392a7a8456..b12d9008689a 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UsageStatsService.java @@ -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 { |