diff options
266 files changed, 11774 insertions, 4090 deletions
diff --git a/Android.bp b/Android.bp index 2dcbc92974a9..69ee848cadad 100644 --- a/Android.bp +++ b/Android.bp @@ -256,6 +256,7 @@ java_library { "core/java/android/service/euicc/IGetEidCallback.aidl", "core/java/android/service/euicc/IGetEuiccInfoCallback.aidl", "core/java/android/service/euicc/IGetEuiccProfileInfoListCallback.aidl", + "core/java/android/service/euicc/IGetOtaStatusCallback.aidl", "core/java/android/service/euicc/IRetainSubscriptionsForFactoryResetCallback.aidl", "core/java/android/service/euicc/ISwitchToSubscriptionCallback.aidl", "core/java/android/service/euicc/IUpdateSubscriptionNicknameCallback.aidl", @@ -721,11 +722,13 @@ gensrcs { ], srcs: [ + "core/proto/android/os/batterytype.proto", "core/proto/android/os/cpufreq.proto", "core/proto/android/os/cpuinfo.proto", "core/proto/android/os/kernelwake.proto", "core/proto/android/os/pagetypeinfo.proto", "core/proto/android/os/procrank.proto", + "core/proto/android/os/ps.proto", "core/proto/android/os/system_properties.proto", ], diff --git a/Android.mk b/Android.mk index a19f2d90d9bb..0e363d2491c8 100644 --- a/Android.mk +++ b/Android.mk @@ -252,12 +252,26 @@ aidl_files := \ system/netd/server/binder/android/net/UidRange.aidl \ frameworks/base/telephony/java/android/telephony/PcoData.aidl \ +aidl_parcelables := +define stubs-to-aidl-parcelables + gen := $(TARGET_OUT_COMMON_INTERMEDIATES)/$1.aidl + aidl_parcelables += $$(gen) + $$(gen): $(call java-lib-header-files,$1) | $(HOST_OUT_EXECUTABLES)/sdkparcelables + @echo Extract SDK parcelables: $$@ + rm -f $$@ + $(HOST_OUT_EXECUTABLES)/sdkparcelables $$< $$@ +endef + +$(foreach stubs,android_stubs_current android_test_stubs_current android_system_stubs_current,\ + $(eval $(call stubs-to-aidl-parcelables,$(stubs)))) + gen := $(TARGET_OUT_COMMON_INTERMEDIATES)/framework.aidl -$(gen): PRIVATE_SRC_FILES := $(aidl_files) -ALL_SDK_FILES += $(gen) -$(gen): $(aidl_files) | $(AIDL) - @echo Aidl Preprocess: $@ - $(hide) $(AIDL) --preprocess $@ $(PRIVATE_SRC_FILES) +.KATI_RESTAT: $(gen) +$(gen): $(aidl_parcelables) + @echo Combining SDK parcelables: $@ + rm -f $@.tmp + cat $^ | sort -u > $@.tmp + $(call commit-change-for-toc,$@) # the documentation # ============================================================ @@ -554,8 +568,6 @@ LOCAL_UNINSTALLABLE_MODULE := true include $(BUILD_DROIDDOC) -# $(gen), i.e. framework.aidl, is also needed while building against the current stub. -$(full_target): $(gen) $(INTERNAL_PLATFORM_API_FILE): $(full_target) $(call dist-for-goals,sdk,$(INTERNAL_PLATFORM_API_FILE)) @@ -591,8 +603,6 @@ LOCAL_UNINSTALLABLE_MODULE := true include $(BUILD_DROIDDOC) -# $(gen), i.e. framework.aidl, is also needed while building against the current stub. -$(full_target): $(gen) $(INTERNAL_PLATFORM_SYSTEM_API_FILE): $(full_target) $(call dist-for-goals,sdk,$(INTERNAL_PLATFORM_SYSTEM_API_FILE)) @@ -629,8 +639,6 @@ LOCAL_UNINSTALLABLE_MODULE := true include $(BUILD_DROIDDOC) -# $(gen), i.e. framework.aidl, is also needed while building against the current stub. -$(full_target): $(gen) $(INTERNAL_PLATFORM_TEST_API_FILE): $(full_target) $(call dist-for-goals,sdk,$(INTERNAL_PLATFORM_TEST_API_FILE)) @@ -660,9 +668,6 @@ LOCAL_UNINSTALLABLE_MODULE := true include $(BUILD_DROIDDOC) -# $(gen), i.e. framework.aidl, is also needed while building against the current stub. -$(full_target): $(gen) - # Run this for checkbuild checkbuild: doc-comment-check-docs # Check comment when you are updating the API diff --git a/api/current.txt b/api/current.txt index c537622b60ca..9741fbc0db7f 100644 --- a/api/current.txt +++ b/api/current.txt @@ -21718,6 +21718,7 @@ package android.media { field public static final java.lang.String ACTION_AUDIO_BECOMING_NOISY = "android.media.AUDIO_BECOMING_NOISY"; field public static final java.lang.String ACTION_HDMI_AUDIO_PLUG = "android.media.action.HDMI_AUDIO_PLUG"; field public static final java.lang.String ACTION_HEADSET_PLUG = "android.intent.action.HEADSET_PLUG"; + field public static final java.lang.String ACTION_MICROPHONE_MUTE_CHANGED = "android.media.action.MICROPHONE_MUTE_CHANGED"; field public static final deprecated java.lang.String ACTION_SCO_AUDIO_STATE_CHANGED = "android.media.SCO_AUDIO_STATE_CHANGED"; field public static final java.lang.String ACTION_SCO_AUDIO_STATE_UPDATED = "android.media.ACTION_SCO_AUDIO_STATE_UPDATED"; field public static final int ADJUST_LOWER = -1; // 0xffffffff @@ -31569,6 +31570,7 @@ package android.os { method public final boolean postAtTime(java.lang.Runnable, long); method public final boolean postAtTime(java.lang.Runnable, java.lang.Object, long); method public final boolean postDelayed(java.lang.Runnable, long); + method public final boolean postDelayed(java.lang.Runnable, java.lang.Object, long); method public final void removeCallbacks(java.lang.Runnable); method public final void removeCallbacks(java.lang.Runnable, java.lang.Object); method public final void removeCallbacksAndMessages(java.lang.Object); diff --git a/api/system-current.txt b/api/system-current.txt index 3e781670c784..596474c2f59f 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -1340,9 +1340,30 @@ package android.hardware.hdmi { package android.hardware.location { + public class ContextHubClient implements java.io.Closeable { + method public void close(); + method public android.hardware.location.ContextHubInfo getAttachedHub(); + method public int sendMessageToNanoApp(android.hardware.location.NanoAppMessage); + } + + public class ContextHubClientCallback { + ctor public ContextHubClientCallback(); + method public void onHubReset(android.hardware.location.ContextHubClient); + method public void onMessageFromNanoApp(android.hardware.location.ContextHubClient, android.hardware.location.NanoAppMessage); + method public void onNanoAppAborted(android.hardware.location.ContextHubClient, long, int); + method public void onNanoAppDisabled(android.hardware.location.ContextHubClient, long); + method public void onNanoAppEnabled(android.hardware.location.ContextHubClient, long); + method public void onNanoAppLoaded(android.hardware.location.ContextHubClient, long); + method public void onNanoAppUnloaded(android.hardware.location.ContextHubClient, long); + } + public class ContextHubInfo implements android.os.Parcelable { ctor public ContextHubInfo(); method public int describeContents(); + method public byte getChreApiMajorVersion(); + method public byte getChreApiMinorVersion(); + method public short getChrePatchVersion(); + method public long getChrePlatformId(); method public int getId(); method public int getMaxPacketLengthBytes(); method public android.hardware.location.MemoryRegion[] getMemoryRegions(); @@ -1362,19 +1383,27 @@ package android.hardware.location { } public final class ContextHubManager { - method public int[] findNanoAppOnHub(int, android.hardware.location.NanoAppFilter); - method public int[] getContextHubHandles(); - method public android.hardware.location.ContextHubInfo getContextHubInfo(int); - method public android.hardware.location.NanoAppInstanceInfo getNanoAppInstanceInfo(int); - method public int loadNanoApp(int, android.hardware.location.NanoApp); - method public int registerCallback(android.hardware.location.ContextHubManager.Callback); - method public int registerCallback(android.hardware.location.ContextHubManager.Callback, android.os.Handler); - method public int sendMessage(int, int, android.hardware.location.ContextHubMessage); - method public int unloadNanoApp(int); - method public int unregisterCallback(android.hardware.location.ContextHubManager.Callback); - } - - public static abstract class ContextHubManager.Callback { + method public android.hardware.location.ContextHubClient createClient(android.hardware.location.ContextHubInfo, android.hardware.location.ContextHubClientCallback, java.util.concurrent.Executor); + method public android.hardware.location.ContextHubClient createClient(android.hardware.location.ContextHubInfo, android.hardware.location.ContextHubClientCallback); + method public android.hardware.location.ContextHubTransaction<java.lang.Void> disableNanoApp(android.hardware.location.ContextHubInfo, long); + method public android.hardware.location.ContextHubTransaction<java.lang.Void> enableNanoApp(android.hardware.location.ContextHubInfo, long); + method public deprecated int[] findNanoAppOnHub(int, android.hardware.location.NanoAppFilter); + method public deprecated int[] getContextHubHandles(); + method public deprecated android.hardware.location.ContextHubInfo getContextHubInfo(int); + method public java.util.List<android.hardware.location.ContextHubInfo> getContextHubs(); + method public deprecated android.hardware.location.NanoAppInstanceInfo getNanoAppInstanceInfo(int); + method public deprecated int loadNanoApp(int, android.hardware.location.NanoApp); + method public android.hardware.location.ContextHubTransaction<java.lang.Void> loadNanoApp(android.hardware.location.ContextHubInfo, android.hardware.location.NanoAppBinary); + method public android.hardware.location.ContextHubTransaction<java.util.List<android.hardware.location.NanoAppState>> queryNanoApps(android.hardware.location.ContextHubInfo); + method public deprecated int registerCallback(android.hardware.location.ContextHubManager.Callback); + method public deprecated int registerCallback(android.hardware.location.ContextHubManager.Callback, android.os.Handler); + method public deprecated int sendMessage(int, int, android.hardware.location.ContextHubMessage); + method public deprecated int unloadNanoApp(int); + method public android.hardware.location.ContextHubTransaction<java.lang.Void> unloadNanoApp(android.hardware.location.ContextHubInfo, long); + method public deprecated int unregisterCallback(android.hardware.location.ContextHubManager.Callback); + } + + public static abstract deprecated class ContextHubManager.Callback { ctor protected ContextHubManager.Callback(); method public abstract void onMessageReceipt(int, int, android.hardware.location.ContextHubMessage); } @@ -1392,6 +1421,37 @@ package android.hardware.location { field public static final android.os.Parcelable.Creator<android.hardware.location.ContextHubMessage> CREATOR; } + public class ContextHubTransaction<T> { + method public int getType(); + method public void setOnCompleteListener(android.hardware.location.ContextHubTransaction.OnCompleteListener<T>, java.util.concurrent.Executor); + method public void setOnCompleteListener(android.hardware.location.ContextHubTransaction.OnCompleteListener<T>); + method public static java.lang.String typeToString(int, boolean); + method public android.hardware.location.ContextHubTransaction.Response<T> waitForResponse(long, java.util.concurrent.TimeUnit) throws java.lang.InterruptedException, java.util.concurrent.TimeoutException; + field public static final int RESULT_FAILED_AT_HUB = 5; // 0x5 + field public static final int RESULT_FAILED_BAD_PARAMS = 2; // 0x2 + field public static final int RESULT_FAILED_BUSY = 4; // 0x4 + field public static final int RESULT_FAILED_HAL_UNAVAILABLE = 8; // 0x8 + field public static final int RESULT_FAILED_SERVICE_INTERNAL_FAILURE = 7; // 0x7 + field public static final int RESULT_FAILED_TIMEOUT = 6; // 0x6 + field public static final int RESULT_FAILED_UNINITIALIZED = 3; // 0x3 + field public static final int RESULT_FAILED_UNKNOWN = 1; // 0x1 + field public static final int RESULT_SUCCESS = 0; // 0x0 + field public static final int TYPE_DISABLE_NANOAPP = 3; // 0x3 + field public static final int TYPE_ENABLE_NANOAPP = 2; // 0x2 + field public static final int TYPE_LOAD_NANOAPP = 0; // 0x0 + field public static final int TYPE_QUERY_NANOAPPS = 4; // 0x4 + field public static final int TYPE_UNLOAD_NANOAPP = 1; // 0x1 + } + + public static abstract interface ContextHubTransaction.OnCompleteListener<L> { + method public abstract void onComplete(android.hardware.location.ContextHubTransaction<L>, android.hardware.location.ContextHubTransaction.Response<L>); + } + + public static class ContextHubTransaction.Response<R> { + method public R getContents(); + method public int getResult(); + } + public final class GeofenceHardware { method public boolean addGeofence(int, int, android.hardware.location.GeofenceHardwareRequest, android.hardware.location.GeofenceHardwareCallback); method public int[] getMonitoringTypes(); @@ -1508,6 +1568,25 @@ package android.hardware.location { field public static final android.os.Parcelable.Creator<android.hardware.location.NanoApp> CREATOR; } + public final class NanoAppBinary implements android.os.Parcelable { + ctor public NanoAppBinary(byte[]); + method public int describeContents(); + method public byte[] getBinary(); + method public byte[] getBinaryNoHeader(); + method public int getFlags(); + method public int getHeaderVersion(); + method public long getHwHubType(); + method public long getNanoAppId(); + method public int getNanoAppVersion(); + method public byte getTargetChreApiMajorVersion(); + method public byte getTargetChreApiMinorVersion(); + method public boolean hasValidHeader(); + method public boolean isEncrypted(); + method public boolean isSigned(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.hardware.location.NanoAppBinary> CREATOR; + } + public class NanoAppFilter { ctor public NanoAppFilter(long, int, int, long); method public int describeContents(); @@ -1541,6 +1620,28 @@ package android.hardware.location { field public static final android.os.Parcelable.Creator<android.hardware.location.NanoAppInstanceInfo> CREATOR; } + public final class NanoAppMessage implements android.os.Parcelable { + method public static android.hardware.location.NanoAppMessage createMessageFromNanoApp(long, int, byte[], boolean); + method public static android.hardware.location.NanoAppMessage createMessageToNanoApp(long, int, byte[]); + method public int describeContents(); + method public byte[] getMessageBody(); + method public int getMessageType(); + method public long getNanoAppId(); + method public boolean isBroadcastMessage(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.hardware.location.NanoAppMessage> CREATOR; + } + + public final class NanoAppState implements android.os.Parcelable { + ctor public NanoAppState(long, int, boolean); + method public int describeContents(); + method public long getNanoAppId(); + method public long getNanoAppVersion(); + method public boolean isEnabled(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.hardware.location.NanoAppState> CREATOR; + } + } package android.hardware.radio { @@ -4368,10 +4469,10 @@ package android.util { } public final class StatsManager { - method public boolean addConfiguration(java.lang.String, byte[], java.lang.String, java.lang.String); - method public byte[] getData(java.lang.String); + method public boolean addConfiguration(long, byte[], java.lang.String, java.lang.String); + method public byte[] getData(long); method public byte[] getMetadata(); - method public boolean removeConfiguration(java.lang.String); + method public boolean removeConfiguration(long); } } diff --git a/cmds/content/src/com/android/commands/content/Content.java b/cmds/content/src/com/android/commands/content/Content.java index 716bc5f24631..f75678b7fa1e 100644 --- a/cmds/content/src/com/android/commands/content/Content.java +++ b/cmds/content/src/com/android/commands/content/Content.java @@ -32,12 +32,10 @@ import android.os.Process; import android.os.UserHandle; import android.text.TextUtils; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; +import libcore.io.Streams; -import libcore.io.IoUtils; +import java.io.FileInputStream; +import java.io.FileOutputStream; /** * This class is a command line utility for manipulating content. A client @@ -122,13 +120,14 @@ public class Content { + "\n" + "usage: adb shell content read --uri <URI> [--user <USER_ID>]\n" + " Example:\n" - + " # cat default ringtone to a file, then pull to host\n" - + " adb shell 'content read --uri content://settings/system/ringtone >" - + " /mnt/sdcard/tmp.ogg' && adb pull /mnt/sdcard/tmp.ogg\n" + + " adb shell 'content read --uri content://settings/system/ringtone_cache' > host.ogg\n" + + "\n" + + "usage: adb shell content write --uri <URI> [--user <USER_ID>]\n" + + " Example:\n" + + " adb shell 'content write --uri content://settings/system/ringtone_cache' < host.ogg\n" + "\n" + "usage: adb shell content gettype --uri <URI> [--user <USER_ID>]\n" + " Example:\n" - + " # Show the mime-type of the URI\n" + " adb shell content gettype --uri content://media/internal/audio/media/\n" + "\n"; @@ -139,6 +138,7 @@ public class Content { private static final String ARGUMENT_QUERY = "query"; private static final String ARGUMENT_CALL = "call"; private static final String ARGUMENT_READ = "read"; + private static final String ARGUMENT_WRITE = "write"; private static final String ARGUMENT_GET_TYPE = "gettype"; private static final String ARGUMENT_WHERE = "--where"; private static final String ARGUMENT_BIND = "--bind"; @@ -179,6 +179,8 @@ public class Content { return parseCallCommand(); } else if (ARGUMENT_READ.equals(operation)) { return parseReadCommand(); + } else if (ARGUMENT_WRITE.equals(operation)) { + return parseWriteCommand(); } else if (ARGUMENT_GET_TYPE.equals(operation)) { return parseGetTypeCommand(); } else { @@ -339,6 +341,25 @@ public class Content { return new ReadCommand(uri, userId); } + private WriteCommand parseWriteCommand() { + Uri uri = null; + int userId = UserHandle.USER_SYSTEM; + for (String argument; (argument = mTokenizer.nextArg())!= null;) { + if (ARGUMENT_URI.equals(argument)) { + uri = Uri.parse(argumentValueRequired(argument)); + } else if (ARGUMENT_USER.equals(argument)) { + userId = Integer.parseInt(argumentValueRequired(argument)); + } else { + throw new IllegalArgumentException("Unsupported argument: " + argument); + } + } + if (uri == null) { + throw new IllegalArgumentException("Content provider URI not specified." + + " Did you specify --uri argument?"); + } + return new WriteCommand(uri, userId); + } + public QueryCommand parseQueryCommand() { Uri uri = null; int userId = UserHandle.USER_SYSTEM; @@ -561,20 +582,21 @@ public class Content { @Override public void onExecute(IContentProvider provider) throws Exception { - final ParcelFileDescriptor fd = provider.openFile(null, mUri, "r", null, null); - copy(new FileInputStream(fd.getFileDescriptor()), System.out); + try (ParcelFileDescriptor fd = provider.openFile(null, mUri, "r", null, null)) { + Streams.copy(new FileInputStream(fd.getFileDescriptor()), System.out); + } } + } - private static void copy(InputStream is, OutputStream os) throws IOException { - final byte[] buffer = new byte[8 * 1024]; - int read; - try { - while ((read = is.read(buffer)) > -1) { - os.write(buffer, 0, read); - } - } finally { - IoUtils.closeQuietly(is); - IoUtils.closeQuietly(os); + private static class WriteCommand extends Command { + public WriteCommand(Uri uri, int userId) { + super(uri, userId); + } + + @Override + public void onExecute(IContentProvider provider) throws Exception { + try (ParcelFileDescriptor fd = provider.openFile(null, mUri, "w", null, null)) { + Streams.copy(System.in, new FileOutputStream(fd.getFileDescriptor())); } } } diff --git a/cmds/incident_helper/src/ih_util.cpp b/cmds/incident_helper/src/ih_util.cpp index 4bf956a9a03d..e23e80ae21e8 100644 --- a/cmds/incident_helper/src/ih_util.cpp +++ b/cmds/incident_helper/src/ih_util.cpp @@ -52,6 +52,12 @@ static inline std::string trimHeader(const std::string& s) { return toLowerStr(trimDefault(s)); } +static inline bool isNumber(const std::string& s) { + std::string::const_iterator it = s.begin(); + while (it != s.end() && std::isdigit(*it)) ++it; + return !s.empty() && it == s.end(); +} + // This is similiar to Split in android-base/file.h, but it won't add empty string static void split(const std::string& line, std::vector<std::string>& words, const trans_func& func, const std::string& delimiters) { @@ -86,24 +92,80 @@ record_t parseRecord(const std::string& line, const std::string& delimiters) { return record; } +bool getColumnIndices(std::vector<int>& indices, const char** headerNames, const std::string& line) { + indices.clear(); + + size_t lastIndex = 0; + int i = 0; + while (headerNames[i] != NULL) { + string s = headerNames[i]; + lastIndex = line.find(s, lastIndex); + if (lastIndex == string::npos) { + fprintf(stderr, "Bad Task Header: %s\n", line.c_str()); + return false; + } + lastIndex += s.length(); + indices.push_back(lastIndex); + i++; + } + + return true; +} + record_t parseRecordByColumns(const std::string& line, const std::vector<int>& indices, const std::string& delimiters) { record_t record; int lastIndex = 0; + int lastBeginning = 0; int lineSize = (int)line.size(); for (std::vector<int>::const_iterator it = indices.begin(); it != indices.end(); ++it) { int idx = *it; - if (lastIndex > idx || idx > lineSize) { - record.clear(); // The indices is wrong, return empty; + if (idx <= lastIndex) { + // We saved up until lastIndex last time, so we should start at + // lastIndex + 1 this time. + idx = lastIndex + 1; + } + if (idx > lineSize) { + if (lastIndex < idx && lastIndex < lineSize) { + // There's a little bit more for us to save, which we'll do + // outside of the loop. + break; + } + // If we're past the end of the line AND we've already saved everything up to the end. + fprintf(stderr, "index wrong: lastIndex: %d, idx: %d, lineSize: %d\n", lastIndex, idx, lineSize); + record.clear(); // The indices are wrong, return empty. return record; } while (idx < lineSize && delimiters.find(line[idx++]) == std::string::npos); record.push_back(trimDefault(line.substr(lastIndex, idx - lastIndex))); + lastBeginning = lastIndex; lastIndex = idx; } - record.push_back(trimDefault(line.substr(lastIndex, lineSize - lastIndex))); + if (lineSize - lastIndex > 0) { + int beginning = lastIndex; + if (record.size() == indices.size()) { + // We've already encountered all of the columns...put whatever is + // left in the last column. + record.pop_back(); + beginning = lastBeginning; + } + record.push_back(trimDefault(line.substr(beginning, lineSize - beginning))); + } return record; } +void printRecord(const record_t& record) { + fprintf(stderr, "Record: { "); + if (record.size() == 0) { + fprintf(stderr, "}\n"); + return; + } + for(size_t i = 0; i < record.size(); ++i) { + if(i != 0) fprintf(stderr, "\", "); + fprintf(stderr, "\"%s", record[i].c_str()); + } + fprintf(stderr, "\" }\n"); +} + bool stripPrefix(std::string* line, const char* key, bool endAtDelimiter) { const auto head = line->find_first_not_of(DEFAULT_WHITESPACE); if (head == std::string::npos) return false; @@ -210,7 +272,10 @@ Table::~Table() void Table::addEnumTypeMap(const char* field, const char* enumNames[], const int enumValues[], const int enumSize) { - if (mFields.find(field) == mFields.end()) return; + if (mFields.find(field) == mFields.end()) { + fprintf(stderr, "Field '%s' not found", string(field).c_str()); + return; + } map<std::string, int> enu; for (int i = 0; i < enumSize; i++) { @@ -268,6 +333,8 @@ Table::insertField(ProtoOutputStream* proto, const std::string& name, const std: } } else if (mEnumValuesByName.find(value) != mEnumValuesByName.end()) { proto->write(found, mEnumValuesByName[value]); + } else if (isNumber(value)) { + proto->write(found, toInt(value)); } else { return false; } diff --git a/cmds/incident_helper/src/ih_util.h b/cmds/incident_helper/src/ih_util.h index 58ef29044048..b063b2fe0bba 100644 --- a/cmds/incident_helper/src/ih_util.h +++ b/cmds/incident_helper/src/ih_util.h @@ -56,12 +56,23 @@ header_t parseHeader(const std::string& line, const std::string& delimiters = DE record_t parseRecord(const std::string& line, const std::string& delimiters = DEFAULT_WHITESPACE); /** + * Gets the list of end indices of each word in the line and places it in the given vector, + * clearing out the vector beforehand. These indices can be used with parseRecordByColumns. + * Will return false if there was a problem getting the indices. headerNames + * must be NULL terminated. + */ +bool getColumnIndices(std::vector<int>& indices, const char* headerNames[], const std::string& line); + +/** * When a text-format table aligns by its vertical position, it is not possible to split them by purely delimiters. * This function allows to parse record by its header's column position' indices, must in ascending order. * At the same time, it still looks at the char at index, if it doesn't belong to delimiters, moves forward to find the delimiters. */ record_t parseRecordByColumns(const std::string& line, const std::vector<int>& indices, const std::string& delimiters = DEFAULT_WHITESPACE); +/** Prints record_t to stderr */ +void printRecord(const record_t& record); + /** * When the line starts/ends with the given key, the function returns true * as well as the line argument is changed to the rest trimmed part of the original. diff --git a/cmds/incident_helper/src/main.cpp b/cmds/incident_helper/src/main.cpp index c8a0883d493c..8c6cd78d3bf2 100644 --- a/cmds/incident_helper/src/main.cpp +++ b/cmds/incident_helper/src/main.cpp @@ -16,11 +16,13 @@ #define LOG_TAG "incident_helper" +#include "parsers/BatteryTypeParser.h" #include "parsers/CpuFreqParser.h" #include "parsers/CpuInfoParser.h" #include "parsers/KernelWakesParser.h" #include "parsers/PageTypeInfoParser.h" #include "parsers/ProcrankParser.h" +#include "parsers/PsParser.h" #include "parsers/SystemPropertiesParser.h" #include <android-base/file.h> @@ -63,6 +65,10 @@ static TextParserBase* selectParser(int section) { return new CpuInfoParser(); case 2004: return new CpuFreqParser(); + case 2005: + return new PsParser(); + case 2006: + return new BatteryTypeParser(); default: return NULL; } diff --git a/cmds/incident_helper/src/parsers/BatteryTypeParser.cpp b/cmds/incident_helper/src/parsers/BatteryTypeParser.cpp new file mode 100644 index 000000000000..ced6cf807e0d --- /dev/null +++ b/cmds/incident_helper/src/parsers/BatteryTypeParser.cpp @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2017 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. + */ +#define LOG_TAG "incident_helper" + +#include <android/util/ProtoOutputStream.h> + +#include "frameworks/base/core/proto/android/os/batterytype.proto.h" +#include "ih_util.h" +#include "BatteryTypeParser.h" + +using namespace android::os; + +status_t +BatteryTypeParser::Parse(const int in, const int out) const +{ + Reader reader(in); + string line; + bool readLine = false; + + ProtoOutputStream proto; + + // parse line by line + while (reader.readLine(&line)) { + if (line.empty()) continue; + + if (readLine) { + fprintf(stderr, "Multiple lines in file. Unsure what to do.\n"); + break; + } + + proto.write(BatteryTypeProto::TYPE, line); + + readLine = true; + } + + if (!reader.ok(&line)) { + fprintf(stderr, "Bad read from fd %d: %s\n", in, line.c_str()); + return -1; + } + + if (!proto.flush(out)) { + fprintf(stderr, "[%s]Error writing proto back\n", this->name.string()); + return -1; + } + fprintf(stderr, "[%s]Proto size: %zu bytes\n", this->name.string(), proto.size()); + return NO_ERROR; +} diff --git a/cmds/incident_helper/src/parsers/BatteryTypeParser.h b/cmds/incident_helper/src/parsers/BatteryTypeParser.h new file mode 100644 index 000000000000..ac0c098965d3 --- /dev/null +++ b/cmds/incident_helper/src/parsers/BatteryTypeParser.h @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2017 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. + */ + +#ifndef BATTERY_TYPE_PARSER_H +#define BATTERY_TYPE_PARSER_H + +#include "TextParserBase.h" + +using namespace android; + +/** + * Battery type parser, parses text in file + * /sys/class/power_supply/bms/battery_type. + */ +class BatteryTypeParser : public TextParserBase { +public: + BatteryTypeParser() : TextParserBase(String8("BatteryTypeParser")) {}; + ~BatteryTypeParser() {}; + + virtual status_t Parse(const int in, const int out) const; +}; + +#endif // BATTERY_TYPE_PARSER_H diff --git a/cmds/incident_helper/src/parsers/CpuInfoParser.cpp b/cmds/incident_helper/src/parsers/CpuInfoParser.cpp index 3faca00c1b88..d73de54d8c5d 100644 --- a/cmds/incident_helper/src/parsers/CpuInfoParser.cpp +++ b/cmds/incident_helper/src/parsers/CpuInfoParser.cpp @@ -49,6 +49,7 @@ CpuInfoParser::Parse(const int in, const int out) const vector<int> columnIndices; // task table can't be split by purely delimiter, needs column positions. record_t record; int nline = 0; + int diff = 0; bool nextToSwap = false; bool nextToUsage = false; @@ -107,18 +108,10 @@ CpuInfoParser::Parse(const int in, const int out) const header = parseHeader(line, "[ %]"); nextToUsage = false; - // NAME is not in the list since the last split index is default to the end of line. - const char* headerNames[11] = { "PID", "TID", "USER", "PR", "NI", "CPU", "S", "VIRT", "RES", "PCY", "CMD" }; - size_t lastIndex = 0; - for (int i = 0; i < 11; i++) { - string s = headerNames[i]; - lastIndex = line.find(s, lastIndex); - if (lastIndex == string::npos) { - fprintf(stderr, "Bad Task Header: %s\n", line.c_str()); - return -1; - } - lastIndex += s.length(); - columnIndices.push_back(lastIndex); + // NAME is not in the list since we need to modify the end of the CMD index. + const char* headerNames[] = { "PID", "TID", "USER", "PR", "NI", "CPU", "S", "VIRT", "RES", "PCY", "CMD", NULL }; + if (!getColumnIndices(columnIndices, headerNames, line)) { + return -1; } // Need to remove the end index of CMD and use the start index of NAME because CMD values contain spaces. // for example: ... CMD NAME @@ -128,12 +121,20 @@ CpuInfoParser::Parse(const int in, const int out) const int endCMD = columnIndices.back(); columnIndices.pop_back(); columnIndices.push_back(line.find("NAME", endCMD) - 1); + // Add NAME index to complete the column list. + columnIndices.push_back(columnIndices.back() + 4); continue; } record = parseRecordByColumns(line, columnIndices); - if (record.size() != header.size()) { - fprintf(stderr, "[%s]Line %d has missing fields:\n%s\n", this->name.string(), nline, line.c_str()); + diff = record.size() - header.size(); + if (diff < 0) { + fprintf(stderr, "[%s]Line %d has %d missing fields\n%s\n", this->name.string(), nline, -diff, line.c_str()); + printRecord(record); + continue; + } else if (diff > 0) { + fprintf(stderr, "[%s]Line %d has %d extra fields\n%s\n", this->name.string(), nline, diff, line.c_str()); + printRecord(record); continue; } diff --git a/cmds/incident_helper/src/parsers/KernelWakesParser.cpp b/cmds/incident_helper/src/parsers/KernelWakesParser.cpp index ada4a5d0ffe2..cae51abbe57f 100644 --- a/cmds/incident_helper/src/parsers/KernelWakesParser.cpp +++ b/cmds/incident_helper/src/parsers/KernelWakesParser.cpp @@ -47,10 +47,14 @@ KernelWakesParser::Parse(const int in, const int out) const // parse for each record, the line delimiter is \t only! record = parseRecord(line, TAB_DELIMITER); - if (record.size() != header.size()) { + if (record.size() < header.size()) { // TODO: log this to incident report! fprintf(stderr, "[%s]Line %d has missing fields\n%s\n", this->name.string(), nline, line.c_str()); continue; + } else if (record.size() > header.size()) { + // TODO: log this to incident report! + fprintf(stderr, "[%s]Line %d has extra fields\n%s\n", this->name.string(), nline, line.c_str()); + continue; } long long token = proto.start(KernelWakeSources::WAKEUP_SOURCES); diff --git a/cmds/incident_helper/src/parsers/PsParser.cpp b/cmds/incident_helper/src/parsers/PsParser.cpp new file mode 100644 index 000000000000..e9014cacfa0b --- /dev/null +++ b/cmds/incident_helper/src/parsers/PsParser.cpp @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2017 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. + */ +#define LOG_TAG "incident_helper" + +#include <android/util/ProtoOutputStream.h> + +#include "frameworks/base/core/proto/android/os/ps.proto.h" +#include "ih_util.h" +#include "PsParser.h" + +using namespace android::os; + +status_t PsParser::Parse(const int in, const int out) const { + Reader reader(in); + string line; + header_t header; // the header of /d/wakeup_sources + vector<int> columnIndices; // task table can't be split by purely delimiter, needs column positions. + record_t record; // retain each record + int nline = 0; + int diff = 0; + + ProtoOutputStream proto; + Table table(PsDumpProto::Process::_FIELD_NAMES, PsDumpProto::Process::_FIELD_IDS, PsDumpProto::Process::_FIELD_COUNT); + const char* pcyNames[] = { "fg", "bg", "ta" }; + const int pcyValues[] = {PsDumpProto::Process::POLICY_FG, PsDumpProto::Process::POLICY_BG, PsDumpProto::Process::POLICY_TA}; + table.addEnumTypeMap("pcy", pcyNames, pcyValues, 3); + const char* sNames[] = { "D", "R", "S", "T", "t", "X", "Z" }; + const int sValues[] = {PsDumpProto::Process::STATE_D, PsDumpProto::Process::STATE_R, PsDumpProto::Process::STATE_S, PsDumpProto::Process::STATE_T, PsDumpProto::Process::STATE_TRACING, PsDumpProto::Process::STATE_X, PsDumpProto::Process::STATE_Z}; + table.addEnumTypeMap("s", sNames, sValues, 7); + + // Parse line by line + while (reader.readLine(&line)) { + if (line.empty()) continue; + + if (nline++ == 0) { + header = parseHeader(line, DEFAULT_WHITESPACE); + + const char* headerNames[] = { "LABEL", "USER", "PID", "TID", "PPID", "VSZ", "RSS", "WCHAN", "ADDR", "S", "PRI", "NI", "RTPRIO", "SCH", "PCY", "TIME", "CMD", NULL }; + if (!getColumnIndices(columnIndices, headerNames, line)) { + return -1; + } + + continue; + } + + record = parseRecordByColumns(line, columnIndices); + + diff = record.size() - header.size(); + if (diff < 0) { + // TODO: log this to incident report! + fprintf(stderr, "[%s]Line %d has %d missing fields\n%s\n", this->name.string(), nline, -diff, line.c_str()); + printRecord(record); + continue; + } else if (diff > 0) { + // TODO: log this to incident report! + fprintf(stderr, "[%s]Line %d has %d extra fields\n%s\n", this->name.string(), nline, diff, line.c_str()); + printRecord(record); + continue; + } + + long long token = proto.start(PsDumpProto::PROCESSES); + for (int i=0; i<(int)record.size(); i++) { + if (!table.insertField(&proto, header[i], record[i])) { + fprintf(stderr, "[%s]Line %d has bad value %s of %s\n", + this->name.string(), nline, header[i].c_str(), record[i].c_str()); + } + } + proto.end(token); + } + + if (!reader.ok(&line)) { + fprintf(stderr, "Bad read from fd %d: %s\n", in, line.c_str()); + return -1; + } + + if (!proto.flush(out)) { + fprintf(stderr, "[%s]Error writing proto back\n", this->name.string()); + return -1; + } + fprintf(stderr, "[%s]Proto size: %zu bytes\n", this->name.string(), proto.size()); + return NO_ERROR; +} diff --git a/cmds/incident_helper/src/parsers/PsParser.h b/cmds/incident_helper/src/parsers/PsParser.h new file mode 100644 index 000000000000..9488e40e88fe --- /dev/null +++ b/cmds/incident_helper/src/parsers/PsParser.h @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2017 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. + */ + +#ifndef PS_PARSER_H +#define PS_PARSER_H + +#include "TextParserBase.h" + +/** + * PS parser, parses output of 'ps' command to protobuf. + */ +class PsParser : public TextParserBase { +public: + PsParser() : TextParserBase(String8("Ps")) {}; + ~PsParser() {}; + + virtual status_t Parse(const int in, const int out) const; +}; + +#endif // PS_PARSER_H diff --git a/cmds/incident_helper/testdata/batterytype.txt b/cmds/incident_helper/testdata/batterytype.txt new file mode 100644 index 000000000000..c763d36ad811 --- /dev/null +++ b/cmds/incident_helper/testdata/batterytype.txt @@ -0,0 +1 @@ +random_battery_type_string diff --git a/cmds/incident_helper/testdata/ps.txt b/cmds/incident_helper/testdata/ps.txt new file mode 100644 index 000000000000..72dafc2c4378 --- /dev/null +++ b/cmds/incident_helper/testdata/ps.txt @@ -0,0 +1,9 @@ +LABEL USER PID TID PPID VSZ RSS WCHAN ADDR S PRI NI RTPRIO SCH PCY TIME CMD +u:r:init:s0 root 1 1 0 15816 2636 SyS_epoll_wait 0 S 19 0 - 0 fg 00:00:01 init +u:r:kernel:s0 root 2 2 0 0 0 kthreadd 0 S 19 0 - 0 fg 00:00:00 kthreadd +u:r:surfaceflinger:s0 system 499 534 1 73940 22024 futex_wait_queue_me 0 S 42 -9 2 1 fg 00:00:00 EventThread +u:r:hal_gnss_default:s0 gps 670 2004 1 43064 7272 poll_schedule_timeout 0 S 19 0 - 0 fg 00:00:00 Loc_hal_worker +u:r:platform_app:s0:c512,c768 u0_a48 1660 1976 806 4468612 138328 binder_thread_read 0 S 35 -16 - 0 ta 00:00:00 HwBinder:1660_1 +u:r:perfd:s0 root 1939 1946 1 18132 2088 __skb_recv_datagram 7b9782fd14 S 19 0 - 0 00:00:00 perfd +u:r:perfd:s0 root 1939 1955 1 18132 2088 do_sigtimedwait 7b9782ff6c S 19 0 - 0 00:00:00 POSIX timer 0 +u:r:shell:s0 shell 2645 2645 802 11664 2972 0 7f67a2f8b4 R 19 0 - 0 fg 00:00:00 ps diff --git a/cmds/incident_helper/tests/BatteryTypeParser_test.cpp b/cmds/incident_helper/tests/BatteryTypeParser_test.cpp new file mode 100644 index 000000000000..7fbe22df4b0a --- /dev/null +++ b/cmds/incident_helper/tests/BatteryTypeParser_test.cpp @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2017 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. + */ + +#include "BatteryTypeParser.h" + +#include "frameworks/base/core/proto/android/os/batterytype.pb.h" + +#include <android-base/file.h> +#include <android-base/test_utils.h> +#include <gmock/gmock.h> +#include <google/protobuf/message_lite.h> +#include <gtest/gtest.h> +#include <string.h> +#include <fcntl.h> + +using namespace android::base; +using namespace android::os; +using namespace std; +using ::testing::StrEq; +using ::testing::Test; +using ::testing::internal::CaptureStderr; +using ::testing::internal::CaptureStdout; +using ::testing::internal::GetCapturedStderr; +using ::testing::internal::GetCapturedStdout; + +class BatteryTypeParserTest : public Test { +public: + virtual void SetUp() override { + ASSERT_TRUE(tf.fd != -1); + } + +protected: + TemporaryFile tf; + + const string kTestPath = GetExecutableDirectory(); + const string kTestDataPath = kTestPath + "/testdata/"; +}; + +TEST_F(BatteryTypeParserTest, Success) { + const string testFile = kTestDataPath + "batterytype.txt"; + BatteryTypeParser parser; + BatteryTypeProto expected; + + expected.set_type("random_battery_type_string"); + + int fd = open(testFile.c_str(), O_RDONLY); + ASSERT_TRUE(fd != -1); + + CaptureStdout(); + ASSERT_EQ(NO_ERROR, parser.Parse(fd, STDOUT_FILENO)); + EXPECT_EQ(GetCapturedStdout(), expected.SerializeAsString()); + close(fd); +} diff --git a/cmds/incident_helper/tests/PsParser_test.cpp b/cmds/incident_helper/tests/PsParser_test.cpp new file mode 100644 index 000000000000..1f03a7f3a332 --- /dev/null +++ b/cmds/incident_helper/tests/PsParser_test.cpp @@ -0,0 +1,300 @@ +/* + * Copyright (C) 2017 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. + */ + +#include "PsParser.h" + +#include "frameworks/base/core/proto/android/os/ps.pb.h" + +#include <android-base/file.h> +#include <android-base/test_utils.h> +#include <gmock/gmock.h> +#include <google/protobuf/message_lite.h> +#include <gtest/gtest.h> +#include <string.h> +#include <fcntl.h> + +using namespace android::base; +using namespace android::os; +using namespace std; +using ::testing::StrEq; +using ::testing::Test; +using ::testing::internal::CaptureStderr; +using ::testing::internal::CaptureStdout; +using ::testing::internal::GetCapturedStderr; +using ::testing::internal::GetCapturedStdout; + +class PsParserTest : public Test { +public: + virtual void SetUp() override { + ASSERT_TRUE(tf.fd != -1); + } + +protected: + TemporaryFile tf; + + const string kTestPath = GetExecutableDirectory(); + const string kTestDataPath = kTestPath + "/testdata/"; +}; + +TEST_F(PsParserTest, Normal) { + const string testFile = kTestDataPath + "ps.txt"; + PsParser parser; + PsDumpProto expected; + PsDumpProto got; + + PsDumpProto::Process* record1 = expected.add_processes(); + record1->set_label("u:r:init:s0"); + record1->set_user("root"); + record1->set_pid(1); + record1->set_tid(1); + record1->set_ppid(0); + record1->set_vsz(15816); + record1->set_rss(2636); + record1->set_wchan("SyS_epoll_wait"); + record1->set_addr("0"); + record1->set_s(PsDumpProto_Process_ProcessStateCode_STATE_S); + record1->set_pri(19); + record1->set_ni(0); + record1->set_rtprio("-"); + record1->set_sch(PsDumpProto_Process_SchedulingPolicy_SCH_NORMAL); + record1->set_pcy(PsDumpProto::Process::POLICY_FG); + record1->set_time("00:00:01"); + record1->set_cmd("init"); + + PsDumpProto::Process* record2 = expected.add_processes(); + record2->set_label("u:r:kernel:s0"); + record2->set_user("root"); + record2->set_pid(2); + record2->set_tid(2); + record2->set_ppid(0); + record2->set_vsz(0); + record2->set_rss(0); + record2->set_wchan("kthreadd"); + record2->set_addr("0"); + record2->set_s(PsDumpProto_Process_ProcessStateCode_STATE_S); + record2->set_pri(19); + record2->set_ni(0); + record2->set_rtprio("-"); + record2->set_sch(PsDumpProto_Process_SchedulingPolicy_SCH_NORMAL); + record2->set_pcy(PsDumpProto::Process::POLICY_FG); + record2->set_time("00:00:00"); + record2->set_cmd("kthreadd"); + + PsDumpProto::Process* record3 = expected.add_processes(); + record3->set_label("u:r:surfaceflinger:s0"); + record3->set_user("system"); + record3->set_pid(499); + record3->set_tid(534); + record3->set_ppid(1); + record3->set_vsz(73940); + record3->set_rss(22024); + record3->set_wchan("futex_wait_queue_me"); + record3->set_addr("0"); + record3->set_s(PsDumpProto_Process_ProcessStateCode_STATE_S); + record3->set_pri(42); + record3->set_ni(-9); + record3->set_rtprio("2"); + record3->set_sch(PsDumpProto_Process_SchedulingPolicy_SCH_FIFO); + record3->set_pcy(PsDumpProto::Process::POLICY_FG); + record3->set_time("00:00:00"); + record3->set_cmd("EventThread"); + + PsDumpProto::Process* record4 = expected.add_processes(); + record4->set_label("u:r:hal_gnss_default:s0"); + record4->set_user("gps"); + record4->set_pid(670); + record4->set_tid(2004); + record4->set_ppid(1); + record4->set_vsz(43064); + record4->set_rss(7272); + record4->set_wchan("poll_schedule_timeout"); + record4->set_addr("0"); + record4->set_s(PsDumpProto_Process_ProcessStateCode_STATE_S); + record4->set_pri(19); + record4->set_ni(0); + record4->set_rtprio("-"); + record4->set_sch(PsDumpProto_Process_SchedulingPolicy_SCH_NORMAL); + record4->set_pcy(PsDumpProto::Process::POLICY_FG); + record4->set_time("00:00:00"); + record4->set_cmd("Loc_hal_worker"); + + PsDumpProto::Process* record5 = expected.add_processes(); + record5->set_label("u:r:platform_app:s0:c512,c768"); + record5->set_user("u0_a48"); + record5->set_pid(1660); + record5->set_tid(1976); + record5->set_ppid(806); + record5->set_vsz(4468612); + record5->set_rss(138328); + record5->set_wchan("binder_thread_read"); + record5->set_addr("0"); + record5->set_s(PsDumpProto_Process_ProcessStateCode_STATE_S); + record5->set_pri(35); + record5->set_ni(-16); + record5->set_rtprio("-"); + record5->set_sch(PsDumpProto_Process_SchedulingPolicy_SCH_NORMAL); + record5->set_pcy(PsDumpProto::Process::POLICY_TA); + record5->set_time("00:00:00"); + record5->set_cmd("HwBinder:1660_1"); + + PsDumpProto::Process* record6 = expected.add_processes(); + record6->set_label("u:r:perfd:s0"); + record6->set_user("root"); + record6->set_pid(1939); + record6->set_tid(1946); + record6->set_ppid(1); + record6->set_vsz(18132); + record6->set_rss(2088); + record6->set_wchan("__skb_recv_datagram"); + record6->set_addr("7b9782fd14"); + record6->set_s(PsDumpProto_Process_ProcessStateCode_STATE_S); + record6->set_pri(19); + record6->set_ni(0); + record6->set_rtprio("-"); + record6->set_sch(PsDumpProto_Process_SchedulingPolicy_SCH_NORMAL); + record6->set_pcy(PsDumpProto::Process::POLICY_UNKNOWN); + record6->set_time("00:00:00"); + record6->set_cmd("perfd"); + + PsDumpProto::Process* record7 = expected.add_processes(); + record7->set_label("u:r:perfd:s0"); + record7->set_user("root"); + record7->set_pid(1939); + record7->set_tid(1955); + record7->set_ppid(1); + record7->set_vsz(18132); + record7->set_rss(2088); + record7->set_wchan("do_sigtimedwait"); + record7->set_addr("7b9782ff6c"); + record7->set_s(PsDumpProto_Process_ProcessStateCode_STATE_S); + record7->set_pri(19); + record7->set_ni(0); + record7->set_rtprio("-"); + record7->set_sch(PsDumpProto_Process_SchedulingPolicy_SCH_NORMAL); + record7->set_pcy(PsDumpProto::Process::POLICY_UNKNOWN); + record7->set_time("00:00:00"); + record7->set_cmd("POSIX timer 0"); + + PsDumpProto::Process* record8 = expected.add_processes(); + record8->set_label("u:r:shell:s0"); + record8->set_user("shell"); + record8->set_pid(2645); + record8->set_tid(2645); + record8->set_ppid(802); + record8->set_vsz(11664); + record8->set_rss(2972); + record8->set_wchan("0"); + record8->set_addr("7f67a2f8b4"); + record8->set_s(PsDumpProto_Process_ProcessStateCode_STATE_R); + record8->set_pri(19); + record8->set_ni(0); + record8->set_rtprio("-"); + record8->set_sch(PsDumpProto_Process_SchedulingPolicy_SCH_NORMAL); + record8->set_pcy(PsDumpProto::Process::POLICY_FG); + record8->set_time("00:00:00"); + record8->set_cmd("ps"); + + int fd = open(testFile.c_str(), O_RDONLY); + ASSERT_TRUE(fd != -1); + + CaptureStdout(); + ASSERT_EQ(NO_ERROR, parser.Parse(fd, STDOUT_FILENO)); + got.ParseFromString(GetCapturedStdout()); + bool matches = true; + + if (got.processes_size() != expected.processes_size()) { + fprintf(stderr, "Got %d processes, want %d\n", got.processes_size(), expected.processes_size()); + matches = false; + } else { + int n = got.processes_size(); + for (int i = 0; i < n; i++) { + PsDumpProto::Process g = got.processes(i); + PsDumpProto::Process e = expected.processes(i); + + if (g.label() != e.label()) { + fprintf(stderr, "prcs[%d]: Invalid label. Got %s, want %s\n", i, g.label().c_str(), e.label().c_str()); + matches = false; + } + if (g.user() != e.user()) { + fprintf(stderr, "prcs[%d]: Invalid user. Got %s, want %s\n", i, g.user().c_str(), e.user().c_str()); + matches = false; + } + if (g.pid() != e.pid()) { + fprintf(stderr, "prcs[%d]: Invalid pid. Got %d, want %d\n", i, g.pid(), e.pid()); + matches = false; + } + if (g.tid() != e.tid()) { + fprintf(stderr, "prcs[%d]: Invalid tid. Got %d, want %d\n", i, g.tid(), e.tid()); + matches = false; + } + if (g.ppid() != e.ppid()) { + fprintf(stderr, "prcs[%d]: Invalid ppid. Got %d, want %d\n", i, g.ppid(), e.ppid()); + matches = false; + } + if (g.vsz() != e.vsz()) { + fprintf(stderr, "prcs[%d]: Invalid vsz. Got %d, want %d\n", i, g.vsz(), e.vsz()); + matches = false; + } + if (g.rss() != e.rss()) { + fprintf(stderr, "prcs[%d]: Invalid rss. Got %d, want %d\n", i, g.rss(), e.rss()); + matches = false; + } + if (g.wchan() != e.wchan()) { + fprintf(stderr, "prcs[%d]: Invalid wchan. Got %s, want %s\n", i, g.wchan().c_str(), e.wchan().c_str()); + matches = false; + } + if (g.addr() != e.addr()) { + fprintf(stderr, "prcs[%d]: Invalid addr. Got %s, want %s\n", i, g.addr().c_str(), e.addr().c_str()); + matches = false; + } + if (g.s() != e.s()) { + fprintf(stderr, "prcs[%d]: Invalid s. Got %u, want %u\n", i, g.s(), e.s()); + matches = false; + } + if (g.pri() != e.pri()) { + fprintf(stderr, "prcs[%d]: Invalid pri. Got %d, want %d\n", i, g.pri(), e.pri()); + matches = false; + } + if (g.ni() != e.ni()) { + fprintf(stderr, "prcs[%d]: Invalid ni. Got %d, want %d\n", i, g.ni(), e.ni()); + matches = false; + } + if (g.rtprio() != e.rtprio()) { + fprintf(stderr, "prcs[%d]: Invalid rtprio. Got %s, want %s\n", i, g.rtprio().c_str(), e.rtprio().c_str()); + matches = false; + } + if (g.sch() != e.sch()) { + fprintf(stderr, "prcs[%d]: Invalid sch. Got %u, want %u\n", i, g.sch(), e.sch()); + matches = false; + } + if (g.pcy() != e.pcy()) { + fprintf(stderr, "prcs[%d]: Invalid pcy. Got %u, want %u\n", i, g.pcy(), e.pcy()); + matches = false; + } + if (g.time() != e.time()) { + fprintf(stderr, "prcs[%d]: Invalid time. Got %s, want %s\n", i, g.time().c_str(), e.time().c_str()); + matches = false; + } + if (g.cmd() != e.cmd()) { + fprintf(stderr, "prcs[%d]: Invalid cmd. Got %s, want %s\n", i, g.cmd().c_str(), e.cmd().c_str()); + matches = false; + } + } + } + + EXPECT_TRUE(matches); + close(fd); +} diff --git a/cmds/incident_helper/tests/ih_util_test.cpp b/cmds/incident_helper/tests/ih_util_test.cpp index 5740b330d949..7b8cf52c8bee 100644 --- a/cmds/incident_helper/tests/ih_util_test.cpp +++ b/cmds/incident_helper/tests/ih_util_test.cpp @@ -71,11 +71,29 @@ TEST(IhUtilTest, ParseRecordByColumns) { EXPECT_EQ(expected, result); result = parseRecordByColumns("abc \t2345 6789 ", indices); - expected = { "abc", "2345", "6789" }; + expected = { "abc", "2345 6789" }; EXPECT_EQ(expected, result); - result = parseRecordByColumns("abc \t23456789 bob", indices); - expected = { "abc", "23456789", "bob" }; + std::string extraColumn1 = "abc \t23456789 bob"; + std::string emptyMidColm = "abc \t bob"; + std::string longFirstClm = "abcdefgt\t6789 bob"; + std::string lngFrstEmpty = "abcdefgt\t bob"; + + result = parseRecordByColumns(extraColumn1, indices); + expected = { "abc", "23456789 bob" }; + EXPECT_EQ(expected, result); + + // 2nd column should be treated as an empty entry. + result = parseRecordByColumns(emptyMidColm, indices); + expected = { "abc", "bob" }; + EXPECT_EQ(expected, result); + + result = parseRecordByColumns(longFirstClm, indices); + expected = { "abcdefgt", "6789 bob" }; + EXPECT_EQ(expected, result); + + result = parseRecordByColumns(lngFrstEmpty, indices); + expected = { "abcdefgt", "bob" }; EXPECT_EQ(expected, result); } diff --git a/cmds/incidentd/Android.mk b/cmds/incidentd/Android.mk index fb8ef6338d90..11d3e4911761 100644 --- a/cmds/incidentd/Android.mk +++ b/cmds/incidentd/Android.mk @@ -72,9 +72,7 @@ LOCAL_GENERATED_SOURCES += $(GEN) gen_src_dir:= GEN:= -ifeq ($(BUILD_WITH_INCIDENTD_RC), true) LOCAL_INIT_RC := incidentd.rc -endif include $(BUILD_EXECUTABLE) diff --git a/cmds/incidentd/src/Section.cpp b/cmds/incidentd/src/Section.cpp index 1bf795bb6557..22053ef3c53a 100644 --- a/cmds/incidentd/src/Section.cpp +++ b/cmds/incidentd/src/Section.cpp @@ -521,7 +521,7 @@ CommandSection::Execute(ReportRequestSet* requests) const ALOGW("CommandSection '%s' failed to set up stdout: %s", this->name.string(), strerror(errno)); _exit(EXIT_FAILURE); } - execv(this->mCommand[0], (char *const *) this->mCommand); + execvp(this->mCommand[0], (char *const *) this->mCommand); int err = errno; // record command error code ALOGW("CommandSection '%s' failed in executing command: %s", this->name.string(), strerror(errno)); _exit(err); // exit with command error code diff --git a/cmds/statsd/Android.mk b/cmds/statsd/Android.mk index f98ee3de2c95..a365f54bfdf0 100644 --- a/cmds/statsd/Android.mk +++ b/cmds/statsd/Android.mk @@ -20,8 +20,12 @@ statsd_common_src := \ src/stats_log.proto \ src/statsd_config.proto \ src/atoms.proto \ + src/field_util.cpp \ + src/stats_log_util.cpp \ + src/dimension.cpp \ src/anomaly/AnomalyMonitor.cpp \ src/anomaly/AnomalyTracker.cpp \ + src/anomaly/DurationAnomalyTracker.cpp \ src/condition/CombinationConditionTracker.cpp \ src/condition/condition_util.cpp \ src/condition/SimpleConditionTracker.cpp \ @@ -162,6 +166,7 @@ LOCAL_SRC_FILES := \ tests/indexed_priority_queue_test.cpp \ tests/LogEntryMatcher_test.cpp \ tests/LogReader_test.cpp \ + tests/LogEvent_test.cpp \ tests/MetricsManager_test.cpp \ tests/StatsLogProcessor_test.cpp \ tests/UidMap_test.cpp \ @@ -175,7 +180,10 @@ LOCAL_SRC_FILES := \ tests/metrics/ValueMetricProducer_test.cpp \ tests/metrics/GaugeMetricProducer_test.cpp \ tests/guardrail/StatsdStats_test.cpp \ - tests/metrics/metrics_test_helper.cpp + tests/metrics/metrics_test_helper.cpp \ + tests/statsd_test_util.cpp \ + tests/e2e/WakelockDuration_e2e_test.cpp \ + tests/e2e/MetricConditionLink_e2e_test.cpp LOCAL_STATIC_LIBRARIES := \ $(statsd_common_static_libraries) \ @@ -197,4 +205,4 @@ statsd_common_shared_libraries:= ############################## -include $(call all-makefiles-under,$(LOCAL_PATH)) +include $(call all-makefiles-under,$(LOCAL_PATH))
\ No newline at end of file diff --git a/cmds/statsd/src/HashableDimensionKey.cpp b/cmds/statsd/src/HashableDimensionKey.cpp index 0b6f8f2b335d..288ebe9dbbe9 100644 --- a/cmds/statsd/src/HashableDimensionKey.cpp +++ b/cmds/statsd/src/HashableDimensionKey.cpp @@ -13,92 +13,108 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + #include "HashableDimensionKey.h" +#include "dimension.h" namespace android { namespace os { namespace statsd { +android::hash_t hashDimensionsValue(int64_t seed, const DimensionsValue& value) { + android::hash_t hash = seed; + hash = android::JenkinsHashMix(hash, android::hash_type(value.field())); + + hash = android::JenkinsHashMix(hash, android::hash_type((int)value.value_case())); + switch (value.value_case()) { + case DimensionsValue::ValueCase::kValueStr: + hash = android::JenkinsHashMix( + hash, + static_cast<uint32_t>(std::hash<std::string>()(value.value_str()))); + break; + case DimensionsValue::ValueCase::kValueInt: + hash = android::JenkinsHashMix(hash, android::hash_type(value.value_int())); + break; + case DimensionsValue::ValueCase::kValueLong: + hash = android::JenkinsHashMix( + hash, android::hash_type(static_cast<int64_t>(value.value_long()))); + break; + case DimensionsValue::ValueCase::kValueBool: + hash = android::JenkinsHashMix(hash, android::hash_type(value.value_bool())); + break; + case DimensionsValue::ValueCase::kValueFloat: { + float floatVal = value.value_float(); + hash = android::JenkinsHashMixBytes(hash, (uint8_t*)&floatVal, sizeof(float)); + break; + } + case DimensionsValue::ValueCase::kValueTuple: { + hash = android::JenkinsHashMix(hash, android::hash_type( + value.value_tuple().dimensions_value_size())); + for (int i = 0; i < value.value_tuple().dimensions_value_size(); ++i) { + hash = android::JenkinsHashMix( + hash, + hashDimensionsValue(value.value_tuple().dimensions_value(i))); + } + break; + } + case DimensionsValue::ValueCase::VALUE_NOT_SET: + break; + } + return JenkinsHashWhiten(hash); +} + +android::hash_t hashDimensionsValue(const DimensionsValue& value) { + return hashDimensionsValue(0, value); +} + using std::string; + string HashableDimensionKey::toString() const { string flattened; - for (const auto& pair : mKeyValuePairs) { - flattened += std::to_string(pair.key()); - flattened += ":"; - switch (pair.value_case()) { - case KeyValuePair::ValueCase::kValueStr: - flattened += pair.value_str(); - break; - case KeyValuePair::ValueCase::kValueInt: - flattened += std::to_string(pair.value_int()); - break; - case KeyValuePair::ValueCase::kValueLong: - flattened += std::to_string(pair.value_long()); - break; - case KeyValuePair::ValueCase::kValueBool: - flattened += std::to_string(pair.value_bool()); - break; - case KeyValuePair::ValueCase::kValueFloat: - flattened += std::to_string(pair.value_float()); - break; - default: - break; - } - flattened += "|"; - } + DimensionsValueToString(getDimensionsValue(), &flattened); return flattened; } -bool HashableDimensionKey::operator==(const HashableDimensionKey& that) const { - const auto& keyValue2 = that.getKeyValuePairs(); - if (mKeyValuePairs.size() != keyValue2.size()) { +bool compareDimensionsValue(const DimensionsValue& s1, const DimensionsValue& s2) { + if (s1.field() != s2.field()) { return false; } - - for (size_t i = 0; i < keyValue2.size(); i++) { - const auto& kv1 = mKeyValuePairs[i]; - const auto& kv2 = keyValue2[i]; - if (kv1.key() != kv2.key()) { - return false; - } - - if (kv1.value_case() != kv2.value_case()) { - return false; - } - - switch (kv1.value_case()) { - case KeyValuePair::ValueCase::kValueStr: - if (kv1.value_str() != kv2.value_str()) { - return false; - } - break; - case KeyValuePair::ValueCase::kValueInt: - if (kv1.value_int() != kv2.value_int()) { - return false; - } - break; - case KeyValuePair::ValueCase::kValueLong: - if (kv1.value_long() != kv2.value_long()) { - return false; - } - break; - case KeyValuePair::ValueCase::kValueBool: - if (kv1.value_bool() != kv2.value_bool()) { + if (s1.value_case() != s1.value_case()) { + return false; + } + switch (s1.value_case()) { + case DimensionsValue::ValueCase::kValueStr: + return (s1.value_str() == s2.value_str()); + case DimensionsValue::ValueCase::kValueInt: + return s1.value_int() == s2.value_int(); + case DimensionsValue::ValueCase::kValueLong: + return s1.value_long() == s2.value_long(); + case DimensionsValue::ValueCase::kValueBool: + return s1.value_bool() == s2.value_bool(); + case DimensionsValue::ValueCase::kValueFloat: + return s1.value_float() == s2.value_float(); + case DimensionsValue::ValueCase::kValueTuple: + { + if (s1.value_tuple().dimensions_value_size() != + s2.value_tuple().dimensions_value_size()) { return false; } - break; - case KeyValuePair::ValueCase::kValueFloat: { - if (kv1.value_float() != kv2.value_float()) { - return false; + bool allMatched = true; + for (int i = 0; allMatched && i < s1.value_tuple().dimensions_value_size(); ++i) { + allMatched &= compareDimensionsValue(s1.value_tuple().dimensions_value(i), + s2.value_tuple().dimensions_value(i)); } - break; + return allMatched; } - case KeyValuePair::ValueCase::VALUE_NOT_SET: - break; - } + case DimensionsValue::ValueCase::VALUE_NOT_SET: + default: + return true; } - return true; +} + +bool HashableDimensionKey::operator==(const HashableDimensionKey& that) const { + return compareDimensionsValue(getDimensionsValue(), that.getDimensionsValue()); }; bool HashableDimensionKey::operator<(const HashableDimensionKey& that) const { diff --git a/cmds/statsd/src/HashableDimensionKey.h b/cmds/statsd/src/HashableDimensionKey.h index 85215552b777..85c317f8cf1f 100644 --- a/cmds/statsd/src/HashableDimensionKey.h +++ b/cmds/statsd/src/HashableDimensionKey.h @@ -25,20 +25,20 @@ namespace statsd { class HashableDimensionKey { public: - explicit HashableDimensionKey(const std::vector<KeyValuePair>& keyValuePairs) - : mKeyValuePairs(keyValuePairs){}; + explicit HashableDimensionKey(const DimensionsValue& dimensionsValue) + : mDimensionsValue(dimensionsValue){}; HashableDimensionKey(){}; HashableDimensionKey(const HashableDimensionKey& that) - : mKeyValuePairs(that.getKeyValuePairs()){}; + : mDimensionsValue(that.getDimensionsValue()){}; HashableDimensionKey& operator=(const HashableDimensionKey& from) = default; std::string toString() const; - inline const std::vector<KeyValuePair>& getKeyValuePairs() const { - return mKeyValuePairs; + inline const DimensionsValue& getDimensionsValue() const { + return mDimensionsValue; } bool operator==(const HashableDimensionKey& that) const; @@ -50,9 +50,12 @@ public: } private: - std::vector<KeyValuePair> mKeyValuePairs; + DimensionsValue mDimensionsValue; }; +android::hash_t hashDimensionsValue(int64_t seed, const DimensionsValue& value); +android::hash_t hashDimensionsValue(const DimensionsValue& value); + } // namespace statsd } // namespace os } // namespace android @@ -60,42 +63,11 @@ private: namespace std { using android::os::statsd::HashableDimensionKey; -using android::os::statsd::KeyValuePair; template <> struct hash<HashableDimensionKey> { std::size_t operator()(const HashableDimensionKey& key) const { - android::hash_t hash = 0; - for (const auto& pair : key.getKeyValuePairs()) { - hash = android::JenkinsHashMix(hash, android::hash_type(pair.key())); - hash = android::JenkinsHashMix( - hash, android::hash_type(static_cast<int32_t>(pair.value_case()))); - switch (pair.value_case()) { - case KeyValuePair::ValueCase::kValueStr: - hash = android::JenkinsHashMix( - hash, - static_cast<uint32_t>(std::hash<std::string>()(pair.value_str()))); - break; - case KeyValuePair::ValueCase::kValueInt: - hash = android::JenkinsHashMix(hash, android::hash_type(pair.value_int())); - break; - case KeyValuePair::ValueCase::kValueLong: - hash = android::JenkinsHashMix( - hash, android::hash_type(static_cast<int64_t>(pair.value_long()))); - break; - case KeyValuePair::ValueCase::kValueBool: - hash = android::JenkinsHashMix(hash, android::hash_type(pair.value_bool())); - break; - case KeyValuePair::ValueCase::kValueFloat: { - float floatVal = pair.value_float(); - hash = android::JenkinsHashMixBytes(hash, (uint8_t*)&floatVal, sizeof(float)); - break; - } - case KeyValuePair::ValueCase::VALUE_NOT_SET: - break; - } - } - return hash; + return hashDimensionsValue(key.getDimensionsValue()); } }; diff --git a/cmds/statsd/src/StatsLogProcessor.cpp b/cmds/statsd/src/StatsLogProcessor.cpp index 0c078d5db83c..9678014b14dc 100644 --- a/cmds/statsd/src/StatsLogProcessor.cpp +++ b/cmds/statsd/src/StatsLogProcessor.cpp @@ -54,7 +54,7 @@ const int FIELD_ID_CONFIG_KEY = 1; const int FIELD_ID_REPORTS = 2; // for ConfigKey const int FIELD_ID_UID = 1; -const int FIELD_ID_NAME = 2; +const int FIELD_ID_ID = 2; // for ConfigMetricsReport const int FIELD_ID_METRICS = 1; const int FIELD_ID_UID_MAP = 2; @@ -63,11 +63,12 @@ const int FIELD_ID_UID_MAP = 2; StatsLogProcessor::StatsLogProcessor(const sp<UidMap>& uidMap, const sp<AnomalyMonitor>& anomalyMonitor, + const long timeBaseSec, const std::function<void(const ConfigKey&)>& sendBroadcast) : mUidMap(uidMap), mAnomalyMonitor(anomalyMonitor), mSendBroadcast(sendBroadcast), - mTimeBaseSec(time(nullptr)) { + mTimeBaseSec(timeBaseSec) { // On each initialization of StatsLogProcessor, check stats-data directory to see if there is // any left over data to be read. StorageManager::sendBroadcast(STATS_DATA_DIR, mSendBroadcast); @@ -81,6 +82,9 @@ StatsLogProcessor::~StatsLogProcessor() { void StatsLogProcessor::onAnomalyAlarmFired( const uint64_t timestampNs, unordered_set<sp<const AnomalyAlarm>, SpHash<AnomalyAlarm>> anomalySet) { + // TODO: This is a thread-safety issue. mMetricsManagers could change under our feet. + // TODO: Solution? Lock everything! :( + // TODO: Question: Can we replace the other lock (broadcast), or do we need to supplement it? for (const auto& itr : mMetricsManagers) { itr.second->onAnomalyAlarmFired(timestampNs, anomalySet); } @@ -94,7 +98,6 @@ void StatsLogProcessor::OnLogEvent(const LogEvent& msg) { pair.second->onLogEvent(msg); flushIfNecessary(msg.GetTimestampNs(), pair.first, *(pair.second)); } - // Hard-coded logic to update the isolated uid's in the uid-map. // The field numbers need to be currently updated by hand with atoms.proto if (msg.GetTagId() == android::util::ISOLATED_UID_CHANGED) { @@ -114,9 +117,7 @@ void StatsLogProcessor::OnLogEvent(const LogEvent& msg) { void StatsLogProcessor::OnConfigUpdated(const ConfigKey& key, const StatsdConfig& config) { ALOGD("Updated configuration for key %s", key.ToString().c_str()); - sp<MetricsManager> newMetricsManager = new MetricsManager(key, config, mTimeBaseSec, mUidMap); - auto it = mMetricsManagers.find(key); if (it == mMetricsManagers.end() && mMetricsManagers.size() > StatsdStats::kMaxConfigCount) { ALOGE("Can't accept more configs!"); @@ -149,6 +150,19 @@ size_t StatsLogProcessor::GetMetricsSize(const ConfigKey& key) const { return it->second->byteSize(); } +void StatsLogProcessor::onDumpReport(const ConfigKey& key, const uint64_t& dumpTimeStampNs, ConfigMetricsReportList* report) { + auto it = mMetricsManagers.find(key); + if (it == mMetricsManagers.end()) { + ALOGW("Config source %s does not exist", key.ToString().c_str()); + return; + } + report->mutable_config_key()->set_uid(key.GetUid()); + report->mutable_config_key()->set_id(key.GetId()); + ConfigMetricsReport* configMetricsReport = report->add_reports(); + it->second->onDumpReport(dumpTimeStampNs, configMetricsReport); + // TODO: dump uid mapping. +} + void StatsLogProcessor::onDumpReport(const ConfigKey& key, vector<uint8_t>* outData) { auto it = mMetricsManagers.find(key); if (it == mMetricsManagers.end()) { @@ -167,7 +181,7 @@ void StatsLogProcessor::onDumpReport(const ConfigKey& key, vector<uint8_t>* outD // Start of ConfigKey. long long configKeyToken = proto.start(FIELD_TYPE_MESSAGE | FIELD_ID_CONFIG_KEY); proto.write(FIELD_TYPE_INT32 | FIELD_ID_UID, key.GetUid()); - proto.write(FIELD_TYPE_STRING | FIELD_ID_NAME, key.GetName()); + proto.write(FIELD_TYPE_INT64 | FIELD_ID_ID, (long long)key.GetId()); proto.end(configKeyToken); // End of ConfigKey. @@ -264,8 +278,8 @@ void StatsLogProcessor::WriteDataToDisk() { vector<uint8_t> data; onDumpReport(key, &data); // TODO: Add a guardrail to prevent accumulation of file on disk. - string file_name = StringPrintf("%s/%d-%s-%ld", STATS_DATA_DIR, key.GetUid(), - key.GetName().c_str(), time(nullptr)); + string file_name = StringPrintf("%s/%d-%lld-%ld", STATS_DATA_DIR, key.GetUid(), + (long long)key.GetId(), time(nullptr)); StorageManager::writeFile(file_name.c_str(), &data[0], data.size()); } } diff --git a/cmds/statsd/src/StatsLogProcessor.h b/cmds/statsd/src/StatsLogProcessor.h index 1e5c426efd02..f62fc4e31c0a 100644 --- a/cmds/statsd/src/StatsLogProcessor.h +++ b/cmds/statsd/src/StatsLogProcessor.h @@ -13,9 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -#ifndef STATS_LOG_PROCESSOR_H -#define STATS_LOG_PROCESSOR_H +#pragma once + +#include <gtest/gtest_prod.h> #include "config/ConfigListener.h" #include "logd/LogReader.h" #include "metrics/MetricsManager.h" @@ -33,6 +34,7 @@ namespace statsd { class StatsLogProcessor : public ConfigListener { public: StatsLogProcessor(const sp<UidMap>& uidMap, const sp<AnomalyMonitor>& anomalyMonitor, + const long timeBaseSec, const std::function<void(const ConfigKey&)>& sendBroadcast); virtual ~StatsLogProcessor(); @@ -44,6 +46,7 @@ public: size_t GetMetricsSize(const ConfigKey& key) const; void onDumpReport(const ConfigKey& key, vector<uint8_t>* outData); + void onDumpReport(const ConfigKey& key, const uint64_t& dumpTimeStampNs, ConfigMetricsReportList* report); /* Tells MetricsManager that the alarms in anomalySet have fired. Modifies anomalySet. */ void onAnomalyAlarmFired( @@ -81,10 +84,12 @@ private: FRIEND_TEST(StatsLogProcessorTest, TestRateLimitByteSize); FRIEND_TEST(StatsLogProcessorTest, TestRateLimitBroadcast); FRIEND_TEST(StatsLogProcessorTest, TestDropWhenByteSizeTooLarge); + FRIEND_TEST(StatsLogProcessorTest, TestDropWhenByteSizeTooLarge); + FRIEND_TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensions); + FRIEND_TEST(MetricConditionLinkE2eTest, TestMultiplePredicatesAndLinks); + }; } // namespace statsd } // namespace os } // namespace android - -#endif // STATS_LOG_PROCESSOR_H diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp index 76e2e48a0379..45f1ea1bb183 100644 --- a/cmds/statsd/src/StatsService.cpp +++ b/cmds/statsd/src/StatsService.cpp @@ -75,7 +75,7 @@ StatsService::StatsService(const sp<Looper>& handlerLooper) { mUidMap = new UidMap(); mConfigManager = new ConfigManager(); - mProcessor = new StatsLogProcessor(mUidMap, mAnomalyMonitor, [this](const ConfigKey& key) { + mProcessor = new StatsLogProcessor(mUidMap, mAnomalyMonitor, time(nullptr), [this](const ConfigKey& key) { sp<IStatsCompanionService> sc = getStatsCompanionService(); auto receiver = mConfigManager->GetConfigReceiver(key); if (sc == nullptr) { @@ -335,7 +335,7 @@ status_t StatsService::cmd_trigger_broadcast(FILE* out, Vector<String8>& args) { print_cmd_help(out); return UNKNOWN_ERROR; } - auto receiver = mConfigManager->GetConfigReceiver(ConfigKey(uid, name)); + auto receiver = mConfigManager->GetConfigReceiver(ConfigKey(uid, StrToInt64(name))); sp<IStatsCompanionService> sc = getStatsCompanionService(); if (sc != nullptr) { sc->sendBroadcast(String16(receiver.first.c_str()), String16(receiver.second.c_str())); @@ -404,13 +404,13 @@ status_t StatsService::cmd_config(FILE* in, FILE* out, FILE* err, Vector<String8 } // Add / update the config. - mConfigManager->UpdateConfig(ConfigKey(uid, name), config); + mConfigManager->UpdateConfig(ConfigKey(uid, StrToInt64(name)), config); } else { if (argCount == 2) { cmd_remove_all_configs(out); } else { // Remove the config. - mConfigManager->RemoveConfig(ConfigKey(uid, name)); + mConfigManager->RemoveConfig(ConfigKey(uid, StrToInt64(name))); } } @@ -459,7 +459,7 @@ status_t StatsService::cmd_dump_report(FILE* out, FILE* err, const Vector<String } if (good) { vector<uint8_t> data; - mProcessor->onDumpReport(ConfigKey(uid, name), &data); + mProcessor->onDumpReport(ConfigKey(uid, StrToInt64(name)), &data); // TODO: print the returned StatsLogReport to file instead of printing to logcat. if (proto) { for (size_t i = 0; i < data.size(); i ++) { @@ -699,12 +699,11 @@ void StatsService::OnLogEvent(const LogEvent& event) { mProcessor->OnLogEvent(event); } -Status StatsService::getData(const String16& key, vector<uint8_t>* output) { +Status StatsService::getData(int64_t key, vector<uint8_t>* output) { IPCThreadState* ipc = IPCThreadState::self(); VLOG("StatsService::getData with Pid %i, Uid %i", ipc->getCallingPid(), ipc->getCallingUid()); if (checkCallingPermission(String16(kPermissionDump))) { - string keyStr = string(String8(key).string()); - ConfigKey configKey(ipc->getCallingUid(), keyStr); + ConfigKey configKey(ipc->getCallingUid(), key); mProcessor->onDumpReport(configKey, output); return Status::ok(); } else { @@ -724,14 +723,13 @@ Status StatsService::getMetadata(vector<uint8_t>* output) { } } -Status StatsService::addConfiguration(const String16& key, +Status StatsService::addConfiguration(int64_t key, const vector <uint8_t>& config, const String16& package, const String16& cls, bool* success) { IPCThreadState* ipc = IPCThreadState::self(); if (checkCallingPermission(String16(kPermissionDump))) { - string keyString = string(String8(key).string()); - ConfigKey configKey(ipc->getCallingUid(), keyString); + ConfigKey configKey(ipc->getCallingUid(), key); StatsdConfig cfg; if (!cfg.ParseFromArray(&config[0], config.size())) { *success = false; @@ -748,11 +746,10 @@ Status StatsService::addConfiguration(const String16& key, } } -Status StatsService::removeConfiguration(const String16& key, bool* success) { +Status StatsService::removeConfiguration(int64_t key, bool* success) { IPCThreadState* ipc = IPCThreadState::self(); if (checkCallingPermission(String16(kPermissionDump))) { - string keyStr = string(String8(key).string()); - mConfigManager->RemoveConfig(ConfigKey(ipc->getCallingUid(), keyStr)); + mConfigManager->RemoveConfig(ConfigKey(ipc->getCallingUid(), key)); *success = true; return Status::ok(); } else { diff --git a/cmds/statsd/src/StatsService.h b/cmds/statsd/src/StatsService.h index 08fcdac27539..c0424f39a1fd 100644 --- a/cmds/statsd/src/StatsService.h +++ b/cmds/statsd/src/StatsService.h @@ -77,25 +77,27 @@ public: /** * Binder call for clients to request data for this configuration key. */ - virtual Status getData(const String16& key, vector<uint8_t>* output) override; + virtual Status getData(int64_t key, vector<uint8_t>* output) override; + /** * Binder call for clients to get metadata across all configs in statsd. */ virtual Status getMetadata(vector<uint8_t>* output) override; + /** * Binder call to let clients send a configuration and indicate they're interested when they * should requestData for this configuration. */ - virtual Status addConfiguration(const String16& key, const vector <uint8_t>& config, - const String16& package, const String16& cls, bool* success) + virtual Status addConfiguration(int64_t key, const vector <uint8_t>& config, + const String16& package, const String16& cls, bool* success) override; /** * Binder call to allow clients to remove the specified configuration. */ - virtual Status removeConfiguration(const String16& key, bool* success) override; + virtual Status removeConfiguration(int64_t key, bool* success) override; // TODO: public for testing since statsd doesn't run when system starts. Change to private // later. diff --git a/cmds/statsd/src/anomaly/AnomalyTracker.cpp b/cmds/statsd/src/anomaly/AnomalyTracker.cpp index 162a34b957f8..9c797dc70501 100644 --- a/cmds/statsd/src/anomaly/AnomalyTracker.cpp +++ b/cmds/statsd/src/anomaly/AnomalyTracker.cpp @@ -30,8 +30,6 @@ namespace android { namespace os { namespace statsd { -// TODO: Separate DurationAnomalyTracker as a separate subclass and let each MetricProducer -// decide and let which one it wants. // TODO: Get rid of bucketNumbers, and return to the original circular array method. AnomalyTracker::AnomalyTracker(const Alert& alert, const ConfigKey& configKey) : mAlert(alert), @@ -52,7 +50,6 @@ AnomalyTracker::AnomalyTracker(const Alert& alert, const ConfigKey& configKey) AnomalyTracker::~AnomalyTracker() { VLOG("~AnomalyTracker() called"); - stopAllAlarms(); } void AnomalyTracker::resetStorage() { @@ -61,8 +58,6 @@ void AnomalyTracker::resetStorage() { // Excludes the current bucket. mPastBuckets.resize(mNumOfPastBuckets); mSumOverPastBuckets.clear(); - - if (!mAlarms.empty()) VLOG("AnomalyTracker.resetStorage() called but mAlarms is NOT empty!"); } size_t AnomalyTracker::index(int64_t bucketNum) const { @@ -205,43 +200,26 @@ void AnomalyTracker::declareAnomaly(const uint64_t& timestampNs) { return; } // TODO(guardrail): Consider guarding against too short refractory periods. - mLastAlarmTimestampNs = timestampNs; - + mLastAnomalyTimestampNs = timestampNs; // TODO: If we had access to the bucket_size_millis, consider calling resetStorage() // if (mAlert.refractory_period_secs() > mNumOfPastBuckets * bucketSizeNs) { resetStorage(); } - if (mAlert.has_incidentd_details()) { - if (mAlert.has_name()) { - ALOGW("An anomaly (%s) has occurred! Informing incidentd.", - mAlert.name().c_str()); + if (!mSubscriptions.empty()) { + if (mAlert.has_id()) { + ALOGI("An anomaly (%llu) has occurred! Informing subscribers.",mAlert.id()); + informSubscribers(); } else { - // TODO: Can construct a name based on the criteria (and/or relay the criteria). - ALOGW("An anomaly (nameless) has occurred! Informing incidentd."); + ALOGI("An anomaly (with no id) has occurred! Not informing any subscribers."); } - informIncidentd(); } else { - ALOGW("An anomaly has occurred! (But informing incidentd not requested.)"); + ALOGI("An anomaly has occurred! (But no subscriber for that alert.)"); } - StatsdStats::getInstance().noteAnomalyDeclared(mConfigKey, mAlert.name()); + StatsdStats::getInstance().noteAnomalyDeclared(mConfigKey, mAlert.id()); android::util::stats_write(android::util::ANOMALY_DETECTED, mConfigKey.GetUid(), - mConfigKey.GetName().c_str(), mAlert.name().c_str()); -} - -void AnomalyTracker::declareAnomalyIfAlarmExpired(const HashableDimensionKey& dimensionKey, - const uint64_t& timestampNs) { - auto itr = mAlarms.find(dimensionKey); - if (itr == mAlarms.end()) { - return; - } - - if (itr->second != nullptr && - static_cast<uint32_t>(timestampNs / NS_PER_SEC) >= itr->second->timestampSec) { - declareAnomaly(timestampNs); - stopAlarm(dimensionKey); - } + mConfigKey.GetId(), mAlert.id()); } void AnomalyTracker::detectAndDeclareAnomaly(const uint64_t& timestampNs, @@ -261,91 +239,51 @@ void AnomalyTracker::detectAndDeclareAnomaly(const uint64_t& timestampNs, } } -void AnomalyTracker::startAlarm(const HashableDimensionKey& dimensionKey, - const uint64_t& timestampNs) { - uint32_t timestampSec = static_cast<uint32_t>(timestampNs / NS_PER_SEC); - if (isInRefractoryPeriod(timestampNs)) { - VLOG("Skipping setting anomaly alarm since it'd fall in the refractory period"); - return; - } - - sp<const AnomalyAlarm> alarm = new AnomalyAlarm{timestampSec}; - mAlarms.insert({dimensionKey, alarm}); - if (mAnomalyMonitor != nullptr) { - mAnomalyMonitor->add(alarm); - } -} - -void AnomalyTracker::stopAlarm(const HashableDimensionKey& dimensionKey) { - auto itr = mAlarms.find(dimensionKey); - if (itr != mAlarms.end()) { - mAlarms.erase(dimensionKey); - if (mAnomalyMonitor != nullptr) { - mAnomalyMonitor->remove(itr->second); - } - } +bool AnomalyTracker::isInRefractoryPeriod(const uint64_t& timestampNs) const { + return mLastAnomalyTimestampNs >= 0 && + timestampNs - mLastAnomalyTimestampNs <= mAlert.refractory_period_secs() * NS_PER_SEC; } -void AnomalyTracker::stopAllAlarms() { - std::set<HashableDimensionKey> keys; - for (auto itr = mAlarms.begin(); itr != mAlarms.end(); ++itr) { - keys.insert(itr->first); - } - for (auto key : keys) { - stopAlarm(key); +void AnomalyTracker::informSubscribers() { + VLOG("informSubscribers called."); + if (mSubscriptions.empty()) { + ALOGE("Attempt to call with no subscribers."); + return; } -} - -bool AnomalyTracker::isInRefractoryPeriod(const uint64_t& timestampNs) { - return mLastAlarmTimestampNs >= 0 && - timestampNs - mLastAlarmTimestampNs <= mAlert.refractory_period_secs() * NS_PER_SEC; -} -void AnomalyTracker::informAlarmsFired(const uint64_t& timestampNs, - unordered_set<sp<const AnomalyAlarm>, SpHash<AnomalyAlarm>>& firedAlarms) { - - if (firedAlarms.empty() || mAlarms.empty()) return; - // Find the intersection of firedAlarms and mAlarms. - // The for loop is inefficient, since it loops over all keys, but that's okay since it is very - // seldomly called. The alternative would be having AnomalyAlarms store information about the - // AnomalyTracker and key, but that's a lot of data overhead to speed up something that is - // rarely ever called. - unordered_map<HashableDimensionKey, sp<const AnomalyAlarm>> matchedAlarms; - for (const auto& kv : mAlarms) { - if (firedAlarms.count(kv.second) > 0) { - matchedAlarms.insert({kv.first, kv.second}); + std::set<int> incidentdSections; + for (const Subscription& subscription : mSubscriptions) { + switch (subscription.subscriber_information_case()) { + case Subscription::SubscriberInformationCase::kIncidentdDetails: + for (int i = 0; i < subscription.incidentd_details().section_size(); i++) { + incidentdSections.insert(subscription.incidentd_details().section(i)); + } + break; + case Subscription::SubscriberInformationCase::kPerfettoDetails: + ALOGW("Perfetto reports not implemented."); + break; + default: + break; } } - - // Now declare each of these alarms to have fired. - for (const auto& kv : matchedAlarms) { - declareAnomaly(timestampNs /* TODO: , kv.first */); - mAlarms.erase(kv.first); - firedAlarms.erase(kv.second); // No one else can also own it, so we're done with it. - } -} - -void AnomalyTracker::informIncidentd() { - VLOG("informIncidentd called."); - if (!mAlert.has_incidentd_details()) { - ALOGE("Attempted to call incidentd without any incidentd_details."); - return; - } - sp<IIncidentManager> service = interface_cast<IIncidentManager>( - defaultServiceManager()->getService(android::String16("incident"))); - if (service == NULL) { - ALOGW("Couldn't get the incident service."); - return; - } - - IncidentReportArgs incidentReport; - const Alert::IncidentdDetails& details = mAlert.incidentd_details(); - for (int i = 0; i < details.section_size(); i++) { - incidentReport.addSection(details.section(i)); + if (!incidentdSections.empty()) { + sp<IIncidentManager> service = interface_cast<IIncidentManager>( + defaultServiceManager()->getService(android::String16("incident"))); + if (service != NULL) { + IncidentReportArgs incidentReport; + for (const auto section : incidentdSections) { + incidentReport.addSection(section); + } + int64_t alertId = mAlert.id(); + std::vector<uint8_t> header; + uint8_t* src = static_cast<uint8_t*>(static_cast<void*>(&alertId)); + header.insert(header.end(), src, src + sizeof(int64_t)); + incidentReport.addHeader(header); + service->reportIncident(incidentReport); + } else { + ALOGW("Couldn't get the incident service."); + } } - // TODO: Pass in mAlert.name() into the addHeader? - - service->reportIncident(incidentReport); } } // namespace statsd diff --git a/cmds/statsd/src/anomaly/AnomalyTracker.h b/cmds/statsd/src/anomaly/AnomalyTracker.h index 874add2ba798..2d5ab867da00 100644 --- a/cmds/statsd/src/anomaly/AnomalyTracker.h +++ b/cmds/statsd/src/anomaly/AnomalyTracker.h @@ -40,6 +40,11 @@ public: virtual ~AnomalyTracker(); + // Add subscriptions that depend on this alert. + void addSubscription(const Subscription& subscription) { + mSubscriptions.push_back(subscription); + } + // Adds a bucket. // Bucket index starts from 0. void addPastBucket(std::shared_ptr<DimToValMap> bucketValues, const int64_t& bucketNum); @@ -61,23 +66,11 @@ public: const HashableDimensionKey& key, const int64_t& currentBucketValue); - // Starts the alarm at the given timestamp. - void startAlarm(const HashableDimensionKey& dimensionKey, const uint64_t& eventTime); - // Stops the alarm. - void stopAlarm(const HashableDimensionKey& dimensionKey); - - // Stop all the alarms owned by this tracker. - void stopAllAlarms(); - - // Init the anmaly monitor which is shared across anomaly trackers. - inline void setAnomalyMonitor(const sp<AnomalyMonitor>& anomalyMonitor) { - mAnomalyMonitor = anomalyMonitor; + // Init the AnomalyMonitor which is shared across anomaly trackers. + virtual void setAnomalyMonitor(const sp<AnomalyMonitor>& anomalyMonitor) { + return; // Base AnomalyTracker class has no need for the AnomalyMonitor. } - // Declares the anomaly when the alarm expired given the current timestamp. - void declareAnomalyIfAlarmExpired(const HashableDimensionKey& dimensionKey, - const uint64_t& timestampNs); - // Helper function to return the sum value of past buckets at given dimension. int64_t getSumOverPastBuckets(const HashableDimensionKey& key) const; @@ -89,9 +82,9 @@ public: return mAlert.trigger_if_sum_gt(); } - // Helper function to return the last alarm timestamp. - inline int64_t getLastAlarmTimestampNs() const { - return mLastAlarmTimestampNs; + // Helper function to return the timestamp of the last detected anomaly. + inline int64_t getLastAnomalyTimestampNs() const { + return mLastAnomalyTimestampNs; } inline int getNumOfPastBuckets() const { @@ -100,18 +93,18 @@ public: // Declares an anomaly for each alarm in firedAlarms that belongs to this AnomalyTracker, // and removes it from firedAlarms. Does NOT remove the alarm from the AnomalyMonitor. - // TODO: This will actually be called from a different thread, so make it thread-safe! - // TODO: Consider having AnomalyMonitor have a reference to each relevant MetricProducer - // instead of calling it from a chain starting at StatsLogProcessor. - void informAlarmsFired(const uint64_t& timestampNs, - unordered_set<sp<const AnomalyAlarm>, SpHash<AnomalyAlarm>>& firedAlarms); + virtual void informAlarmsFired(const uint64_t& timestampNs, + unordered_set<sp<const AnomalyAlarm>, SpHash<AnomalyAlarm>>& firedAlarms) { + return; // The base AnomalyTracker class doesn't have alarms. + } protected: - void flushPastBuckets(const int64_t& currBucketNum); - // statsd_config.proto Alert message that defines this tracker. const Alert mAlert; + // The subscriptions that depend on this alert. + std::vector<Subscription> mSubscriptions; + // A reference to the Alert's config key. const ConfigKey& mConfigKey; @@ -119,14 +112,7 @@ protected: // for the anomaly detection (since the current bucket is not in the past). int mNumOfPastBuckets; - // The alarms owned by this tracker. The alarm monitor also shares the alarm pointers when they - // are still active. - std::unordered_map<HashableDimensionKey, sp<const AnomalyAlarm>> mAlarms; - - // Anomaly alarm monitor. - sp<AnomalyMonitor> mAnomalyMonitor; - - // The exisiting bucket list. + // The existing bucket list. std::vector<shared_ptr<DimToValMap>> mPastBuckets; // Sum over all existing buckets cached in mPastBuckets. @@ -136,7 +122,9 @@ protected: int64_t mMostRecentBucketNum = -1; // The timestamp when the last anomaly was declared. - int64_t mLastAlarmTimestampNs = -1; + int64_t mLastAnomalyTimestampNs = -1; + + void flushPastBuckets(const int64_t& currBucketNum); // Add the information in the given bucket to mSumOverPastBuckets. void addBucketToSum(const shared_ptr<DimToValMap>& bucket); @@ -145,26 +133,21 @@ protected: // and remove any items with value 0. void subtractBucketFromSum(const shared_ptr<DimToValMap>& bucket); - bool isInRefractoryPeriod(const uint64_t& timestampNs); + bool isInRefractoryPeriod(const uint64_t& timestampNs) const; // Calculates the corresponding bucket index within the circular array. size_t index(int64_t bucketNum) const; // Resets all bucket data. For use when all the data gets stale. - void resetStorage(); + virtual void resetStorage(); - // Informs the incident service that an anomaly has occurred. - void informIncidentd(); + // Informs the subscribers that an anomaly has occurred. + void informSubscribers(); FRIEND_TEST(AnomalyTrackerTest, TestConsecutiveBuckets); FRIEND_TEST(AnomalyTrackerTest, TestSparseBuckets); FRIEND_TEST(GaugeMetricProducerTest, TestAnomalyDetection); FRIEND_TEST(CountMetricProducerTest, TestAnomalyDetection); - FRIEND_TEST(OringDurationTrackerTest, TestPredictAnomalyTimestamp); - FRIEND_TEST(OringDurationTrackerTest, TestAnomalyDetection); - FRIEND_TEST(MaxDurationTrackerTest, TestAnomalyDetection); - FRIEND_TEST(MaxDurationTrackerTest, TestAnomalyDetection); - FRIEND_TEST(OringDurationTrackerTest, TestAnomalyDetection); }; } // namespace statsd diff --git a/cmds/statsd/src/anomaly/DurationAnomalyTracker.cpp b/cmds/statsd/src/anomaly/DurationAnomalyTracker.cpp new file mode 100644 index 000000000000..d30810fc800d --- /dev/null +++ b/cmds/statsd/src/anomaly/DurationAnomalyTracker.cpp @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2017 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. + */ + +#define DEBUG true // STOPSHIP if true +#include "Log.h" + +#include "DurationAnomalyTracker.h" +#include "guardrail/StatsdStats.h" + +namespace android { +namespace os { +namespace statsd { + +DurationAnomalyTracker::DurationAnomalyTracker(const Alert& alert, const ConfigKey& configKey) + : AnomalyTracker(alert, configKey) { +} + +DurationAnomalyTracker::~DurationAnomalyTracker() { + stopAllAlarms(); +} + +void DurationAnomalyTracker::resetStorage() { + AnomalyTracker::resetStorage(); + if (!mAlarms.empty()) VLOG("AnomalyTracker.resetStorage() called but mAlarms is NOT empty!"); +} + +void DurationAnomalyTracker::declareAnomalyIfAlarmExpired(const HashableDimensionKey& dimensionKey, + const uint64_t& timestampNs) { + auto itr = mAlarms.find(dimensionKey); + if (itr == mAlarms.end()) { + return; + } + + if (itr->second != nullptr && + static_cast<uint32_t>(timestampNs / NS_PER_SEC) >= itr->second->timestampSec) { + declareAnomaly(timestampNs); + stopAlarm(dimensionKey); + } +} + +void DurationAnomalyTracker::startAlarm(const HashableDimensionKey& dimensionKey, + const uint64_t& timestampNs) { + + uint32_t timestampSec = static_cast<uint32_t>(timestampNs / NS_PER_SEC); + if (isInRefractoryPeriod(timestampNs)) { + VLOG("Skipping setting anomaly alarm since it'd fall in the refractory period"); + return; + } + sp<const AnomalyAlarm> alarm = new AnomalyAlarm{timestampSec}; + mAlarms.insert({dimensionKey, alarm}); + if (mAnomalyMonitor != nullptr) { + mAnomalyMonitor->add(alarm); + } +} + +void DurationAnomalyTracker::stopAlarm(const HashableDimensionKey& dimensionKey) { + auto itr = mAlarms.find(dimensionKey); + if (itr != mAlarms.end()) { + mAlarms.erase(dimensionKey); + if (mAnomalyMonitor != nullptr) { + mAnomalyMonitor->remove(itr->second); + } + } +} + +void DurationAnomalyTracker::stopAllAlarms() { + std::set<HashableDimensionKey> keys; + for (auto itr = mAlarms.begin(); itr != mAlarms.end(); ++itr) { + keys.insert(itr->first); + } + for (auto key : keys) { + stopAlarm(key); + } +} + +void DurationAnomalyTracker::informAlarmsFired(const uint64_t& timestampNs, + unordered_set<sp<const AnomalyAlarm>, SpHash<AnomalyAlarm>>& firedAlarms) { + + if (firedAlarms.empty() || mAlarms.empty()) return; + // Find the intersection of firedAlarms and mAlarms. + // The for loop is inefficient, since it loops over all keys, but that's okay since it is very + // seldomly called. The alternative would be having AnomalyAlarms store information about the + // DurationAnomalyTracker and key, but that's a lot of data overhead to speed up something that is + // rarely ever called. + unordered_map<HashableDimensionKey, sp<const AnomalyAlarm>> matchedAlarms; + for (const auto& kv : mAlarms) { + if (firedAlarms.count(kv.second) > 0) { + matchedAlarms.insert({kv.first, kv.second}); + } + } + + // Now declare each of these alarms to have fired. + for (const auto& kv : matchedAlarms) { + declareAnomaly(timestampNs /* TODO: , kv.first */); + mAlarms.erase(kv.first); + firedAlarms.erase(kv.second); // No one else can also own it, so we're done with it. + } +} + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/cmds/statsd/src/anomaly/DurationAnomalyTracker.h b/cmds/statsd/src/anomaly/DurationAnomalyTracker.h new file mode 100644 index 000000000000..182ce3ba74c0 --- /dev/null +++ b/cmds/statsd/src/anomaly/DurationAnomalyTracker.h @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2017 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. + */ + +#pragma once + +#include "AnomalyMonitor.h" +#include "AnomalyTracker.h" + +namespace android { +namespace os { +namespace statsd { + +using std::unordered_map; + +class DurationAnomalyTracker : public virtual AnomalyTracker { +public: + DurationAnomalyTracker(const Alert& alert, const ConfigKey& configKey); + + virtual ~DurationAnomalyTracker(); + + // Starts the alarm at the given timestamp. + void startAlarm(const HashableDimensionKey& dimensionKey, const uint64_t& eventTime); + + // Stops the alarm. + void stopAlarm(const HashableDimensionKey& dimensionKey); + + // Stop all the alarms owned by this tracker. + void stopAllAlarms(); + + // Init the AnomalyMonitor which is shared across anomaly trackers. + void setAnomalyMonitor(const sp<AnomalyMonitor>& anomalyMonitor) override { + mAnomalyMonitor = anomalyMonitor; + } + + // Declares the anomaly when the alarm expired given the current timestamp. + void declareAnomalyIfAlarmExpired(const HashableDimensionKey& dimensionKey, + const uint64_t& timestampNs); + + // Declares an anomaly for each alarm in firedAlarms that belongs to this DurationAnomalyTracker + // and removes it from firedAlarms. Does NOT remove the alarm from the AnomalyMonitor. + // TODO: This will actually be called from a different thread, so make it thread-safe! + // This means that almost every function in DurationAnomalyTracker needs to be locked. + // But this should be done at the level of StatsLogProcessor, which needs to lock + // mMetricsMangers anyway. + void informAlarmsFired(const uint64_t& timestampNs, + unordered_set<sp<const AnomalyAlarm>, SpHash<AnomalyAlarm>>& firedAlarms) override; + +protected: + // The alarms owned by this tracker. The alarm monitor also shares the alarm pointers when they + // are still active. + std::unordered_map<HashableDimensionKey, sp<const AnomalyAlarm>> mAlarms; + + // Anomaly alarm monitor. + sp<AnomalyMonitor> mAnomalyMonitor; + + // Resets all bucket data. For use when all the data gets stale. + void resetStorage() override; + + FRIEND_TEST(OringDurationTrackerTest, TestPredictAnomalyTimestamp); + FRIEND_TEST(OringDurationTrackerTest, TestAnomalyDetection); + FRIEND_TEST(MaxDurationTrackerTest, TestAnomalyDetection); + FRIEND_TEST(MaxDurationTrackerTest, TestAnomalyDetection); + FRIEND_TEST(OringDurationTrackerTest, TestAnomalyDetection); +}; + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto index 1c6d9b09a839..221a55438f73 100644 --- a/cmds/statsd/src/atoms.proto +++ b/cmds/statsd/src/atoms.proto @@ -104,24 +104,18 @@ message Atom { } /** - * An attribution represents an application or module that is part of process where a particular bit - * of work is done. + * This proto represents a node of an attribution chain. + * Note: All attribution chains are represented as a repeated field of type + * AttributionNode. It is understood that in such arrays, the order is that + * of calls, that is [A, B, C] if A calls B that calls C. */ -message Attribution { - // The uid for an application or module. +message AttributionNode { + // The uid for a given element in the attribution chain. optional int32 uid = 1; - // The string tag for the attribution node. - optional string tag = 2; -} -/** - * An attribution chain represents the chained attributions of applications or modules that - * resulted in a particular bit of work being done. - * The ordering of the attributions is that of calls, that is uid = [A, B, C] if A calls B that - * calls C. - */ -message AttributionChain { - repeated Attribution attribution = 1; + // The (optional) string tag for an element in the attribution chain. If the + // element has no tag, it is encoded as an empty string. + optional string tag = 2; } /* @@ -151,7 +145,7 @@ message AttributionChain { */ message AttributionChainDummyAtom { - optional AttributionChain attribution_chain = 1; + repeated AttributionNode attribution_node = 1; optional int32 value = 2; } @@ -421,8 +415,7 @@ message CameraStateChanged { * TODO */ message WakelockStateChanged { - // TODO: Add attribution instead of uid. - optional int32 uid = 1; + repeated AttributionNode attribution_node = 1; // Type of wakelock. enum Type { @@ -780,13 +773,14 @@ message SettingChanged { * frameworks/base/services/core/java/com/android/server/am/ActivityRecord.java */ message ActivityForegroundStateChanged { + optional int32 uid = 1; + optional string pkg_name = 2; + optional string class_name = 3; + enum Activity { MOVE_TO_BACKGROUND = 0; MOVE_TO_FOREGROUND = 1; } - optional int32 uid = 1; - optional string pkg_name = 2; - optional string class_name = 3; optional Activity activity = 4; } @@ -851,11 +845,11 @@ message AnomalyDetected { // Uid that owns the config whose anomaly detection alert fired. optional int32 config_uid = 1; - // Name of the config whose anomaly detection alert fired. - optional string config_name = 2; + // Id of the config whose anomaly detection alert fired. + optional int64 config_id = 2; - // Name of the alert (i.e. name of the anomaly that was detected). - optional string alert_name = 3; + // Id of the alert (i.e. name of the anomaly that was detected). + optional int64 alert_id = 3; } /** diff --git a/cmds/statsd/src/condition/CombinationConditionTracker.cpp b/cmds/statsd/src/condition/CombinationConditionTracker.cpp index bb4b817a820d..afa26f6da08a 100644 --- a/cmds/statsd/src/condition/CombinationConditionTracker.cpp +++ b/cmds/statsd/src/condition/CombinationConditionTracker.cpp @@ -30,20 +30,20 @@ using std::unique_ptr; using std::unordered_map; using std::vector; -CombinationConditionTracker::CombinationConditionTracker(const string& name, const int index) - : ConditionTracker(name, index) { - VLOG("creating CombinationConditionTracker %s", mName.c_str()); +CombinationConditionTracker::CombinationConditionTracker(const int64_t& id, const int index) + : ConditionTracker(id, index) { + VLOG("creating CombinationConditionTracker %lld", (long long)mConditionId); } CombinationConditionTracker::~CombinationConditionTracker() { - VLOG("~CombinationConditionTracker() %s", mName.c_str()); + VLOG("~CombinationConditionTracker() %lld", (long long)mConditionId); } bool CombinationConditionTracker::init(const vector<Predicate>& allConditionConfig, const vector<sp<ConditionTracker>>& allConditionTrackers, - const unordered_map<string, int>& conditionNameIndexMap, + const unordered_map<int64_t, int>& conditionIdIndexMap, vector<bool>& stack) { - VLOG("Combination predicate init() %s", mName.c_str()); + VLOG("Combination predicate init() %lld", (long long)mConditionId); if (mInitialized) { return true; } @@ -62,11 +62,11 @@ bool CombinationConditionTracker::init(const vector<Predicate>& allConditionConf return false; } - for (string child : combinationCondition.predicate()) { - auto it = conditionNameIndexMap.find(child); + for (auto child : combinationCondition.predicate()) { + auto it = conditionIdIndexMap.find(child); - if (it == conditionNameIndexMap.end()) { - ALOGW("Predicate %s not found in the config", child.c_str()); + if (it == conditionIdIndexMap.end()) { + ALOGW("Predicate %lld not found in the config", (long long)child); return false; } @@ -79,13 +79,13 @@ bool CombinationConditionTracker::init(const vector<Predicate>& allConditionConf } bool initChildSucceeded = childTracker->init(allConditionConfig, allConditionTrackers, - conditionNameIndexMap, stack); + conditionIdIndexMap, stack); if (!initChildSucceeded) { - ALOGW("Child initialization failed %s ", child.c_str()); + ALOGW("Child initialization failed %lld ", (long long)child); return false; } else { - ALOGW("Child initialization success %s ", child.c_str()); + ALOGW("Child initialization success %lld ", (long long)child); } mChildren.push_back(childIndex); @@ -103,7 +103,7 @@ bool CombinationConditionTracker::init(const vector<Predicate>& allConditionConf } void CombinationConditionTracker::isConditionMet( - const map<string, HashableDimensionKey>& conditionParameters, + const ConditionKey& conditionParameters, const vector<sp<ConditionTracker>>& allConditions, vector<ConditionState>& conditionCache) const { for (const int childIndex : mChildren) { @@ -154,8 +154,8 @@ void CombinationConditionTracker::evaluateCondition( } } nonSlicedConditionCache[mIndex] = ConditionState::kUnknown; - ALOGD("CombinationPredicate %s sliced may changed? %d", mName.c_str(), - conditionChangedCache[mIndex] == true); + ALOGD("CombinationPredicate %lld sliced may changed? %d", (long long)mConditionId, + conditionChangedCache[mIndex] == true); } } diff --git a/cmds/statsd/src/condition/CombinationConditionTracker.h b/cmds/statsd/src/condition/CombinationConditionTracker.h index 93369144ecd6..dfd3837f31f4 100644 --- a/cmds/statsd/src/condition/CombinationConditionTracker.h +++ b/cmds/statsd/src/condition/CombinationConditionTracker.h @@ -26,13 +26,13 @@ namespace statsd { class CombinationConditionTracker : public virtual ConditionTracker { public: - CombinationConditionTracker(const std::string& name, const int index); + CombinationConditionTracker(const int64_t& id, const int index); ~CombinationConditionTracker(); bool init(const std::vector<Predicate>& allConditionConfig, const std::vector<sp<ConditionTracker>>& allConditionTrackers, - const std::unordered_map<std::string, int>& conditionNameIndexMap, + const std::unordered_map<int64_t, int>& conditionIdIndexMap, std::vector<bool>& stack) override; void evaluateCondition(const LogEvent& event, @@ -41,7 +41,7 @@ public: std::vector<ConditionState>& conditionCache, std::vector<bool>& changedCache) override; - void isConditionMet(const std::map<std::string, HashableDimensionKey>& conditionParameters, + void isConditionMet(const ConditionKey& conditionParameters, const std::vector<sp<ConditionTracker>>& allConditions, std::vector<ConditionState>& conditionCache) const override; diff --git a/cmds/statsd/src/condition/ConditionTracker.h b/cmds/statsd/src/condition/ConditionTracker.h index 6f66ad685ccf..773860f429b1 100644 --- a/cmds/statsd/src/condition/ConditionTracker.h +++ b/cmds/statsd/src/condition/ConditionTracker.h @@ -32,8 +32,8 @@ namespace statsd { class ConditionTracker : public virtual RefBase { public: - ConditionTracker(const std::string& name, const int index) - : mName(name), + ConditionTracker(const int64_t& id, const int index) + : mConditionId(id), mIndex(index), mInitialized(false), mTrackerIndex(), @@ -42,17 +42,19 @@ public: virtual ~ConditionTracker(){}; + inline const int64_t& getId() { return mConditionId; } + // Initialize this ConditionTracker. This initialization is done recursively (DFS). It can also // be done in the constructor, but we do it separately because (1) easy to return a bool to // indicate whether the initialization is successful. (2) makes unit test easier. // allConditionConfig: the list of all Predicate config from statsd_config. // allConditionTrackers: the list of all ConditionTrackers (this is needed because we may also // need to call init() on children conditions) - // conditionNameIndexMap: the mapping from condition name to its index. + // conditionIdIndexMap: the mapping from condition id to its index. // stack: a bit map to keep track which nodes have been visited on the stack in the recursion. virtual bool init(const std::vector<Predicate>& allConditionConfig, const std::vector<sp<ConditionTracker>>& allConditionTrackers, - const std::unordered_map<std::string, int>& conditionNameIndexMap, + const std::unordered_map<int64_t, int>& conditionIdIndexMap, std::vector<bool>& stack) = 0; // evaluate current condition given the new event. @@ -83,7 +85,7 @@ public: // done recursively // [conditionCache]: the cache holding the condition evaluation values. virtual void isConditionMet( - const std::map<std::string, HashableDimensionKey>& conditionParameters, + const ConditionKey& conditionParameters, const std::vector<sp<ConditionTracker>>& allConditions, std::vector<ConditionState>& conditionCache) const = 0; @@ -97,9 +99,7 @@ public: } protected: - // We don't really need the string name, but having a name here makes log messages - // easy to debug. - const std::string mName; + const int64_t mConditionId; // the index of this condition in the manager's condition list. const int mIndex; diff --git a/cmds/statsd/src/condition/ConditionWizard.cpp b/cmds/statsd/src/condition/ConditionWizard.cpp index 411f7e510c3b..d99c2ccd1fda 100644 --- a/cmds/statsd/src/condition/ConditionWizard.cpp +++ b/cmds/statsd/src/condition/ConditionWizard.cpp @@ -24,7 +24,7 @@ using std::string; using std::vector; ConditionState ConditionWizard::query(const int index, - const map<string, HashableDimensionKey>& parameters) { + const ConditionKey& parameters) { vector<ConditionState> cache(mAllConditions.size(), ConditionState::kNotEvaluated); mAllConditions[index]->isConditionMet(parameters, mAllConditions, cache); diff --git a/cmds/statsd/src/condition/ConditionWizard.h b/cmds/statsd/src/condition/ConditionWizard.h index 30a368412c8e..4ff5c07e210f 100644 --- a/cmds/statsd/src/condition/ConditionWizard.h +++ b/cmds/statsd/src/condition/ConditionWizard.h @@ -19,6 +19,7 @@ #include "ConditionTracker.h" #include "condition_util.h" +#include "stats_util.h" namespace android { namespace os { @@ -40,7 +41,7 @@ public: // the conditionParameters contains the parameters for it's children SimpleConditionTrackers. virtual ConditionState query( const int conditionIndex, - const std::map<std::string, HashableDimensionKey>& conditionParameters); + const ConditionKey& conditionParameters); private: std::vector<sp<ConditionTracker>> mAllConditions; diff --git a/cmds/statsd/src/condition/SimpleConditionTracker.cpp b/cmds/statsd/src/condition/SimpleConditionTracker.cpp index a63bc042284a..25257213a5d0 100644 --- a/cmds/statsd/src/condition/SimpleConditionTracker.cpp +++ b/cmds/statsd/src/condition/SimpleConditionTracker.cpp @@ -33,17 +33,17 @@ using std::unordered_map; using std::vector; SimpleConditionTracker::SimpleConditionTracker( - const ConfigKey& key, const string& name, const int index, + const ConfigKey& key, const int64_t& id, const int index, const SimplePredicate& simplePredicate, - const unordered_map<string, int>& trackerNameIndexMap) - : ConditionTracker(name, index), mConfigKey(key) { - VLOG("creating SimpleConditionTracker %s", mName.c_str()); + const unordered_map<int64_t, int>& trackerNameIndexMap) + : ConditionTracker(id, index), mConfigKey(key) { + VLOG("creating SimpleConditionTracker %lld", (long long)mConditionId); mCountNesting = simplePredicate.count_nesting(); if (simplePredicate.has_start()) { auto pair = trackerNameIndexMap.find(simplePredicate.start()); if (pair == trackerNameIndexMap.end()) { - ALOGW("Start matcher %s not found in the config", simplePredicate.start().c_str()); + ALOGW("Start matcher %lld not found in the config", (long long)simplePredicate.start()); return; } mStartLogMatcherIndex = pair->second; @@ -55,7 +55,7 @@ SimpleConditionTracker::SimpleConditionTracker( if (simplePredicate.has_stop()) { auto pair = trackerNameIndexMap.find(simplePredicate.stop()); if (pair == trackerNameIndexMap.end()) { - ALOGW("Stop matcher %s not found in the config", simplePredicate.stop().c_str()); + ALOGW("Stop matcher %lld not found in the config", (long long)simplePredicate.stop()); return; } mStopLogMatcherIndex = pair->second; @@ -67,7 +67,7 @@ SimpleConditionTracker::SimpleConditionTracker( if (simplePredicate.has_stop_all()) { auto pair = trackerNameIndexMap.find(simplePredicate.stop_all()); if (pair == trackerNameIndexMap.end()) { - ALOGW("Stop all matcher %s not found in the config", simplePredicate.stop().c_str()); + ALOGW("Stop all matcher %lld found in the config", (long long)simplePredicate.stop_all()); return; } mStopAllLogMatcherIndex = pair->second; @@ -76,10 +76,9 @@ SimpleConditionTracker::SimpleConditionTracker( mStopAllLogMatcherIndex = -1; } - mOutputDimension.insert(mOutputDimension.begin(), simplePredicate.dimension().begin(), - simplePredicate.dimension().end()); + mOutputDimensions = simplePredicate.dimensions(); - if (mOutputDimension.size() > 0) { + if (mOutputDimensions.child_size() > 0) { mSliced = true; } @@ -100,15 +99,15 @@ SimpleConditionTracker::~SimpleConditionTracker() { bool SimpleConditionTracker::init(const vector<Predicate>& allConditionConfig, const vector<sp<ConditionTracker>>& allConditionTrackers, - const unordered_map<string, int>& conditionNameIndexMap, + const unordered_map<int64_t, int>& conditionIdIndexMap, vector<bool>& stack) { // SimpleConditionTracker does not have dependency on other conditions, thus we just return // if the initialization was successful. return mInitialized; } -void print(map<HashableDimensionKey, int>& conditions, const string& name) { - VLOG("%s DUMP:", name.c_str()); +void print(map<HashableDimensionKey, int>& conditions, const int64_t& id) { + VLOG("%lld DUMP:", (long long)id); for (const auto& pair : conditions) { VLOG("\t%s : %d", pair.first.c_str(), pair.second); } @@ -136,10 +135,11 @@ bool SimpleConditionTracker::hitGuardRail(const HashableDimensionKey& newKey) { // 1. Report the tuple count if the tuple count > soft limit if (mSlicedConditionState.size() > StatsdStats::kDimensionKeySizeSoftLimit - 1) { size_t newTupleCount = mSlicedConditionState.size() + 1; - StatsdStats::getInstance().noteConditionDimensionSize(mConfigKey, mName, newTupleCount); + StatsdStats::getInstance().noteConditionDimensionSize(mConfigKey, mConditionId, newTupleCount); // 2. Don't add more tuples, we are above the allowed threshold. Drop the data. if (newTupleCount > StatsdStats::kDimensionKeySizeHardLimit) { - ALOGE("Predicate %s dropping data for dimension key %s", mName.c_str(), newKey.c_str()); + ALOGE("Predicate %lld dropping data for dimension key %s", + (long long)mConditionId, newKey.c_str()); return true; } } @@ -150,6 +150,14 @@ void SimpleConditionTracker::handleConditionEvent(const HashableDimensionKey& ou bool matchStart, std::vector<ConditionState>& conditionCache, std::vector<bool>& conditionChangedCache) { + if ((int)conditionChangedCache.size() <= mIndex) { + ALOGE("handleConditionEvent: param conditionChangedCache not initialized."); + return; + } + if ((int)conditionCache.size() <= mIndex) { + ALOGE("handleConditionEvent: param conditionCache not initialized."); + return; + } bool changed = false; auto outputIt = mSlicedConditionState.find(outputKey); ConditionState newCondition; @@ -215,13 +223,13 @@ void SimpleConditionTracker::handleConditionEvent(const HashableDimensionKey& ou // dump all dimensions for debugging if (DEBUG) { - print(mSlicedConditionState, mName); + print(mSlicedConditionState, mConditionId); } conditionChangedCache[mIndex] = changed; conditionCache[mIndex] = newCondition; - VLOG("SimplePredicate %s nonSlicedChange? %d", mName.c_str(), + VLOG("SimplePredicate %lld nonSlicedChange? %d", (long long)mConditionId, conditionChangedCache[mIndex] == true); } @@ -232,7 +240,8 @@ void SimpleConditionTracker::evaluateCondition(const LogEvent& event, vector<bool>& conditionChangedCache) { if (conditionCache[mIndex] != ConditionState::kNotEvaluated) { // it has been evaluated. - VLOG("Yes, already evaluated, %s %d", mName.c_str(), conditionCache[mIndex]); + VLOG("Yes, already evaluated, %lld %d", + (long long)mConditionId, conditionCache[mIndex]); return; } @@ -278,37 +287,65 @@ void SimpleConditionTracker::evaluateCondition(const LogEvent& event, return; } - // outputKey is the output key values. e.g, uid:1234 - const HashableDimensionKey outputKey(getDimensionKey(event, mOutputDimension)); - handleConditionEvent(outputKey, matchedState == 1, conditionCache, conditionChangedCache); + // outputKey is the output values. e.g, uid:1234 + const std::vector<DimensionsValue> outputValues = getDimensionKeys(event, mOutputDimensions); + if (outputValues.size() == 0) { + // The original implementation would generate an empty string dimension hash when condition + // is not sliced. + handleConditionEvent( + DEFAULT_DIMENSION_KEY, matchedState == 1, conditionCache, conditionChangedCache); + } else if (outputValues.size() == 1) { + handleConditionEvent(HashableDimensionKey(outputValues[0]), matchedState == 1, + conditionCache, conditionChangedCache); + } else { + // If this event has multiple nodes in the attribution chain, this log event probably will + // generate multiple dimensions. If so, we will find if the condition changes for any + // dimension and ask the corresponding metric producer to verify whether the actual sliced + // condition has changed or not. + // A high level assumption is that a predicate is either sliced or unsliced. We will never + // have both sliced and unsliced version of a predicate. + for (const DimensionsValue& outputValue : outputValues) { + vector<ConditionState> dimensionalConditionCache(conditionCache.size(), + ConditionState::kNotEvaluated); + vector<bool> dimensionalConditionChangedCache(conditionChangedCache.size(), false); + + handleConditionEvent(HashableDimensionKey(outputValue), matchedState == 1, + dimensionalConditionCache, dimensionalConditionChangedCache); + + OrConditionState(dimensionalConditionCache, &conditionCache); + OrBooleanVector(dimensionalConditionChangedCache, &conditionChangedCache); + } + } } void SimpleConditionTracker::isConditionMet( - const map<string, HashableDimensionKey>& conditionParameters, + const ConditionKey& conditionParameters, const vector<sp<ConditionTracker>>& allConditions, vector<ConditionState>& conditionCache) const { - const auto pair = conditionParameters.find(mName); - HashableDimensionKey key = - (pair == conditionParameters.end()) ? DEFAULT_DIMENSION_KEY : pair->second; + const auto pair = conditionParameters.find(mConditionId); - if (pair == conditionParameters.end() && mOutputDimension.size() > 0) { - ALOGE("Predicate %s output has dimension, but it's not specified in the query!", - mName.c_str()); + if (pair == conditionParameters.end() && mOutputDimensions.child_size() > 0) { + ALOGE("Predicate %lld output has dimension, but it's not specified in the query!", + (long long)mConditionId); conditionCache[mIndex] = mInitialValue; return; } - - VLOG("simplePredicate %s query key: %s", mName.c_str(), key.c_str()); - - auto startedCountIt = mSlicedConditionState.find(key); - if (startedCountIt == mSlicedConditionState.end()) { - conditionCache[mIndex] = mInitialValue; - } else { - conditionCache[mIndex] = - startedCountIt->second > 0 ? ConditionState::kTrue : ConditionState::kFalse; + std::vector<HashableDimensionKey> defaultKeys = {DEFAULT_DIMENSION_KEY}; + const std::vector<HashableDimensionKey> &keys = + (pair == conditionParameters.end()) ? defaultKeys : pair->second; + + ConditionState conditionState = ConditionState::kNotEvaluated; + for (const auto& key : keys) { + auto startedCountIt = mSlicedConditionState.find(key); + if (startedCountIt != mSlicedConditionState.end()) { + conditionState = conditionState | + (startedCountIt->second > 0 ? ConditionState::kTrue : ConditionState::kFalse); + } else { + conditionState = conditionState | mInitialValue; + } } - - VLOG("Predicate %s return %d", mName.c_str(), conditionCache[mIndex]); + conditionCache[mIndex] = conditionState; + VLOG("Predicate %lld return %d", (long long)mConditionId, conditionCache[mIndex]); } } // namespace statsd diff --git a/cmds/statsd/src/condition/SimpleConditionTracker.h b/cmds/statsd/src/condition/SimpleConditionTracker.h index 644d84c76591..815b445a8c5b 100644 --- a/cmds/statsd/src/condition/SimpleConditionTracker.h +++ b/cmds/statsd/src/condition/SimpleConditionTracker.h @@ -29,15 +29,15 @@ namespace statsd { class SimpleConditionTracker : public virtual ConditionTracker { public: - SimpleConditionTracker(const ConfigKey& key, const std::string& name, const int index, + SimpleConditionTracker(const ConfigKey& key, const int64_t& id, const int index, const SimplePredicate& simplePredicate, - const std::unordered_map<std::string, int>& trackerNameIndexMap); + const std::unordered_map<int64_t, int>& trackerNameIndexMap); ~SimpleConditionTracker(); bool init(const std::vector<Predicate>& allConditionConfig, const std::vector<sp<ConditionTracker>>& allConditionTrackers, - const std::unordered_map<std::string, int>& conditionNameIndexMap, + const std::unordered_map<int64_t, int>& conditionIdIndexMap, std::vector<bool>& stack) override; void evaluateCondition(const LogEvent& event, @@ -46,7 +46,7 @@ public: std::vector<ConditionState>& conditionCache, std::vector<bool>& changedCache) override; - void isConditionMet(const std::map<std::string, HashableDimensionKey>& conditionParameters, + void isConditionMet(const ConditionKey& conditionParameters, const std::vector<sp<ConditionTracker>>& allConditions, std::vector<ConditionState>& conditionCache) const override; @@ -66,7 +66,7 @@ private: ConditionState mInitialValue; - std::vector<KeyMatcher> mOutputDimension; + FieldMatcher mOutputDimensions; std::map<HashableDimensionKey, int> mSlicedConditionState; diff --git a/cmds/statsd/src/condition/condition_util.cpp b/cmds/statsd/src/condition/condition_util.cpp index 53ef9d58b889..a5aee73ce84f 100644 --- a/cmds/statsd/src/condition/condition_util.cpp +++ b/cmds/statsd/src/condition/condition_util.cpp @@ -27,6 +27,7 @@ #include "ConditionTracker.h" #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h" #include "stats_util.h" +#include "dimension.h" namespace android { namespace os { @@ -93,23 +94,106 @@ ConditionState evaluateCombinationCondition(const std::vector<int>& children, return newCondition; } -HashableDimensionKey getDimensionKeyForCondition(const LogEvent& event, - const MetricConditionLink& link) { - vector<KeyMatcher> eventKey; - eventKey.reserve(link.key_in_what().size()); +ConditionState operator|(ConditionState l, ConditionState r) { + return l >= r ? l : r; +} + +void OrConditionState(const std::vector<ConditionState>& ref, vector<ConditionState> * ored) { + if (ref.size() != ored->size()) { + return; + } + for (size_t i = 0; i < ored->size(); ++i) { + ored->at(i) = ored->at(i) | ref.at(i); + } +} - for (const auto& key : link.key_in_what()) { - eventKey.push_back(key); +void OrBooleanVector(const std::vector<bool>& ref, vector<bool> * ored) { + if (ref.size() != ored->size()) { + return; + } + for (size_t i = 0; i < ored->size(); ++i) { + ored->at(i) = ored->at(i) | ref.at(i); + } +} + +void getFieldsFromFieldMatcher(const FieldMatcher& matcher, const Field& parentField, + std::vector<Field> *allFields) { + Field newParent = parentField; + Field* leaf = getSingleLeaf(&newParent); + leaf->set_field(matcher.field()); + if (matcher.child_size() == 0) { + allFields->push_back(newParent); + return; + } + for (int i = 0; i < matcher.child_size(); ++i) { + leaf->add_child(); + getFieldsFromFieldMatcher(matcher.child(i), newParent, allFields); } +} + +void getFieldsFromFieldMatcher(const FieldMatcher& matcher, std::vector<Field> *allFields) { + Field parentField; + getFieldsFromFieldMatcher(matcher, parentField, allFields); +} - vector<KeyValuePair> dimensionKey = getDimensionKey(event, eventKey); +void flattenValueLeaves(const DimensionsValue& value, + std::vector<DimensionsValue> *allLaves) { + switch (value.value_case()) { + case DimensionsValue::ValueCase::kValueStr: + case DimensionsValue::ValueCase::kValueInt: + case DimensionsValue::ValueCase::kValueLong: + case DimensionsValue::ValueCase::kValueBool: + case DimensionsValue::ValueCase::kValueFloat: + case DimensionsValue::ValueCase::VALUE_NOT_SET: + allLaves->push_back(value); + break; + case DimensionsValue::ValueCase::kValueTuple: + for (int i = 0; i < value.value_tuple().dimensions_value_size(); ++i) { + flattenValueLeaves(value.value_tuple().dimensions_value(i), allLaves); + } + break; + } +} - for (int i = 0; i < link.key_in_what_size(); i++) { - auto& kv = dimensionKey[i]; - kv.set_key(link.key_in_condition(i).key()); +std::vector<HashableDimensionKey> getDimensionKeysForCondition( + const LogEvent& event, const MetricConditionLink& link) { + std::vector<Field> whatFields; + getFieldsFromFieldMatcher(link.dimensions_in_what(), &whatFields); + std::vector<Field> conditionFields; + getFieldsFromFieldMatcher(link.dimensions_in_condition(), &conditionFields); + + std::vector<HashableDimensionKey> hashableDimensionKeys; + + // TODO(yanglu): here we could simplify the logic to get the leaf value node in what and + // directly construct the full condition value tree. + std::vector<DimensionsValue> whatValues = getDimensionKeys(event, link.dimensions_in_what()); + + for (size_t i = 0; i < whatValues.size(); ++i) { + std::vector<DimensionsValue> whatLeaves; + flattenValueLeaves(whatValues[i], &whatLeaves); + if (whatLeaves.size() != whatFields.size() || + whatLeaves.size() != conditionFields.size()) { + ALOGE("Dimensions between what and condition not equal."); + return hashableDimensionKeys; + } + FieldValueMap conditionValueMap; + for (size_t j = 0; j < whatLeaves.size(); ++j) { + if (!setFieldInLeafValueProto(conditionFields[j], &whatLeaves[j])) { + ALOGE("Not able to reset the field for condition leaf value."); + return hashableDimensionKeys; + } + conditionValueMap.insert(std::make_pair(conditionFields[j], whatLeaves[j])); + } + std::vector<DimensionsValue> conditionValues; + findDimensionsValues(conditionValueMap, link.dimensions_in_condition(), &conditionValues); + if (conditionValues.size() != 1) { + ALOGE("Not able to find unambiguous field value in condition atom."); + continue; + } + hashableDimensionKeys.push_back(HashableDimensionKey(conditionValues[0])); } - return HashableDimensionKey(dimensionKey); + return hashableDimensionKeys; } } // namespace statsd diff --git a/cmds/statsd/src/condition/condition_util.h b/cmds/statsd/src/condition/condition_util.h index 934c2076ddfa..598027b7e366 100644 --- a/cmds/statsd/src/condition/condition_util.h +++ b/cmds/statsd/src/condition/condition_util.h @@ -32,12 +32,16 @@ enum ConditionState { kTrue = 1, }; +ConditionState operator|(ConditionState l, ConditionState r); +void OrConditionState(const std::vector<ConditionState>& ref, vector<ConditionState> * ored); +void OrBooleanVector(const std::vector<bool>& ref, vector<bool> * ored); + ConditionState evaluateCombinationCondition(const std::vector<int>& children, const LogicalOperation& operation, const std::vector<ConditionState>& conditionCache); -HashableDimensionKey getDimensionKeyForCondition(const LogEvent& event, - const MetricConditionLink& link); +std::vector<HashableDimensionKey> getDimensionKeysForCondition( + const LogEvent& event, const MetricConditionLink& link); } // namespace statsd } // namespace os } // namespace android diff --git a/cmds/statsd/src/config/ConfigKey.cpp b/cmds/statsd/src/config/ConfigKey.cpp index a365dc0b9189..d791f8632f3c 100644 --- a/cmds/statsd/src/config/ConfigKey.cpp +++ b/cmds/statsd/src/config/ConfigKey.cpp @@ -27,10 +27,10 @@ using std::ostringstream; ConfigKey::ConfigKey() { } -ConfigKey::ConfigKey(const ConfigKey& that) : mName(that.mName), mUid(that.mUid) { +ConfigKey::ConfigKey(const ConfigKey& that) : mId(that.mId), mUid(that.mUid) { } -ConfigKey::ConfigKey(int uid, const string& name) : mName(name), mUid(uid) { +ConfigKey::ConfigKey(int uid, const int64_t& id) : mId(id), mUid(uid) { } ConfigKey::~ConfigKey() { @@ -38,10 +38,21 @@ ConfigKey::~ConfigKey() { string ConfigKey::ToString() const { ostringstream out; - out << '(' << mUid << ',' << mName << ')'; + out << '(' << mUid << ',' << mId << ')'; return out.str(); } + +int64_t StrToInt64(const string& str) { + char* endp; + int64_t value; + value = strtoll(str.c_str(), &endp, 0); + if (endp == str.c_str() || *endp != '\0') { + value = 0; + } + return value; +} + } // namespace statsd } // namespace os } // namespace android diff --git a/cmds/statsd/src/config/ConfigKey.h b/cmds/statsd/src/config/ConfigKey.h index 3489c43c8052..3ad0eed3f2b9 100644 --- a/cmds/statsd/src/config/ConfigKey.h +++ b/cmds/statsd/src/config/ConfigKey.h @@ -37,14 +37,14 @@ class ConfigKey { public: ConfigKey(); explicit ConfigKey(const ConfigKey& that); - ConfigKey(int uid, const string& name); + ConfigKey(int uid, const int64_t& id); ~ConfigKey(); inline int GetUid() const { return mUid; } - inline const string& GetName() const { - return mName; + inline const int64_t& GetId() const { + return mId; } inline bool operator<(const ConfigKey& that) const { @@ -54,17 +54,17 @@ public: if (mUid > that.mUid) { return false; } - return mName < that.mName; + return mId < that.mId; }; inline bool operator==(const ConfigKey& that) const { - return mUid == that.mUid && mName == that.mName; + return mUid == that.mUid && mId == that.mId; }; string ToString() const; private: - string mName; + int64_t mId; int mUid; }; @@ -72,6 +72,8 @@ inline ostream& operator<<(ostream& os, const ConfigKey& config) { return os << config.ToString(); } +int64_t StrToInt64(const string& str); + } // namespace statsd } // namespace os } // namespace android @@ -87,7 +89,7 @@ using android::os::statsd::ConfigKey; template <> struct hash<ConfigKey> { std::size_t operator()(const ConfigKey& key) const { - return (7 * key.GetUid()) ^ ((hash<string>()(key.GetName()))); + return (7 * key.GetUid()) ^ ((hash<long long>()(key.GetId()))); } }; diff --git a/cmds/statsd/src/config/ConfigManager.cpp b/cmds/statsd/src/config/ConfigManager.cpp index cb3f3d634695..5cf8b9b59bbb 100644 --- a/cmds/statsd/src/config/ConfigManager.cpp +++ b/cmds/statsd/src/config/ConfigManager.cpp @@ -103,7 +103,7 @@ void ConfigManager::RemoveConfig(const ConfigKey& key) { } void ConfigManager::remove_saved_configs(const ConfigKey& key) { - string prefix = StringPrintf("%d-%s", key.GetUid(), key.GetName().c_str()); + string prefix = StringPrintf("%d-%lld", key.GetUid(), (long long)key.GetId()); StorageManager::deletePrefixedFiles(STATS_SERVICE_DIR, prefix.c_str()); } @@ -173,7 +173,7 @@ void ConfigManager::Dump(FILE* out) { fprintf(out, "CONFIGURATIONS (%d)\n", (int)mConfigs.size()); fprintf(out, " uid name\n"); for (const auto& key : mConfigs) { - fprintf(out, " %6d %s\n", key.GetUid(), key.GetName().c_str()); + fprintf(out, " %6d %lld\n", key.GetUid(), (long long)key.GetId()); auto receiverIt = mConfigReceivers.find(key); if (receiverIt != mConfigReceivers.end()) { fprintf(out, " -> received by %s, %s\n", receiverIt->second.first.c_str(), @@ -189,8 +189,8 @@ void ConfigManager::update_saved_configs(const ConfigKey& key, const StatsdConfi remove_saved_configs(key); // Then we save the latest config. - string file_name = StringPrintf("%s/%d-%s-%ld", STATS_SERVICE_DIR, key.GetUid(), - key.GetName().c_str(), time(nullptr)); + string file_name = StringPrintf("%s/%d-%lld-%ld", STATS_SERVICE_DIR, key.GetUid(), + (long long)key.GetId(), time(nullptr)); const int numBytes = config.ByteSize(); vector<uint8_t> buffer(numBytes); config.SerializeToArray(&buffer[0], numBytes); @@ -200,7 +200,7 @@ void ConfigManager::update_saved_configs(const ConfigKey& key, const StatsdConfi StatsdConfig build_fake_config() { // HACK: Hard code a test metric for counting screen on events... StatsdConfig config; - config.set_name("CONFIG_12345"); + config.set_id(12345); int WAKE_LOCK_TAG_ID = 1111; // put a fake id here to make testing easier. int WAKE_LOCK_UID_KEY_ID = 1; @@ -209,7 +209,7 @@ StatsdConfig build_fake_config() { int WAKE_LOCK_ACQUIRE_VALUE = 1; int WAKE_LOCK_RELEASE_VALUE = 0; - int APP_USAGE_ID = 12345; + int APP_USAGE_TAG_ID = 12345; int APP_USAGE_UID_KEY_ID = 1; int APP_USAGE_STATE_KEY = 2; int APP_USAGE_FOREGROUND = 1; @@ -232,14 +232,14 @@ StatsdConfig build_fake_config() { // Count Screen ON events. CountMetric* metric = config.add_count_metric(); - metric->set_name("METRIC_1"); - metric->set_what("SCREEN_TURNED_ON"); + metric->set_id(1); // METRIC_1 + metric->set_what(102); // "SCREEN_TURNED_ON" metric->mutable_bucket()->set_bucket_size_millis(30 * 1000L); // Anomaly threshold for screen-on count. // TODO(b/70627390): Uncomment once the bug is fixed. /*Alert* alert = config.add_alert(); - alert->set_name("ALERT_1"); + alert->set_id("ALERT_1"); alert->set_metric_name("METRIC_1"); alert->set_number_of_buckets(6); alert->set_trigger_if_sum_gt(10); @@ -256,17 +256,18 @@ StatsdConfig build_fake_config() { // Count process state changes, slice by uid. metric = config.add_count_metric(); - metric->set_name("METRIC_2"); - metric->set_what("PROCESS_STATE_CHANGE"); + metric->set_id(2); // "METRIC_2" + metric->set_what(104); metric->mutable_bucket()->set_bucket_size_millis(30 * 1000L); - KeyMatcher* keyMatcher = metric->add_dimension(); - keyMatcher->set_key(UID_PROCESS_STATE_UID_KEY); + FieldMatcher* dimensions = metric->mutable_dimensions(); + dimensions->set_field(UID_PROCESS_STATE_TAG_ID); + dimensions->add_child()->set_field(UID_PROCESS_STATE_UID_KEY); // Anomaly threshold for background count. // TODO(b/70627390): Uncomment once the bug is fixed. /* alert = config.add_alert(); - alert->set_name("ALERT_2"); + alert->set_id("ALERT_2"); alert->set_metric_name("METRIC_2"); alert->set_number_of_buckets(4); alert->set_trigger_if_sum_gt(30); @@ -277,79 +278,95 @@ StatsdConfig build_fake_config() { // Count process state changes, slice by uid, while SCREEN_IS_OFF metric = config.add_count_metric(); - metric->set_name("METRIC_3"); - metric->set_what("PROCESS_STATE_CHANGE"); + metric->set_id(3); + metric->set_what(104); metric->mutable_bucket()->set_bucket_size_millis(30 * 1000L); - keyMatcher = metric->add_dimension(); - keyMatcher->set_key(UID_PROCESS_STATE_UID_KEY); - metric->set_condition("SCREEN_IS_OFF"); + + dimensions = metric->mutable_dimensions(); + dimensions->set_field(UID_PROCESS_STATE_TAG_ID); + dimensions->add_child()->set_field(UID_PROCESS_STATE_UID_KEY); + metric->set_condition(202); // Count wake lock, slice by uid, while SCREEN_IS_ON and app in background metric = config.add_count_metric(); - metric->set_name("METRIC_4"); - metric->set_what("APP_GET_WL"); + metric->set_id(4); + metric->set_what(107); metric->mutable_bucket()->set_bucket_size_millis(30 * 1000L); - keyMatcher = metric->add_dimension(); - keyMatcher->set_key(WAKE_LOCK_UID_KEY_ID); - metric->set_condition("APP_IS_BACKGROUND_AND_SCREEN_ON"); + dimensions = metric->mutable_dimensions(); + dimensions->set_field(WAKE_LOCK_TAG_ID); + dimensions->add_child()->set_field(WAKE_LOCK_UID_KEY_ID); + + + metric->set_condition(204); MetricConditionLink* link = metric->add_links(); - link->set_condition("APP_IS_BACKGROUND"); - link->add_key_in_what()->set_key(WAKE_LOCK_UID_KEY_ID); - link->add_key_in_condition()->set_key(APP_USAGE_UID_KEY_ID); + link->set_condition(203); + link->mutable_dimensions_in_what()->set_field(WAKE_LOCK_TAG_ID); + link->mutable_dimensions_in_what()->add_child()->set_field(WAKE_LOCK_UID_KEY_ID); + link->mutable_dimensions_in_condition()->set_field(APP_USAGE_TAG_ID); + link->mutable_dimensions_in_condition()->add_child()->set_field(APP_USAGE_UID_KEY_ID); // Duration of an app holding any wl, while screen on and app in background, slice by uid DurationMetric* durationMetric = config.add_duration_metric(); - durationMetric->set_name("METRIC_5"); + durationMetric->set_id(5); durationMetric->mutable_bucket()->set_bucket_size_millis(30 * 1000L); durationMetric->set_aggregation_type(DurationMetric_AggregationType_SUM); - keyMatcher = durationMetric->add_dimension(); - keyMatcher->set_key(WAKE_LOCK_UID_KEY_ID); - durationMetric->set_what("WL_HELD_PER_APP_PER_NAME"); - durationMetric->set_condition("APP_IS_BACKGROUND_AND_SCREEN_ON"); + dimensions = durationMetric->mutable_dimensions(); + dimensions->set_field(WAKE_LOCK_TAG_ID); + dimensions->add_child()->set_field(WAKE_LOCK_UID_KEY_ID); + durationMetric->set_what(205); + durationMetric->set_condition(204); link = durationMetric->add_links(); - link->set_condition("APP_IS_BACKGROUND"); - link->add_key_in_what()->set_key(WAKE_LOCK_UID_KEY_ID); - link->add_key_in_condition()->set_key(APP_USAGE_UID_KEY_ID); + link->set_condition(203); + link->mutable_dimensions_in_what()->set_field(WAKE_LOCK_TAG_ID); + link->mutable_dimensions_in_what()->add_child()->set_field(WAKE_LOCK_UID_KEY_ID); + link->mutable_dimensions_in_condition()->set_field(APP_USAGE_TAG_ID); + link->mutable_dimensions_in_condition()->add_child()->set_field(APP_USAGE_UID_KEY_ID); // max Duration of an app holding any wl, while screen on and app in background, slice by uid durationMetric = config.add_duration_metric(); - durationMetric->set_name("METRIC_6"); + durationMetric->set_id(6); durationMetric->mutable_bucket()->set_bucket_size_millis(30 * 1000L); durationMetric->set_aggregation_type(DurationMetric_AggregationType_MAX_SPARSE); - keyMatcher = durationMetric->add_dimension(); - keyMatcher->set_key(WAKE_LOCK_UID_KEY_ID); - durationMetric->set_what("WL_HELD_PER_APP_PER_NAME"); - durationMetric->set_condition("APP_IS_BACKGROUND_AND_SCREEN_ON"); + dimensions = durationMetric->mutable_dimensions(); + dimensions->set_field(WAKE_LOCK_TAG_ID); + dimensions->add_child()->set_field(WAKE_LOCK_UID_KEY_ID); + durationMetric->set_what(205); + durationMetric->set_condition(204); link = durationMetric->add_links(); - link->set_condition("APP_IS_BACKGROUND"); - link->add_key_in_what()->set_key(WAKE_LOCK_UID_KEY_ID); - link->add_key_in_condition()->set_key(APP_USAGE_UID_KEY_ID); + link->set_condition(203); + link->mutable_dimensions_in_what()->set_field(WAKE_LOCK_TAG_ID); + link->mutable_dimensions_in_what()->add_child()->set_field(WAKE_LOCK_UID_KEY_ID); + link->mutable_dimensions_in_condition()->set_field(APP_USAGE_TAG_ID); + link->mutable_dimensions_in_condition()->add_child()->set_field(APP_USAGE_UID_KEY_ID); // Duration of an app holding any wl, while screen on and app in background durationMetric = config.add_duration_metric(); - durationMetric->set_name("METRIC_7"); + durationMetric->set_id(7); durationMetric->mutable_bucket()->set_bucket_size_millis(30 * 1000L); durationMetric->set_aggregation_type(DurationMetric_AggregationType_MAX_SPARSE); - durationMetric->set_what("WL_HELD_PER_APP_PER_NAME"); - durationMetric->set_condition("APP_IS_BACKGROUND_AND_SCREEN_ON"); + durationMetric->set_what(205); + durationMetric->set_condition(204); link = durationMetric->add_links(); - link->set_condition("APP_IS_BACKGROUND"); - link->add_key_in_what()->set_key(WAKE_LOCK_UID_KEY_ID); - link->add_key_in_condition()->set_key(APP_USAGE_UID_KEY_ID); + link->set_condition(203); + link->mutable_dimensions_in_what()->set_field(WAKE_LOCK_TAG_ID); + link->mutable_dimensions_in_what()->add_child()->set_field(WAKE_LOCK_UID_KEY_ID); + link->mutable_dimensions_in_condition()->set_field(APP_USAGE_TAG_ID); + link->mutable_dimensions_in_condition()->add_child()->set_field(APP_USAGE_UID_KEY_ID); + // Duration of screen on time. durationMetric = config.add_duration_metric(); - durationMetric->set_name("METRIC_8"); + durationMetric->set_id(8); durationMetric->mutable_bucket()->set_bucket_size_millis(10 * 1000L); durationMetric->set_aggregation_type(DurationMetric_AggregationType_SUM); - durationMetric->set_what("SCREEN_IS_ON"); + durationMetric->set_what(201); // Anomaly threshold for background count. // TODO(b/70627390): Uncomment once the bug is fixed. /* alert = config.add_alert(); - alert->set_name("ALERT_8"); - alert->set_metric_name("METRIC_8"); + alert->set_id(308); + alert->set_metric_id(8); alert->set_number_of_buckets(4); alert->set_trigger_if_sum_gt(2000000000); // 2 seconds alert->set_refractory_period_secs(120); @@ -358,142 +375,147 @@ StatsdConfig build_fake_config() { // Value metric to count KERNEL_WAKELOCK when screen turned on ValueMetric* valueMetric = config.add_value_metric(); - valueMetric->set_name("METRIC_6"); - valueMetric->set_what("KERNEL_WAKELOCK"); + valueMetric->set_id(11); + valueMetric->set_what(109); valueMetric->set_value_field(KERNEL_WAKELOCK_COUNT_KEY); - valueMetric->set_condition("SCREEN_IS_ON"); - keyMatcher = valueMetric->add_dimension(); - keyMatcher->set_key(KERNEL_WAKELOCK_NAME_KEY); + valueMetric->set_condition(201); + dimensions = valueMetric->mutable_dimensions(); + dimensions->set_field(KERNEL_WAKELOCK_TAG_ID); + dimensions->add_child()->set_field(KERNEL_WAKELOCK_NAME_KEY); // This is for testing easier. We should never set bucket size this small. valueMetric->mutable_bucket()->set_bucket_size_millis(60 * 1000L); // Add an EventMetric to log process state change events. EventMetric* eventMetric = config.add_event_metric(); - eventMetric->set_name("METRIC_9"); - eventMetric->set_what("SCREEN_TURNED_ON"); + eventMetric->set_id(9); + eventMetric->set_what(102); // "SCREEN_TURNED_ON" // Add an GaugeMetric. GaugeMetric* gaugeMetric = config.add_gauge_metric(); - gaugeMetric->set_name("METRIC_10"); - gaugeMetric->set_what("DEVICE_TEMPERATURE"); - gaugeMetric->mutable_gauge_fields()->add_field_num(DEVICE_TEMPERATURE_KEY); + gaugeMetric->set_id(10); + gaugeMetric->set_what(101); + auto gaugeFieldMatcher = gaugeMetric->mutable_gauge_fields_filter()->mutable_fields(); + gaugeFieldMatcher->set_field(DEVICE_TEMPERATURE_TAG_ID); + gaugeFieldMatcher->add_child()->set_field(DEVICE_TEMPERATURE_KEY); gaugeMetric->mutable_bucket()->set_bucket_size_millis(60 * 1000L); - // Event matchers............ + // Event matchers. AtomMatcher* temperatureAtomMatcher = config.add_atom_matcher(); - temperatureAtomMatcher->set_name("DEVICE_TEMPERATURE"); - temperatureAtomMatcher->mutable_simple_atom_matcher()->set_tag( + temperatureAtomMatcher->set_id(101); // "DEVICE_TEMPERATURE" + temperatureAtomMatcher->mutable_simple_atom_matcher()->set_atom_id( DEVICE_TEMPERATURE_TAG_ID); AtomMatcher* eventMatcher = config.add_atom_matcher(); - eventMatcher->set_name("SCREEN_TURNED_ON"); + eventMatcher->set_id(102); // "SCREEN_TURNED_ON" SimpleAtomMatcher* simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher(); - simpleAtomMatcher->set_tag(SCREEN_EVENT_TAG_ID); - KeyValueMatcher* keyValueMatcher = simpleAtomMatcher->add_key_value_matcher(); - keyValueMatcher->mutable_key_matcher()->set_key(SCREEN_EVENT_STATE_KEY); - keyValueMatcher->set_eq_int(SCREEN_EVENT_ON_VALUE); + simpleAtomMatcher->set_atom_id(SCREEN_EVENT_TAG_ID); + FieldValueMatcher* fieldValueMatcher = simpleAtomMatcher->add_field_value_matcher(); + fieldValueMatcher->set_field(SCREEN_EVENT_STATE_KEY); + fieldValueMatcher->set_eq_int(SCREEN_EVENT_ON_VALUE); eventMatcher = config.add_atom_matcher(); - eventMatcher->set_name("SCREEN_TURNED_OFF"); + eventMatcher->set_id(103); // "SCREEN_TURNED_OFF" simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher(); - simpleAtomMatcher->set_tag(SCREEN_EVENT_TAG_ID); - keyValueMatcher = simpleAtomMatcher->add_key_value_matcher(); - keyValueMatcher->mutable_key_matcher()->set_key(SCREEN_EVENT_STATE_KEY); - keyValueMatcher->set_eq_int(SCREEN_EVENT_OFF_VALUE); + simpleAtomMatcher->set_atom_id(SCREEN_EVENT_TAG_ID); + fieldValueMatcher = simpleAtomMatcher->add_field_value_matcher(); + fieldValueMatcher->set_field(SCREEN_EVENT_STATE_KEY); + fieldValueMatcher->set_eq_int(SCREEN_EVENT_OFF_VALUE); eventMatcher = config.add_atom_matcher(); - eventMatcher->set_name("PROCESS_STATE_CHANGE"); + eventMatcher->set_id(104); // "PROCESS_STATE_CHANGE" simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher(); - simpleAtomMatcher->set_tag(UID_PROCESS_STATE_TAG_ID); + simpleAtomMatcher->set_atom_id(UID_PROCESS_STATE_TAG_ID); eventMatcher = config.add_atom_matcher(); - eventMatcher->set_name("APP_GOES_BACKGROUND"); + eventMatcher->set_id(105); // "APP_GOES_BACKGROUND" simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher(); - simpleAtomMatcher->set_tag(APP_USAGE_ID); - keyValueMatcher = simpleAtomMatcher->add_key_value_matcher(); - keyValueMatcher->mutable_key_matcher()->set_key(APP_USAGE_STATE_KEY); - keyValueMatcher->set_eq_int(APP_USAGE_BACKGROUND); + simpleAtomMatcher->set_atom_id(APP_USAGE_TAG_ID); + fieldValueMatcher = simpleAtomMatcher->add_field_value_matcher(); + fieldValueMatcher->set_field(APP_USAGE_STATE_KEY); + fieldValueMatcher->set_eq_int(APP_USAGE_BACKGROUND); eventMatcher = config.add_atom_matcher(); - eventMatcher->set_name("APP_GOES_FOREGROUND"); + eventMatcher->set_id(106); // "APP_GOES_FOREGROUND" simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher(); - simpleAtomMatcher->set_tag(APP_USAGE_ID); - keyValueMatcher = simpleAtomMatcher->add_key_value_matcher(); - keyValueMatcher->mutable_key_matcher()->set_key(APP_USAGE_STATE_KEY); - keyValueMatcher->set_eq_int(APP_USAGE_FOREGROUND); + simpleAtomMatcher->set_atom_id(APP_USAGE_TAG_ID); + fieldValueMatcher = simpleAtomMatcher->add_field_value_matcher(); + fieldValueMatcher->set_field(APP_USAGE_STATE_KEY); + fieldValueMatcher->set_eq_int(APP_USAGE_FOREGROUND); eventMatcher = config.add_atom_matcher(); - eventMatcher->set_name("APP_GET_WL"); + eventMatcher->set_id(107); // "APP_GET_WL" simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher(); - simpleAtomMatcher->set_tag(WAKE_LOCK_TAG_ID); - keyValueMatcher = simpleAtomMatcher->add_key_value_matcher(); - keyValueMatcher->mutable_key_matcher()->set_key(WAKE_LOCK_STATE_KEY); - keyValueMatcher->set_eq_int(WAKE_LOCK_ACQUIRE_VALUE); + simpleAtomMatcher->set_atom_id(WAKE_LOCK_TAG_ID); + fieldValueMatcher = simpleAtomMatcher->add_field_value_matcher(); + fieldValueMatcher->set_field(WAKE_LOCK_STATE_KEY); + fieldValueMatcher->set_eq_int(WAKE_LOCK_ACQUIRE_VALUE); eventMatcher = config.add_atom_matcher(); - eventMatcher->set_name("APP_RELEASE_WL"); + eventMatcher->set_id(108); //"APP_RELEASE_WL" simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher(); - simpleAtomMatcher->set_tag(WAKE_LOCK_TAG_ID); - keyValueMatcher = simpleAtomMatcher->add_key_value_matcher(); - keyValueMatcher->mutable_key_matcher()->set_key(WAKE_LOCK_STATE_KEY); - keyValueMatcher->set_eq_int(WAKE_LOCK_RELEASE_VALUE); + simpleAtomMatcher->set_atom_id(WAKE_LOCK_TAG_ID); + fieldValueMatcher = simpleAtomMatcher->add_field_value_matcher(); + fieldValueMatcher->set_field(WAKE_LOCK_STATE_KEY); + fieldValueMatcher->set_eq_int(WAKE_LOCK_RELEASE_VALUE); // pulled events eventMatcher = config.add_atom_matcher(); - eventMatcher->set_name("KERNEL_WAKELOCK"); + eventMatcher->set_id(109); // "KERNEL_WAKELOCK" simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher(); - simpleAtomMatcher->set_tag(KERNEL_WAKELOCK_TAG_ID); + simpleAtomMatcher->set_atom_id(KERNEL_WAKELOCK_TAG_ID); // Predicates............. Predicate* predicate = config.add_predicate(); - predicate->set_name("SCREEN_IS_ON"); + predicate->set_id(201); // "SCREEN_IS_ON" SimplePredicate* simplePredicate = predicate->mutable_simple_predicate(); - simplePredicate->set_start("SCREEN_TURNED_ON"); - simplePredicate->set_stop("SCREEN_TURNED_OFF"); + simplePredicate->set_start(102); // "SCREEN_TURNED_ON" + simplePredicate->set_stop(103); simplePredicate->set_count_nesting(false); predicate = config.add_predicate(); - predicate->set_name("SCREEN_IS_OFF"); + predicate->set_id(202); // "SCREEN_IS_OFF" simplePredicate = predicate->mutable_simple_predicate(); - simplePredicate->set_start("SCREEN_TURNED_OFF"); - simplePredicate->set_stop("SCREEN_TURNED_ON"); + simplePredicate->set_start(103); + simplePredicate->set_stop(102); // "SCREEN_TURNED_ON" simplePredicate->set_count_nesting(false); predicate = config.add_predicate(); - predicate->set_name("APP_IS_BACKGROUND"); + predicate->set_id(203); // "APP_IS_BACKGROUND" simplePredicate = predicate->mutable_simple_predicate(); - simplePredicate->set_start("APP_GOES_BACKGROUND"); - simplePredicate->set_stop("APP_GOES_FOREGROUND"); - KeyMatcher* predicate_dimension1 = simplePredicate->add_dimension(); - predicate_dimension1->set_key(APP_USAGE_UID_KEY_ID); + simplePredicate->set_start(105); + simplePredicate->set_stop(106); + FieldMatcher* predicate_dimension1 = simplePredicate->mutable_dimensions(); + predicate_dimension1->set_field(APP_USAGE_TAG_ID); + predicate_dimension1->add_child()->set_field(APP_USAGE_UID_KEY_ID); simplePredicate->set_count_nesting(false); predicate = config.add_predicate(); - predicate->set_name("APP_IS_BACKGROUND_AND_SCREEN_ON"); + predicate->set_id(204); // "APP_IS_BACKGROUND_AND_SCREEN_ON" Predicate_Combination* combination_predicate = predicate->mutable_combination(); combination_predicate->set_operation(LogicalOperation::AND); - combination_predicate->add_predicate("APP_IS_BACKGROUND"); - combination_predicate->add_predicate("SCREEN_IS_ON"); + combination_predicate->add_predicate(203); + combination_predicate->add_predicate(201); predicate = config.add_predicate(); - predicate->set_name("WL_HELD_PER_APP_PER_NAME"); + predicate->set_id(205); // "WL_HELD_PER_APP_PER_NAME" simplePredicate = predicate->mutable_simple_predicate(); - simplePredicate->set_start("APP_GET_WL"); - simplePredicate->set_stop("APP_RELEASE_WL"); - KeyMatcher* predicate_dimension = simplePredicate->add_dimension(); - predicate_dimension->set_key(WAKE_LOCK_UID_KEY_ID); - predicate_dimension = simplePredicate->add_dimension(); - predicate_dimension->set_key(WAKE_LOCK_NAME_KEY); + simplePredicate->set_start(107); + simplePredicate->set_stop(108); + FieldMatcher* predicate_dimension = simplePredicate->mutable_dimensions(); + predicate_dimension1->set_field(WAKE_LOCK_TAG_ID); + predicate_dimension->add_child()->set_field(WAKE_LOCK_UID_KEY_ID); + predicate_dimension->add_child()->set_field(WAKE_LOCK_NAME_KEY); simplePredicate->set_count_nesting(true); predicate = config.add_predicate(); - predicate->set_name("WL_HELD_PER_APP"); + predicate->set_id(206); // "WL_HELD_PER_APP" simplePredicate = predicate->mutable_simple_predicate(); - simplePredicate->set_start("APP_GET_WL"); - simplePredicate->set_stop("APP_RELEASE_WL"); + simplePredicate->set_start(107); + simplePredicate->set_stop(108); simplePredicate->set_initial_value(SimplePredicate_InitialValue_FALSE); - predicate_dimension = simplePredicate->add_dimension(); - predicate_dimension->set_key(WAKE_LOCK_UID_KEY_ID); + predicate_dimension = simplePredicate->mutable_dimensions(); + predicate_dimension->set_field(WAKE_LOCK_TAG_ID); + predicate_dimension->add_child()->set_field(WAKE_LOCK_UID_KEY_ID); simplePredicate->set_count_nesting(true); return config; diff --git a/cmds/statsd/src/dimension.cpp b/cmds/statsd/src/dimension.cpp new file mode 100644 index 000000000000..886a33bf540f --- /dev/null +++ b/cmds/statsd/src/dimension.cpp @@ -0,0 +1,356 @@ +/* + * Copyright (C) 2017 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. + */ + +#include "Log.h" + +#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h" +#include "dimension.h" +#include "field_util.h" + + +namespace android { +namespace os { +namespace statsd { + +const DimensionsValue* getSingleLeafValue(const DimensionsValue* value) { + if (value->value_case() == DimensionsValue::ValueCase::kValueTuple) { + return getSingleLeafValue(&value->value_tuple().dimensions_value(0)); + } else { + return value; + } +} + +DimensionsValue getSingleLeafValue(const DimensionsValue& value) { + const DimensionsValue* leafValue = getSingleLeafValue(&value); + return *leafValue; +} + +void appendLeafNodeToParent(const Field& field, + const DimensionsValue& value, + DimensionsValue* parentValue) { + if (field.child_size() <= 0) { + *parentValue = value; + parentValue->set_field(field.field()); + return; + } + parentValue->set_field(field.field()); + int idx = -1; + for (int i = 0; i < parentValue->mutable_value_tuple()->dimensions_value_size(); ++i) { + if (parentValue->mutable_value_tuple()->dimensions_value(i).field() == + field.child(0).field()) { + idx = i; + } + } + if (idx < 0) { + parentValue->mutable_value_tuple()->add_dimensions_value(); + idx = parentValue->mutable_value_tuple()->dimensions_value_size() - 1; + } + appendLeafNodeToParent( + field.child(0), value, + parentValue->mutable_value_tuple()->mutable_dimensions_value(idx)); +} + +void addNodeToRootDimensionsValues(const Field& field, + const DimensionsValue& node, + std::vector<DimensionsValue>* rootValues) { + if (rootValues == nullptr) { + return; + } + if (rootValues->empty()) { + DimensionsValue rootValue; + appendLeafNodeToParent(field, node, &rootValue); + rootValues->push_back(rootValue); + } else { + for (size_t i = 0; i < rootValues->size(); ++i) { + appendLeafNodeToParent(field, node, &rootValues->at(i)); + } + } +} + +namespace { + +void findDimensionsValues( + const FieldValueMap& fieldValueMap, + const FieldMatcher& matcher, + const Field& field, + std::vector<DimensionsValue>* rootDimensionsValues); + +void findNonRepeatedDimensionsValues( + const FieldValueMap& fieldValueMap, + const FieldMatcher& matcher, + const Field& field, + std::vector<DimensionsValue>* rootValues) { + if (matcher.child_size() > 0) { + for (const auto& childMatcher : matcher.child()) { + Field childField = field; + appendLeaf(&childField, childMatcher.field()); + findDimensionsValues(fieldValueMap, childMatcher, childField, rootValues); + } + } else { + auto ret = fieldValueMap.equal_range(field); + int found = 0; + for (auto it = ret.first; it != ret.second; ++it) { + found++; + } + // Not found. + if (found <= 0) { + return; + } + if (found > 1) { + ALOGE("Found multiple values for optional field."); + return; + } + addNodeToRootDimensionsValues(field, ret.first->second, rootValues); + } +} + +void findRepeatedDimensionsValues(const FieldValueMap& fieldValueMap, + const FieldMatcher& matcher, + const Field& field, + std::vector<DimensionsValue>* rootValues) { + if (matcher.position() == Position::FIRST) { + Field first_field = field; + setPositionForLeaf(&first_field, 0); + findNonRepeatedDimensionsValues(fieldValueMap, matcher, first_field, rootValues); + } else { + auto itLower = fieldValueMap.lower_bound(field); + if (itLower == fieldValueMap.end()) { + return; + } + Field next_field = field; + getNextField(&next_field); + auto itUpper = fieldValueMap.lower_bound(next_field); + + switch (matcher.position()) { + case Position::LAST: + { + itUpper--; + if (itUpper != fieldValueMap.end()) { + Field last_field = field; + int last_index = getPositionByReferenceField(field, itUpper->first); + if (last_index < 0) { + return; + } + setPositionForLeaf(&last_field, last_index); + findNonRepeatedDimensionsValues( + fieldValueMap, matcher, last_field, rootValues); + } + } + break; + case Position::ANY: + { + std::set<int> indexes; + for (auto it = itLower; it != itUpper; ++it) { + int index = getPositionByReferenceField(field, it->first); + if (index >= 0) { + indexes.insert(index); + } + } + if (!indexes.empty()) { + Field any_field = field; + std::vector<DimensionsValue> allValues; + for (const int index : indexes) { + setPositionForLeaf(&any_field, index); + std::vector<DimensionsValue> newValues = *rootValues; + findNonRepeatedDimensionsValues( + fieldValueMap, matcher, any_field, &newValues); + allValues.insert(allValues.end(), newValues.begin(), newValues.end()); + } + rootValues->clear(); + rootValues->insert(rootValues->end(), allValues.begin(), allValues.end()); + } + } + break; + default: + break; + } + } +} + +void findDimensionsValues( + const FieldValueMap& fieldValueMap, + const FieldMatcher& matcher, + const Field& field, + std::vector<DimensionsValue>* rootDimensionsValues) { + if (!matcher.has_position()) { + findNonRepeatedDimensionsValues(fieldValueMap, matcher, field, rootDimensionsValues); + } else { + findRepeatedDimensionsValues(fieldValueMap, matcher, field, rootDimensionsValues); + } +} + +} // namespace + +void findDimensionsValues( + const FieldValueMap& fieldValueMap, + const FieldMatcher& matcher, + std::vector<DimensionsValue>* rootDimensionsValues) { + findDimensionsValues(fieldValueMap, matcher, + buildSimpleAtomField(matcher.field()), rootDimensionsValues); +} + +FieldMatcher buildSimpleAtomFieldMatcher(const int tagId) { + FieldMatcher matcher; + matcher.set_field(tagId); + return matcher; +} + +FieldMatcher buildSimpleAtomFieldMatcher(const int tagId, const int atomFieldNum) { + FieldMatcher matcher; + matcher.set_field(tagId); + matcher.add_child()->set_field(atomFieldNum); + return matcher; +} + +constexpr int ATTRIBUTION_FIELD_NUM_IN_ATOM_PROTO = 1; +constexpr int UID_FIELD_NUM_IN_ATTRIBUTION_NODE_PROTO = 1; +constexpr int TAG_FIELD_NUM_IN_ATTRIBUTION_NODE_PROTO = 2; + +void buildAttributionUidFieldMatcher(const int tagId, const Position position, + FieldMatcher *matcher) { + matcher->set_field(tagId); + matcher->add_child()->set_field(ATTRIBUTION_FIELD_NUM_IN_ATOM_PROTO); + FieldMatcher* child = matcher->mutable_child(0)->add_child(); + child->set_field(UID_FIELD_NUM_IN_ATTRIBUTION_NODE_PROTO); +} + +void buildAttributionTagFieldMatcher(const int tagId, const Position position, + FieldMatcher *matcher) { + matcher->set_field(tagId); + FieldMatcher* child = matcher->add_child(); + child->set_field(ATTRIBUTION_FIELD_NUM_IN_ATOM_PROTO); + child->set_position(position); + child = child->add_child(); + child->set_field(TAG_FIELD_NUM_IN_ATTRIBUTION_NODE_PROTO); +} + +void buildAttributionFieldMatcher(const int tagId, const Position position, + FieldMatcher *matcher) { + matcher->set_field(tagId); + FieldMatcher* child = matcher->add_child(); + child->set_field(ATTRIBUTION_FIELD_NUM_IN_ATOM_PROTO); + child->set_position(position); + child = child->add_child(); + child->set_field(UID_FIELD_NUM_IN_ATTRIBUTION_NODE_PROTO); + child->set_field(TAG_FIELD_NUM_IN_ATTRIBUTION_NODE_PROTO); +} + +void DimensionsValueToString(const DimensionsValue& value, std::string *flattened) { + *flattened += std::to_string(value.field()); + *flattened += ":"; + switch (value.value_case()) { + case DimensionsValue::ValueCase::kValueStr: + *flattened += value.value_str(); + break; + case DimensionsValue::ValueCase::kValueInt: + *flattened += std::to_string(value.value_int()); + break; + case DimensionsValue::ValueCase::kValueLong: + *flattened += std::to_string(value.value_long()); + break; + case DimensionsValue::ValueCase::kValueBool: + *flattened += std::to_string(value.value_bool()); + break; + case DimensionsValue::ValueCase::kValueFloat: + *flattened += std::to_string(value.value_float()); + break; + case DimensionsValue::ValueCase::kValueTuple: + { + *flattened += "{"; + for (int i = 0; i < value.value_tuple().dimensions_value_size(); ++i) { + DimensionsValueToString(value.value_tuple().dimensions_value(i), flattened); + *flattened += "|"; + } + *flattened += "}"; + } + break; + case DimensionsValue::ValueCase::VALUE_NOT_SET: + break; + } +} + +void getDimensionsValueLeafNodes( + const DimensionsValue& value, std::vector<DimensionsValue> *leafNodes) { + switch (value.value_case()) { + case DimensionsValue::ValueCase::kValueStr: + case DimensionsValue::ValueCase::kValueInt: + case DimensionsValue::ValueCase::kValueLong: + case DimensionsValue::ValueCase::kValueBool: + case DimensionsValue::ValueCase::kValueFloat: + leafNodes->push_back(value); + break; + case DimensionsValue::ValueCase::kValueTuple: + for (int i = 0; i < value.value_tuple().dimensions_value_size(); ++i) { + getDimensionsValueLeafNodes(value.value_tuple().dimensions_value(i), leafNodes); + } + break; + case DimensionsValue::ValueCase::VALUE_NOT_SET: + break; + default: + break; + } +} + +std::string DimensionsValueToString(const DimensionsValue& value) { + std::string flatten; + DimensionsValueToString(value, &flatten); + return flatten; +} + +bool IsSubDimension(const DimensionsValue& dimension, const DimensionsValue& sub) { + if (dimension.field() != sub.field()) { + return false; + } + if (dimension.value_case() != sub.value_case()) { + return false; + } + switch (dimension.value_case()) { + case DimensionsValue::ValueCase::kValueStr: + return dimension.value_str() == sub.value_str(); + case DimensionsValue::ValueCase::kValueInt: + return dimension.value_int() == sub.value_int(); + case DimensionsValue::ValueCase::kValueLong: + return dimension.value_long() == sub.value_long(); + case DimensionsValue::ValueCase::kValueBool: + return dimension.value_bool() == sub.value_bool(); + case DimensionsValue::ValueCase::kValueFloat: + return dimension.value_float() == sub.value_float(); + case DimensionsValue::ValueCase::kValueTuple: { + if (dimension.value_tuple().dimensions_value_size() < sub.value_tuple().dimensions_value_size()) { + return false; + } + bool allSub = true; + for (int i = 0; i < sub.value_tuple().dimensions_value_size(); ++i) { + bool isSub = false; + for (int j = 0; !isSub && j < dimension.value_tuple().dimensions_value_size(); ++j) { + isSub |= IsSubDimension(dimension.value_tuple().dimensions_value(j), + sub.value_tuple().dimensions_value(i)); + } + allSub &= isSub; + } + return allSub; + } + break; + case DimensionsValue::ValueCase::VALUE_NOT_SET: + return false; + default: + return false; + } +} + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/cmds/statsd/src/dimension.h b/cmds/statsd/src/dimension.h new file mode 100644 index 000000000000..c866958fdaae --- /dev/null +++ b/cmds/statsd/src/dimension.h @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2017 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. + */ + +#pragma once + +#include <log/logprint.h> +#include <set> +#include <vector> +#include "frameworks/base/cmds/statsd/src/stats_log.pb.h" +#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h" +#include "field_util.h" + +namespace android { +namespace os { +namespace statsd { + + +// Returns the leaf node from the DimensionsValue proto. It assume that the input has only one +// leaf node at most. +const DimensionsValue* getSingleLeafValue(const DimensionsValue* value); +DimensionsValue getSingleLeafValue(const DimensionsValue& value); + +// Constructs the DimensionsValue protos from the FieldMatcher. Each DimensionsValue proto +// represents a tree. When the input proto has repeated fields and the input "dimensions" wants +// "ANY" locations, it will return multiple trees. +void findDimensionsValues( + const FieldValueMap& fieldValueMap, + const FieldMatcher& matcher, + std::vector<DimensionsValue>* rootDimensionsValues); + +// Utils to build FieldMatcher proto for simple one-depth atoms. +FieldMatcher buildSimpleAtomFieldMatcher(const int tagId, const int atomFieldNum); +FieldMatcher buildSimpleAtomFieldMatcher(const int tagId); + +// Utils to build FieldMatcher proto for attribution nodes. +FieldMatcher buildAttributionUidFieldMatcher(const int tagId, const Position position); +FieldMatcher buildAttributionTagFieldMatcher(const int tagId, const Position position); +FieldMatcher buildAttributionFieldMatcher(const int tagId, const Position position); + +// Utils to print pretty string for DimensionsValue proto. +std::string DimensionsValueToString(const DimensionsValue& value); +void DimensionsValueToString(const DimensionsValue& value, std::string *flattened); + +bool IsSubDimension(const DimensionsValue& dimension, const DimensionsValue& sub); + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/cmds/statsd/src/field_util.cpp b/cmds/statsd/src/field_util.cpp new file mode 100644 index 000000000000..d10e1670ddf6 --- /dev/null +++ b/cmds/statsd/src/field_util.cpp @@ -0,0 +1,318 @@ +/* + * Copyright (C) 2017 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. + */ + +#include "Log.h" +#include "field_util.h" + +#include <set> +#include <vector> + +namespace android { +namespace os { +namespace statsd { + +// This function is to compare two Field trees where each node has at most one child. +bool CompareField(const Field& a, const Field& b) { + if (a.field() < b.field()) { + return true; + } + if (a.field() > b.field()) { + return false; + } + if (a.position_index() < b.position_index()) { + return true; + } + if (a.position_index() > b.position_index()) { + return false; + } + if (a.child_size() < b.child_size()) { + return true; + } + if (a.child_size() > b.child_size()) { + return false; + } + if (a.child_size() == 0 && b.child_size() == 0) { + return false; + } + return CompareField(a.child(0), b.child(0)); +} + +const Field* getSingleLeaf(const Field* field) { + if (field->child_size() <= 0) { + return field; + } else { + return getSingleLeaf(&field->child(0)); + } +} + +Field* getSingleLeaf(Field* field) { + if (field->child_size() <= 0) { + return field; + } else { + return getSingleLeaf(field->mutable_child(0)); + } +} + +void FieldToString(const Field& field, std::string *flattened) { + *flattened += std::to_string(field.field()); + if (field.has_position_index()) { + *flattened += "["; + *flattened += std::to_string(field.position_index()); + *flattened += "]"; + } + if (field.child_size() <= 0) { + return; + } + *flattened += "."; + *flattened += "{"; + for (int i = 0 ; i < field.child_size(); ++i) { + *flattened += FieldToString(field.child(i)); + } + *flattened += "},"; +} + +std::string FieldToString(const Field& field) { + std::string flatten; + FieldToString(field, &flatten); + return flatten; +} + +bool setFieldInLeafValueProto(const Field &field, DimensionsValue* leafValue) { + if (field.child_size() <= 0) { + leafValue->set_field(field.field()); + return true; + } else if (field.child_size() == 1) { + return setFieldInLeafValueProto(field.child(0), leafValue); + } else { + ALOGE("Not able to set the 'field' in leaf value for multiple children."); + return false; + } +} + +Field buildAtomField(const int tagId, const Field &atomField) { + Field field; + *field.add_child() = atomField; + field.set_field(tagId); + return field; +} + +Field buildSimpleAtomField(const int tagId, const int atomFieldNum) { + Field field; + field.set_field(tagId); + field.add_child()->set_field(atomFieldNum); + return field; +} + +Field buildSimpleAtomField(const int tagId) { + Field field; + field.set_field(tagId); + return field; +} + +void appendLeaf(Field *parent, int node_field_num) { + if (!parent->has_field()) { + parent->set_field(node_field_num); + } else if (parent->child_size() <= 0) { + parent->add_child()->set_field(node_field_num); + } else { + appendLeaf(parent->mutable_child(0), node_field_num); + } +} + +void appendLeaf(Field *parent, int node_field_num, int position) { + if (!parent->has_field()) { + parent->set_field(node_field_num); + parent->set_position_index(position); + } else if (parent->child_size() <= 0) { + auto child = parent->add_child(); + child->set_field(node_field_num); + child->set_position_index(position); + } else { + appendLeaf(parent->mutable_child(0), node_field_num, position); + } +} + + +void getNextField(Field* field) { + if (field->child_size() <= 0) { + field->set_field(field->field() + 1); + return; + } + if (field->child_size() != 1) { + return; + } + getNextField(field->mutable_child(0)); +} + +void increasePosition(Field *field) { + if (!field->has_position_index()) { + field->set_position_index(0); + } else { + field->set_position_index(field->position_index() + 1); + } +} + +int getPositionByReferenceField(const Field& ref, const Field& field_with_index) { + if (ref.child_size() <= 0) { + return field_with_index.position_index(); + } + if (ref.child_size() != 1 || + field_with_index.child_size() != 1) { + return -1; + } + return getPositionByReferenceField(ref.child(0), field_with_index.child(0)); +} + +void setPositionForLeaf(Field *field, int index) { + if (field->child_size() <= 0) { + field->set_position_index(index); + } else { + setPositionForLeaf(field->mutable_child(0), index); + } +} + +void findFields( + const FieldValueMap& fieldValueMap, + const FieldMatcher& matcher, + const Field& field, + std::vector<Field>* rootFields); + +void findNonRepeatedFields( + const FieldValueMap& fieldValueMap, + const FieldMatcher& matcher, + const Field& field, + std::vector<Field>* rootFields) { + if (matcher.child_size() > 0) { + for (const auto& childMatcher : matcher.child()) { + Field childField = field; + appendLeaf(&childField, childMatcher.field()); + findFields(fieldValueMap, childMatcher, childField, rootFields); + } + } else { + auto ret = fieldValueMap.equal_range(field); + int found = 0; + for (auto it = ret.first; it != ret.second; ++it) { + found++; + } + // Not found. + if (found <= 0) { + return; + } + if (found > 1) { + ALOGE("Found multiple values for optional field."); + return; + } + rootFields->push_back(ret.first->first); + } +} + +void findRepeatedFields(const FieldValueMap& fieldValueMap, const FieldMatcher& matcher, + const Field& field, std::vector<Field>* rootFields) { + if (matcher.position() == Position::FIRST) { + Field first_field = field; + setPositionForLeaf(&first_field, 0); + findNonRepeatedFields(fieldValueMap, matcher, first_field, rootFields); + } else { + auto itLower = fieldValueMap.lower_bound(field); + if (itLower == fieldValueMap.end()) { + return; + } + Field next_field = field; + getNextField(&next_field); + auto itUpper = fieldValueMap.lower_bound(next_field); + + switch (matcher.position()) { + case Position::LAST: + { + itUpper--; + if (itUpper != fieldValueMap.end()) { + Field last_field = field; + int last_index = getPositionByReferenceField(field, itUpper->first); + if (last_index < 0) { + return; + } + setPositionForLeaf(&last_field, last_index); + findNonRepeatedFields( + fieldValueMap, matcher, last_field, rootFields); + } + } + break; + case Position::ANY: + { + std::set<int> indexes; + for (auto it = itLower; it != itUpper; ++it) { + int index = getPositionByReferenceField(field, it->first); + if (index >= 0) { + indexes.insert(index); + } + } + if (!indexes.empty()) { + Field any_field = field; + for (const int index : indexes) { + setPositionForLeaf(&any_field, index); + findNonRepeatedFields( + fieldValueMap, matcher, any_field, rootFields); + } + } + } + break; + default: + break; + } + } +} + +void findFields( + const FieldValueMap& fieldValueMap, + const FieldMatcher& matcher, + const Field& field, + std::vector<Field>* rootFields) { + if (!matcher.has_position()) { + findNonRepeatedFields(fieldValueMap, matcher, field, rootFields); + } else { + findRepeatedFields(fieldValueMap, matcher, field, rootFields); + } +} + +void filterFields(const FieldMatcher& matcher, FieldValueMap* fieldValueMap) { + std::vector<Field> rootFields; + findFields(*fieldValueMap, matcher, buildSimpleAtomField(matcher.field()), &rootFields); + std::set<Field, FieldCmp> rootFieldSet(rootFields.begin(), rootFields.end()); + auto it = fieldValueMap->begin(); + while (it != fieldValueMap->end()) { + if (rootFieldSet.find(it->first) == rootFieldSet.end()) { + it = fieldValueMap->erase(it); + } else { + it++; + } + } +} + +bool hasLeafNode(const FieldMatcher& matcher) { + if (!matcher.has_field()) { + return false; + } + for (int i = 0; i < matcher.child_size(); ++i) { + if (hasLeafNode(matcher.child(i))) { + return true; + } + } + return true; +} + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/cmds/statsd/src/field_util.h b/cmds/statsd/src/field_util.h new file mode 100644 index 000000000000..5907e17d666b --- /dev/null +++ b/cmds/statsd/src/field_util.h @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2017 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. + */ + +#pragma once + +#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h" +#include "frameworks/base/cmds/statsd/src/stats_log.pb.h" + +#include <unordered_map> + +namespace android { +namespace os { +namespace statsd { + +// Function to sort the Field protos. +bool CompareField(const Field& a, const Field& b); +struct FieldCmp { + bool operator()(const Field& a, const Field& b) const { + return CompareField(a, b); + } +}; + +// Flattened dimensions value map. To save space, usually the key contains the tree structure info +// and value field is only leaf node. +typedef std::map<Field, DimensionsValue, FieldCmp> FieldValueMap; + +// Util function to print the Field proto. +std::string FieldToString(const Field& field); + +// Util function to find the leaf node from the input Field proto and set it in the corresponding +// value proto. +bool setFieldInLeafValueProto(const Field &field, DimensionsValue* leafValue); + +// Returns the leaf node from the Field proto. It assume that the input has only one +// leaf node at most. +const Field* getSingleLeaf(const Field* field); +Field* getSingleLeaf(Field* field); + +// Append a node to the current leaf. It assumes that the input "parent" has one leaf node at most. +void appendLeaf(Field *parent, int node_field_num); +void appendLeaf(Field *parent, int node_field_num, int position); + +// Given the field sorting logic, this function is to increase the "field" at the leaf node. +void getNextField(Field* field); + +// Increase the position index for the node. If the "position_index" is not set, set it as 0. +void increasePosition(Field *field); + +// Finds the leaf node and set the index there. +void setPositionForLeaf(Field *field, int index); + +// Returns true if the matcher has specified at least one leaf node. +bool hasLeafNode(const FieldMatcher& matcher); + +// The two input Field proto are describing the same tree structure. Both contain one leaf node at +// most. This is find the position index info for the leaf node at "reference" stored in the +// "field_with_index" tree. +int getPositionByReferenceField(const Field& reference, const Field& field_with_index); + +// Utils to build the Field proto for simple atom fields. +Field buildAtomField(const int tagId, const Field &atomField); +Field buildSimpleAtomField(const int tagId, const int atomFieldNum); +Field buildSimpleAtomField(const int tagId); + +// Find out all the fields specified by the matcher. +void findFields( + const FieldValueMap& fieldValueMap, + const FieldMatcher& matcher, + std::vector<Field>* rootFields); + +// Filter out the fields not in the field matcher. +void filterFields(const FieldMatcher& matcher, FieldValueMap* fieldValueMap); + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/cmds/statsd/src/guardrail/StatsdStats.cpp b/cmds/statsd/src/guardrail/StatsdStats.cpp index bf277f0a383d..33927aa9b44c 100644 --- a/cmds/statsd/src/guardrail/StatsdStats.cpp +++ b/cmds/statsd/src/guardrail/StatsdStats.cpp @@ -80,7 +80,7 @@ void StatsdStats::noteConfigReceived(const ConfigKey& key, int metricsCount, int StatsdStatsReport_ConfigStats configStats; configStats.set_uid(key.GetUid()); - configStats.set_name(key.GetName()); + configStats.set_id(key.GetId()); configStats.set_creation_time_sec(nowTimeSec); configStats.set_metric_count(metricsCount); configStats.set_condition_count(conditionsCount); @@ -196,34 +196,34 @@ void StatsdStats::setCurrentUidMapMemory(int bytes) { mUidMapStats.set_bytes_used(bytes); } -void StatsdStats::noteConditionDimensionSize(const ConfigKey& key, const string& name, int size) { +void StatsdStats::noteConditionDimensionSize(const ConfigKey& key, const int64_t& id, int size) { lock_guard<std::mutex> lock(mLock); // if name doesn't exist before, it will create the key with count 0. auto& conditionSizeMap = mConditionStats[key]; - if (size > conditionSizeMap[name]) { - conditionSizeMap[name] = size; + if (size > conditionSizeMap[id]) { + conditionSizeMap[id] = size; } } -void StatsdStats::noteMetricDimensionSize(const ConfigKey& key, const string& name, int size) { +void StatsdStats::noteMetricDimensionSize(const ConfigKey& key, const int64_t& id, int size) { lock_guard<std::mutex> lock(mLock); // if name doesn't exist before, it will create the key with count 0. auto& metricsDimensionMap = mMetricsStats[key]; - if (size > metricsDimensionMap[name]) { - metricsDimensionMap[name] = size; + if (size > metricsDimensionMap[id]) { + metricsDimensionMap[id] = size; } } -void StatsdStats::noteMatcherMatched(const ConfigKey& key, const string& name) { +void StatsdStats::noteMatcherMatched(const ConfigKey& key, const int64_t& id) { lock_guard<std::mutex> lock(mLock); auto& matcherStats = mMatcherStats[key]; - matcherStats[name]++; + matcherStats[id]++; } -void StatsdStats::noteAnomalyDeclared(const ConfigKey& key, const string& name) { +void StatsdStats::noteAnomalyDeclared(const ConfigKey& key, const int64_t& id) { lock_guard<std::mutex> lock(mLock); auto& alertStats = mAlertStats[key]; - alertStats[name]++; + alertStats[id]++; } void StatsdStats::noteRegisteredAnomalyAlarmChanged() { @@ -279,9 +279,10 @@ void StatsdStats::addSubStatsToConfigLocked(const ConfigKey& key, const auto& matcherStats = mMatcherStats[key]; for (const auto& stats : matcherStats) { auto output = configStats.add_matcher_stats(); - output->set_name(stats.first); + output->set_id(stats.first); output->set_matched_times(stats.second); - VLOG("matcher %s matched %d times", stats.first.c_str(), stats.second); + VLOG("matcher %lld matched %d times", + (long long)stats.first, stats.second); } } // Add condition stats @@ -289,9 +290,10 @@ void StatsdStats::addSubStatsToConfigLocked(const ConfigKey& key, const auto& conditionStats = mConditionStats[key]; for (const auto& stats : conditionStats) { auto output = configStats.add_condition_stats(); - output->set_name(stats.first); + output->set_id(stats.first); output->set_max_tuple_counts(stats.second); - VLOG("condition %s max output tuple size %d", stats.first.c_str(), stats.second); + VLOG("condition %lld max output tuple size %d", + (long long)stats.first, stats.second); } } // Add metrics stats @@ -299,9 +301,10 @@ void StatsdStats::addSubStatsToConfigLocked(const ConfigKey& key, const auto& conditionStats = mMetricsStats[key]; for (const auto& stats : conditionStats) { auto output = configStats.add_metric_stats(); - output->set_name(stats.first); + output->set_id(stats.first); output->set_max_tuple_counts(stats.second); - VLOG("metrics %s max output tuple size %d", stats.first.c_str(), stats.second); + VLOG("metrics %lld max output tuple size %d", + (long long)stats.first, stats.second); } } // Add anomaly detection alert stats @@ -309,9 +312,9 @@ void StatsdStats::addSubStatsToConfigLocked(const ConfigKey& key, const auto& alertStats = mAlertStats[key]; for (const auto& stats : alertStats) { auto output = configStats.add_alert_stats(); - output->set_name(stats.first); + output->set_id(stats.first); output->set_alerted_times(stats.second); - VLOG("alert %s declared %d times", stats.first.c_str(), stats.second); + VLOG("alert %lld declared %d times", (long long)stats.first, stats.second); } } } @@ -343,9 +346,9 @@ void StatsdStats::dumpStats(std::vector<uint8_t>* output, bool reset) { // in production. if (DEBUG) { VLOG("*****ICEBOX*****"); - VLOG("Config {%d-%s}: creation=%d, deletion=%d, #metric=%d, #condition=%d, " + VLOG("Config {%d-%lld}: creation=%d, deletion=%d, #metric=%d, #condition=%d, " "#matcher=%d, #alert=%d, #valid=%d", - configStats.uid(), configStats.name().c_str(), configStats.creation_time_sec(), + configStats.uid(), (long long)configStats.id(), configStats.creation_time_sec(), configStats.deletion_time_sec(), configStats.metric_count(), configStats.condition_count(), configStats.matcher_count(), configStats.alert_count(), configStats.is_valid()); @@ -364,9 +367,9 @@ void StatsdStats::dumpStats(std::vector<uint8_t>* output, bool reset) { auto& configStats = pair.second; if (DEBUG) { VLOG("********Active Configs***********"); - VLOG("Config {%d-%s}: creation=%d, deletion=%d, #metric=%d, #condition=%d, " + VLOG("Config {%d-%lld}: creation=%d, deletion=%d, #metric=%d, #condition=%d, " "#matcher=%d, #alert=%d, #valid=%d", - configStats.uid(), configStats.name().c_str(), configStats.creation_time_sec(), + configStats.uid(), (long long)configStats.id(), configStats.creation_time_sec(), configStats.deletion_time_sec(), configStats.metric_count(), configStats.condition_count(), configStats.matcher_count(), configStats.alert_count(), configStats.is_valid()); diff --git a/cmds/statsd/src/guardrail/StatsdStats.h b/cmds/statsd/src/guardrail/StatsdStats.h index cb868e1fd8c6..45aa192318a4 100644 --- a/cmds/statsd/src/guardrail/StatsdStats.h +++ b/cmds/statsd/src/guardrail/StatsdStats.h @@ -99,10 +99,10 @@ public: * count > kDimensionKeySizeSoftLimit. * * [key]: The config key that this condition belongs to. - * [name]: The name of the condition. + * [id]: The id of the condition. * [size]: The output tuple size. */ - void noteConditionDimensionSize(const ConfigKey& key, const std::string& name, int size); + void noteConditionDimensionSize(const ConfigKey& key, const int64_t& id, int size); /** * Report the size of output tuple of a metric. @@ -111,26 +111,26 @@ public: * count > kDimensionKeySizeSoftLimit. * * [key]: The config key that this metric belongs to. - * [name]: The name of the metric. + * [id]: The id of the metric. * [size]: The output tuple size. */ - void noteMetricDimensionSize(const ConfigKey& key, const std::string& name, int size); + void noteMetricDimensionSize(const ConfigKey& key, const int64_t& id, int size); /** * Report a matcher has been matched. * * [key]: The config key that this matcher belongs to. - * [name]: The name of the matcher. + * [id]: The id of the matcher. */ - void noteMatcherMatched(const ConfigKey& key, const std::string& name); + void noteMatcherMatched(const ConfigKey& key, const int64_t& id); /** * Report that an anomaly detection alert has been declared. * * [key]: The config key that this alert belongs to. - * [name]: The name of the alert. + * [id]: The id of the alert. */ - void noteAnomalyDeclared(const ConfigKey& key, const std::string& name); + void noteAnomalyDeclared(const ConfigKey& key, const int64_t& id); /** * Report an atom event has been logged. @@ -187,12 +187,12 @@ private: // Stores the number of output tuple of condition trackers when it's bigger than // kDimensionKeySizeSoftLimit. When you see the number is kDimensionKeySizeHardLimit +1, // it means some data has been dropped. - std::map<const ConfigKey, std::map<const std::string, int>> mConditionStats; + std::map<const ConfigKey, std::map<const int64_t, int>> mConditionStats; // Stores the number of output tuple of metric producers when it's bigger than // kDimensionKeySizeSoftLimit. When you see the number is kDimensionKeySizeHardLimit +1, // it means some data has been dropped. - std::map<const ConfigKey, std::map<const std::string, int>> mMetricsStats; + std::map<const ConfigKey, std::map<const int64_t, int>> mMetricsStats; // Stores the number of times a pushed atom is logged. // The size of the vector is the largest pushed atom id in atoms.proto + 1. Atoms @@ -206,10 +206,10 @@ private: // Stores the number of times an anomaly detection alert has been declared // (per config, per alert name). - std::map<const ConfigKey, std::map<const std::string, int>> mAlertStats; + std::map<const ConfigKey, std::map<const int64_t, int>> mAlertStats; // Stores how many times a matcher have been matched. - std::map<const ConfigKey, std::map<const std::string, int>> mMatcherStats; + std::map<const ConfigKey, std::map<const int64_t, int>> mMatcherStats; void noteConfigRemovedInternalLocked(const ConfigKey& key); diff --git a/cmds/statsd/src/logd/LogEvent.cpp b/cmds/statsd/src/logd/LogEvent.cpp index d660b5f9fe02..49a6e330c590 100644 --- a/cmds/statsd/src/logd/LogEvent.cpp +++ b/cmds/statsd/src/logd/LogEvent.cpp @@ -17,8 +17,14 @@ #define DEBUG true // STOPSHIP if true #include "logd/LogEvent.h" +#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h" + +#include <set> #include <sstream> -#include "stats_util.h" + +#include "field_util.h" +#include "dimension.h" +#include "stats_log_util.h" namespace android { namespace os { @@ -30,16 +36,20 @@ using std::string; using android::util::ProtoOutputStream; LogEvent::LogEvent(log_msg& msg) { - mContext = + android_log_context context = create_android_log_parser(msg.msg() + sizeof(uint32_t), msg.len() - sizeof(uint32_t)); mTimestampNs = msg.entry_v1.sec * NS_PER_SEC + msg.entry_v1.nsec; mLogUid = msg.entry_v4.uid; - init(mContext); + init(context); + if (context) { + android_log_destroy(&context); + } } LogEvent::LogEvent(int32_t tagId, uint64_t timestampNs) { mTimestampNs = timestampNs; mTagId = tagId; + mLogUid = 0; mContext = create_android_logger(1937006964); // the event tag shared by all stats logs if (mContext) { android_log_write_int32(mContext, tagId); @@ -53,6 +63,14 @@ void LogEvent::init() { // turns to reader mode mContext = create_android_log_parser(buffer, len); init(mContext); + // destroy the context to save memory. + android_log_destroy(&mContext); + } +} + +LogEvent::~LogEvent() { + if (mContext) { + android_log_destroy(&mContext); } } @@ -98,19 +116,72 @@ bool LogEvent::write(float value) { return false; } -LogEvent::~LogEvent() { +bool LogEvent::write(const std::vector<AttributionNode>& nodes) { if (mContext) { - android_log_destroy(&mContext); + if (android_log_write_list_begin(mContext) < 0) { + return false; + } + for (size_t i = 0; i < nodes.size(); ++i) { + if (!write(nodes[i])) { + return false; + } + } + if (android_log_write_list_end(mContext) < 0) { + return false; + } + return true; + } + return false; +} + +bool LogEvent::write(const AttributionNode& node) { + if (mContext) { + if (android_log_write_list_begin(mContext) < 0) { + return false; + } + if (android_log_write_int32(mContext, node.uid()) < 0) { + return false; + } + if (android_log_write_string8(mContext, node.tag().c_str()) < 0) { + return false; + } + if (android_log_write_int32(mContext, node.uid()) < 0) { + return false; + } + if (android_log_write_list_end(mContext) < 0) { + return false; + } + return true; } + return false; } +namespace { + +void increaseField(Field *field, bool is_child) { + if (is_child) { + if (field->child_size() <= 0) { + field->add_child(); + } + } else { + field->clear_child(); + } + Field* curr = is_child ? field->mutable_child(0) : field; + if (!curr->has_field()) { + curr->set_field(1); + } else { + curr->set_field(curr->field() + 1); + } +} + +} // namespace + /** * The elements of each log event are stored as a vector of android_log_list_elements. * The goal is to do as little preprocessing as possible, because we read a tiny fraction * of the elements that are written to the log. */ void LogEvent::init(android_log_context context) { - mElements.clear(); android_log_list_element elem; // TODO: The log is actually structured inside one list. This is convenient // because we'll be able to use it to put the attribution (WorkSource) block first @@ -118,24 +189,79 @@ void LogEvent::init(android_log_context context) { // list-related log elements and the order we get there is our index-keyed data // structure. int i = 0; + + int seenListStart = 0; + + Field field; do { elem = android_log_read_next(context); switch ((int)elem.type) { case EVENT_TYPE_INT: - // elem at [0] is EVENT_TYPE_LIST, [1] is the tag id. If we add WorkSource, it would - // be the list starting at [2]. + // elem at [0] is EVENT_TYPE_LIST, [1] is the tag id. if (i == 1) { mTagId = elem.data.int32; - break; + } else { + increaseField(&field, seenListStart > 0/* is_child */); + DimensionsValue dimensionsValue; + dimensionsValue.set_value_int(elem.data.int32); + setFieldInLeafValueProto(field, &dimensionsValue); + mFieldValueMap.insert( + std::make_pair(buildAtomField(mTagId, field), dimensionsValue)); } + break; case EVENT_TYPE_FLOAT: + { + increaseField(&field, seenListStart > 0/* is_child */); + DimensionsValue dimensionsValue; + dimensionsValue.set_value_float(elem.data.float32); + setFieldInLeafValueProto(field, &dimensionsValue); + mFieldValueMap.insert( + std::make_pair(buildAtomField(mTagId, field), dimensionsValue)); + } + break; case EVENT_TYPE_STRING: + { + increaseField(&field, seenListStart > 0/* is_child */); + DimensionsValue dimensionsValue; + dimensionsValue.set_value_str(string(elem.data.string, elem.len).c_str()); + setFieldInLeafValueProto(field, &dimensionsValue); + mFieldValueMap.insert( + std::make_pair(buildAtomField(mTagId, field), dimensionsValue)); + } + break; case EVENT_TYPE_LONG: - mElements.push_back(elem); + { + increaseField(&field, seenListStart > 0 /* is_child */); + DimensionsValue dimensionsValue; + dimensionsValue.set_value_long(elem.data.int64); + setFieldInLeafValueProto(field, &dimensionsValue); + mFieldValueMap.insert( + std::make_pair(buildAtomField(mTagId, field), dimensionsValue)); + } break; case EVENT_TYPE_LIST: + if (i >= 1) { + if (seenListStart > 0) { + increasePosition(&field); + } else { + increaseField(&field, false /* is_child */); + } + seenListStart++; + if (seenListStart >= 3) { + ALOGE("Depth > 2. Not supported!"); + return; + } + } break; case EVENT_TYPE_LIST_STOP: + seenListStart--; + if (seenListStart == 0) { + field.clear_position_index(); + } else { + if (field.child_size() > 0) { + field.mutable_child(0)->clear_field(); + } + } break; case EVENT_TYPE_UNKNOWN: break; @@ -147,142 +273,145 @@ void LogEvent::init(android_log_context context) { } int64_t LogEvent::GetLong(size_t key, status_t* err) const { - if (key < 1 || (key - 1) >= mElements.size()) { + DimensionsValue value; + if (!GetSimpleAtomDimensionsValueProto(key, &value)) { *err = BAD_INDEX; return 0; } - key--; - const android_log_list_element& elem = mElements[key]; - if (elem.type == EVENT_TYPE_INT) { - return elem.data.int32; - } else if (elem.type == EVENT_TYPE_LONG) { - return elem.data.int64; - } else if (elem.type == EVENT_TYPE_FLOAT) { - return (int64_t)elem.data.float32; - } else { - *err = BAD_TYPE; - return 0; + const DimensionsValue* leafValue = getSingleLeafValue(&value); + switch (leafValue->value_case()) { + case DimensionsValue::ValueCase::kValueInt: + return (int64_t)leafValue->value_int(); + case DimensionsValue::ValueCase::kValueLong: + return leafValue->value_long(); + case DimensionsValue::ValueCase::kValueBool: + return leafValue->value_bool() ? 1 : 0; + case DimensionsValue::ValueCase::kValueFloat: + return (int64_t)leafValue->value_float(); + case DimensionsValue::ValueCase::kValueTuple: + case DimensionsValue::ValueCase::kValueStr: + case DimensionsValue::ValueCase::VALUE_NOT_SET: { + *err = BAD_TYPE; + return 0; + } } } const char* LogEvent::GetString(size_t key, status_t* err) const { - if (key < 1 || (key - 1) >= mElements.size()) { + DimensionsValue value; + if (!GetSimpleAtomDimensionsValueProto(key, &value)) { *err = BAD_INDEX; - return NULL; + return 0; } - key--; - const android_log_list_element& elem = mElements[key]; - if (elem.type != EVENT_TYPE_STRING) { - *err = BAD_TYPE; - return NULL; + const DimensionsValue* leafValue = getSingleLeafValue(&value); + switch (leafValue->value_case()) { + case DimensionsValue::ValueCase::kValueStr: + return leafValue->value_str().c_str(); + case DimensionsValue::ValueCase::kValueInt: + case DimensionsValue::ValueCase::kValueLong: + case DimensionsValue::ValueCase::kValueBool: + case DimensionsValue::ValueCase::kValueFloat: + case DimensionsValue::ValueCase::kValueTuple: + case DimensionsValue::ValueCase::VALUE_NOT_SET: { + *err = BAD_TYPE; + return 0; + } } - // Need to add the '/0' at the end by specifying the length of the string. - return string(elem.data.string, elem.len).c_str(); } bool LogEvent::GetBool(size_t key, status_t* err) const { - if (key < 1 || (key - 1) >= mElements.size()) { + DimensionsValue value; + if (!GetSimpleAtomDimensionsValueProto(key, &value)) { *err = BAD_INDEX; return 0; } - key--; - const android_log_list_element& elem = mElements[key]; - if (elem.type == EVENT_TYPE_INT) { - return elem.data.int32 != 0; - } else if (elem.type == EVENT_TYPE_LONG) { - return elem.data.int64 != 0; - } else if (elem.type == EVENT_TYPE_FLOAT) { - return elem.data.float32 != 0; - } else { - *err = BAD_TYPE; - return 0; + const DimensionsValue* leafValue = getSingleLeafValue(&value); + switch (leafValue->value_case()) { + case DimensionsValue::ValueCase::kValueInt: + return leafValue->value_int() != 0; + case DimensionsValue::ValueCase::kValueLong: + return leafValue->value_long() != 0; + case DimensionsValue::ValueCase::kValueBool: + return leafValue->value_bool(); + case DimensionsValue::ValueCase::kValueFloat: + return leafValue->value_float() != 0; + case DimensionsValue::ValueCase::kValueTuple: + case DimensionsValue::ValueCase::kValueStr: + case DimensionsValue::ValueCase::VALUE_NOT_SET: { + *err = BAD_TYPE; + return 0; + } } } float LogEvent::GetFloat(size_t key, status_t* err) const { - if (key < 1 || (key - 1) >= mElements.size()) { + DimensionsValue value; + if (!GetSimpleAtomDimensionsValueProto(key, &value)) { *err = BAD_INDEX; return 0; } - key--; - const android_log_list_element& elem = mElements[key]; - if (elem.type == EVENT_TYPE_INT) { - return (float)elem.data.int32; - } else if (elem.type == EVENT_TYPE_LONG) { - return (float)elem.data.int64; - } else if (elem.type == EVENT_TYPE_FLOAT) { - return elem.data.float32; - } else { - *err = BAD_TYPE; - return 0; + const DimensionsValue* leafValue = getSingleLeafValue(&value); + switch (leafValue->value_case()) { + case DimensionsValue::ValueCase::kValueInt: + return (float)leafValue->value_int(); + case DimensionsValue::ValueCase::kValueLong: + return (float)leafValue->value_long(); + case DimensionsValue::ValueCase::kValueBool: + return leafValue->value_bool() ? 1.0f : 0.0f; + case DimensionsValue::ValueCase::kValueFloat: + return leafValue->value_float(); + case DimensionsValue::ValueCase::kValueTuple: + case DimensionsValue::ValueCase::kValueStr: + case DimensionsValue::ValueCase::VALUE_NOT_SET: { + *err = BAD_TYPE; + return 0; + } } } -KeyValuePair LogEvent::GetKeyValueProto(size_t key) const { - KeyValuePair pair; - pair.set_key(key); - // If the value is not valid, return the KeyValuePair without assigning the value. - // Caller can detect the error by checking the enum for "one of" proto type. - if (key < 1 || (key - 1) >= mElements.size()) { - return pair; - } - key--; - - const android_log_list_element& elem = mElements[key]; - if (elem.type == EVENT_TYPE_INT) { - pair.set_value_int(elem.data.int32); - } else if (elem.type == EVENT_TYPE_LONG) { - pair.set_value_long(elem.data.int64); - } else if (elem.type == EVENT_TYPE_STRING) { - pair.set_value_str(elem.data.string); - } else if (elem.type == EVENT_TYPE_FLOAT) { - pair.set_value_float(elem.data.float32); +void LogEvent::GetAtomDimensionsValueProtos(const FieldMatcher& matcher, + std::vector<DimensionsValue> *dimensionsValues) const { + findDimensionsValues(mFieldValueMap, matcher, dimensionsValues); +} + +bool LogEvent::GetAtomDimensionsValueProto(const FieldMatcher& matcher, + DimensionsValue* dimensionsValue) const { + std::vector<DimensionsValue> rootDimensionsValues; + findDimensionsValues(mFieldValueMap, matcher, &rootDimensionsValues); + if (rootDimensionsValues.size() != 1) { + return false; } - return pair; + *dimensionsValue = rootDimensionsValues.front(); + return true; +} + +bool LogEvent::GetSimpleAtomDimensionsValueProto(size_t atomField, + DimensionsValue* dimensionsValue) const { + return GetAtomDimensionsValueProto( + buildSimpleAtomFieldMatcher(mTagId, atomField), dimensionsValue); +} + +DimensionsValue LogEvent::GetSimpleAtomDimensionsValueProto(size_t atomField) const { + DimensionsValue dimensionsValue; + GetSimpleAtomDimensionsValueProto(atomField, &dimensionsValue); + return dimensionsValue; } string LogEvent::ToString() const { ostringstream result; result << "{ " << mTimestampNs << " (" << mTagId << ")"; - const size_t N = mElements.size(); - for (size_t i=0; i<N; i++) { - result << " "; - result << (i + 1); + for (const auto& itr : mFieldValueMap) { + result << FieldToString(itr.first); result << "->"; - const android_log_list_element& elem = mElements[i]; - if (elem.type == EVENT_TYPE_INT) { - result << elem.data.int32; - } else if (elem.type == EVENT_TYPE_LONG) { - result << elem.data.int64; - } else if (elem.type == EVENT_TYPE_FLOAT) { - result << elem.data.float32; - } else if (elem.type == EVENT_TYPE_STRING) { - // Need to add the '/0' at the end by specifying the length of the string. - result << string(elem.data.string, elem.len).c_str(); - } + result << DimensionsValueToString(itr.second); + result << " "; } result << " }"; return result.str(); } -void LogEvent::ToProto(ProtoOutputStream& proto) const { - long long atomToken = proto.start(FIELD_TYPE_MESSAGE | mTagId); - const size_t N = mElements.size(); - for (size_t i=0; i<N; i++) { - const int key = i + 1; - - const android_log_list_element& elem = mElements[i]; - if (elem.type == EVENT_TYPE_INT) { - proto.write(FIELD_TYPE_INT32 | key, elem.data.int32); - } else if (elem.type == EVENT_TYPE_LONG) { - proto.write(FIELD_TYPE_INT64 | key, (long long)elem.data.int64); - } else if (elem.type == EVENT_TYPE_FLOAT) { - proto.write(FIELD_TYPE_FLOAT | key, elem.data.float32); - } else if (elem.type == EVENT_TYPE_STRING) { - proto.write(FIELD_TYPE_STRING | key, elem.data.string); - } - } - proto.end(atomToken); +void LogEvent::ToProto(ProtoOutputStream& protoOutput) const { + writeFieldValueTreeToStream(getFieldValueMap(), &protoOutput); } } // namespace statsd diff --git a/cmds/statsd/src/logd/LogEvent.h b/cmds/statsd/src/logd/LogEvent.h index d3f38deef886..8f3dedfd1825 100644 --- a/cmds/statsd/src/logd/LogEvent.h +++ b/cmds/statsd/src/logd/LogEvent.h @@ -16,6 +16,7 @@ #pragma once +#include "field_util.h" #include "frameworks/base/cmds/statsd/src/stats_log.pb.h" #include <android/util/ProtoOutputStream.h> @@ -23,9 +24,11 @@ #include <log/log_read.h> #include <private/android_logger.h> #include <utils/Errors.h> +#include <utils/JenkinsHash.h> #include <memory> #include <string> +#include <map> #include <vector> namespace android { @@ -77,6 +80,20 @@ public: bool GetBool(size_t key, status_t* err) const; float GetFloat(size_t key, status_t* err) const; + /* + * Get DimensionsValue proto objects from FieldMatcher. + */ + void GetAtomDimensionsValueProtos( + const FieldMatcher& matcher, std::vector<DimensionsValue> *dimensionsValues) const; + bool GetAtomDimensionsValueProto( + const FieldMatcher& matcher, DimensionsValue* dimensionsValue) const; + + /* + * Get a DimensionsValue proto objects from Field. + */ + bool GetSimpleAtomDimensionsValueProto(size_t field, DimensionsValue* dimensionsValue) const; + DimensionsValue GetSimpleAtomDimensionsValueProto(size_t atomField) const; + /** * Write test data to the LogEvent. This can only be used when the LogEvent is constructed * using LogEvent(tagId, timestampNs). You need to call init() before you can read from it. @@ -87,6 +104,8 @@ public: bool write(int64_t value); bool write(const string& value); bool write(float value); + bool write(const std::vector<AttributionNode>& nodes); + bool write(const AttributionNode& node); /** * Return a string representation of this event. @@ -98,11 +117,6 @@ public: */ void ToProto(android::util::ProtoOutputStream& out) const; - /* - * Get a KeyValuePair proto object. - */ - KeyValuePair GetKeyValueProto(size_t key) const; - /** * Used with the constructor where tag is passed in. Converts the log_event_list to read mode * and prepares the list for reading. @@ -114,10 +128,12 @@ public: */ void setTimestampNs(uint64_t timestampNs) {mTimestampNs = timestampNs;} - int size() const { - return mElements.size(); + inline int size() const { + return mFieldValueMap.size(); } + inline const FieldValueMap& getFieldValueMap() const { return mFieldValueMap; } + private: /** * Don't copy, it's slower. If we really need this we can add it but let's try to @@ -130,8 +146,11 @@ private: */ void init(android_log_context context); - vector<android_log_list_element> mElements; + FieldValueMap mFieldValueMap; + // This field is used when statsD wants to create log event object and write fields to it. After + // calling init() function, this object would be destroyed to save memory usage. + // When the log event is created from log msg, this field is never initiated. android_log_context mContext; uint64_t mTimestampNs; diff --git a/cmds/statsd/src/matchers/CombinationLogMatchingTracker.cpp b/cmds/statsd/src/matchers/CombinationLogMatchingTracker.cpp index 51a38b61ccf2..15c067ee936d 100644 --- a/cmds/statsd/src/matchers/CombinationLogMatchingTracker.cpp +++ b/cmds/statsd/src/matchers/CombinationLogMatchingTracker.cpp @@ -29,8 +29,8 @@ using std::unique_ptr; using std::unordered_map; using std::vector; -CombinationLogMatchingTracker::CombinationLogMatchingTracker(const string& name, const int index) - : LogMatchingTracker(name, index) { +CombinationLogMatchingTracker::CombinationLogMatchingTracker(const int64_t& id, const int index) + : LogMatchingTracker(id, index) { } CombinationLogMatchingTracker::~CombinationLogMatchingTracker() { @@ -38,7 +38,7 @@ CombinationLogMatchingTracker::~CombinationLogMatchingTracker() { bool CombinationLogMatchingTracker::init(const vector<AtomMatcher>& allLogMatchers, const vector<sp<LogMatchingTracker>>& allTrackers, - const unordered_map<string, int>& matcherMap, + const unordered_map<int64_t, int>& matcherMap, vector<bool>& stack) { if (mInitialized) { return true; @@ -60,10 +60,10 @@ bool CombinationLogMatchingTracker::init(const vector<AtomMatcher>& allLogMatche return false; } - for (const string& child : matcher.matcher()) { + for (const auto& child : matcher.matcher()) { auto pair = matcherMap.find(child); if (pair == matcherMap.end()) { - ALOGW("Matcher %s not found in the config", child.c_str()); + ALOGW("Matcher %lld not found in the config", (long long)child); return false; } @@ -76,14 +76,14 @@ bool CombinationLogMatchingTracker::init(const vector<AtomMatcher>& allLogMatche } if (!allTrackers[childIndex]->init(allLogMatchers, allTrackers, matcherMap, stack)) { - ALOGW("child matcher init failed %s", child.c_str()); + ALOGW("child matcher init failed %lld", (long long)child); return false; } mChildren.push_back(childIndex); - const set<int>& childTagIds = allTrackers[childIndex]->getTagIds(); - mTagIds.insert(childTagIds.begin(), childTagIds.end()); + const set<int>& childTagIds = allTrackers[childIndex]->getAtomIds(); + mAtomIds.insert(childTagIds.begin(), childTagIds.end()); } mInitialized = true; @@ -100,7 +100,7 @@ void CombinationLogMatchingTracker::onLogEvent(const LogEvent& event, return; } - if (mTagIds.find(event.GetTagId()) == mTagIds.end()) { + if (mAtomIds.find(event.GetTagId()) == mAtomIds.end()) { matcherResults[mIndex] = MatchingState::kNotMatched; return; } diff --git a/cmds/statsd/src/matchers/CombinationLogMatchingTracker.h b/cmds/statsd/src/matchers/CombinationLogMatchingTracker.h index 81f6e8052ebc..2a3f08da7b96 100644 --- a/cmds/statsd/src/matchers/CombinationLogMatchingTracker.h +++ b/cmds/statsd/src/matchers/CombinationLogMatchingTracker.h @@ -31,11 +31,11 @@ namespace statsd { // Represents a AtomMatcher_Combination in the StatsdConfig. class CombinationLogMatchingTracker : public virtual LogMatchingTracker { public: - CombinationLogMatchingTracker(const std::string& name, const int index); + CombinationLogMatchingTracker(const int64_t& id, const int index); bool init(const std::vector<AtomMatcher>& allLogMatchers, const std::vector<sp<LogMatchingTracker>>& allTrackers, - const std::unordered_map<std::string, int>& matcherMap, + const std::unordered_map<int64_t, int>& matcherMap, std::vector<bool>& stack); ~CombinationLogMatchingTracker(); diff --git a/cmds/statsd/src/matchers/LogMatchingTracker.h b/cmds/statsd/src/matchers/LogMatchingTracker.h index 8162c443fbd3..4f30a047e256 100644 --- a/cmds/statsd/src/matchers/LogMatchingTracker.h +++ b/cmds/statsd/src/matchers/LogMatchingTracker.h @@ -33,8 +33,8 @@ namespace statsd { class LogMatchingTracker : public virtual RefBase { public: - LogMatchingTracker(const std::string& name, const int index) - : mName(name), mIndex(index), mInitialized(false){}; + LogMatchingTracker(const int64_t& id, const int index) + : mId(id), mIndex(index), mInitialized(false){}; virtual ~LogMatchingTracker(){}; @@ -48,7 +48,7 @@ public: // circle dependency. virtual bool init(const std::vector<AtomMatcher>& allLogMatchers, const std::vector<sp<LogMatchingTracker>>& allTrackers, - const std::unordered_map<std::string, int>& matcherMap, + const std::unordered_map<int64_t, int>& matcherMap, std::vector<bool>& stack) = 0; // Called when a log event comes. @@ -65,17 +65,17 @@ public: // Get the tagIds that this matcher cares about. The combined collection is stored // in MetricMananger, so that we can pass any LogEvents that are not interest of us. It uses // some memory but hopefully it can save us much CPU time when there is flood of events. - virtual const std::set<int>& getTagIds() const { - return mTagIds; + virtual const std::set<int>& getAtomIds() const { + return mAtomIds; } - const std::string& getName() const { - return mName; + const int64_t& getId() const { + return mId; } protected: // Name of this matching. We don't really need the name, but it makes log message easy to debug. - const std::string mName; + const int64_t mId; // Index of this LogMatchingTracker in MetricsManager's container. const int mIndex; @@ -88,7 +88,7 @@ protected: // useful when we have a complex CombinationLogMatcherTracker. // TODO: Consider use an array instead of stl set. In reality, the number of the tag ids a // LogMatchingTracker cares is only a few. - std::set<int> mTagIds; + std::set<int> mAtomIds; }; } // namespace statsd diff --git a/cmds/statsd/src/matchers/SimpleLogMatchingTracker.cpp b/cmds/statsd/src/matchers/SimpleLogMatchingTracker.cpp index ac217abecba3..31b3db524e80 100644 --- a/cmds/statsd/src/matchers/SimpleLogMatchingTracker.cpp +++ b/cmds/statsd/src/matchers/SimpleLogMatchingTracker.cpp @@ -29,13 +29,14 @@ using std::unordered_map; using std::vector; -SimpleLogMatchingTracker::SimpleLogMatchingTracker(const string& name, const int index, - const SimpleAtomMatcher& matcher) - : LogMatchingTracker(name, index), mMatcher(matcher) { - if (!matcher.has_tag()) { +SimpleLogMatchingTracker::SimpleLogMatchingTracker(const int64_t& id, const int index, + const SimpleAtomMatcher& matcher, + const UidMap& uidMap) + : LogMatchingTracker(id, index), mMatcher(matcher), mUidMap(uidMap) { + if (!matcher.has_atom_id()) { mInitialized = false; } else { - mTagIds.insert(matcher.tag()); + mAtomIds.insert(matcher.atom_id()); mInitialized = true; } } @@ -45,7 +46,7 @@ SimpleLogMatchingTracker::~SimpleLogMatchingTracker() { bool SimpleLogMatchingTracker::init(const vector<AtomMatcher>& allLogMatchers, const vector<sp<LogMatchingTracker>>& allTrackers, - const unordered_map<string, int>& matcherMap, + const unordered_map<int64_t, int>& matcherMap, vector<bool>& stack) { // no need to do anything. return mInitialized; @@ -55,18 +56,18 @@ void SimpleLogMatchingTracker::onLogEvent(const LogEvent& event, const vector<sp<LogMatchingTracker>>& allTrackers, vector<MatchingState>& matcherResults) { if (matcherResults[mIndex] != MatchingState::kNotComputed) { - VLOG("Matcher %s already evaluated ", mName.c_str()); + VLOG("Matcher %lld already evaluated ", (long long)mId); return; } - if (mTagIds.find(event.GetTagId()) == mTagIds.end()) { + if (mAtomIds.find(event.GetTagId()) == mAtomIds.end()) { matcherResults[mIndex] = MatchingState::kNotMatched; return; } - bool matched = matchesSimple(mMatcher, event); + bool matched = matchesSimple(mUidMap, mMatcher, event); matcherResults[mIndex] = matched ? MatchingState::kMatched : MatchingState::kNotMatched; - VLOG("Stats SimpleLogMatcher %s matched? %d", mName.c_str(), matched); + VLOG("Stats SimpleLogMatcher %lld matched? %d", (long long)mId, matched); } } // namespace statsd diff --git a/cmds/statsd/src/matchers/SimpleLogMatchingTracker.h b/cmds/statsd/src/matchers/SimpleLogMatchingTracker.h index 2c188c18c95b..28b339caa466 100644 --- a/cmds/statsd/src/matchers/SimpleLogMatchingTracker.h +++ b/cmds/statsd/src/matchers/SimpleLogMatchingTracker.h @@ -24,6 +24,7 @@ #include <vector> #include "LogMatchingTracker.h" #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h" +#include "packages/UidMap.h" namespace android { namespace os { @@ -31,14 +32,15 @@ namespace statsd { class SimpleLogMatchingTracker : public virtual LogMatchingTracker { public: - SimpleLogMatchingTracker(const std::string& name, const int index, - const SimpleAtomMatcher& matcher); + SimpleLogMatchingTracker(const int64_t& id, const int index, + const SimpleAtomMatcher& matcher, + const UidMap& uidMap); ~SimpleLogMatchingTracker(); bool init(const std::vector<AtomMatcher>& allLogMatchers, const std::vector<sp<LogMatchingTracker>>& allTrackers, - const std::unordered_map<std::string, int>& matcherMap, + const std::unordered_map<int64_t, int>& matcherMap, std::vector<bool>& stack) override; void onLogEvent(const LogEvent& event, @@ -47,6 +49,7 @@ public: private: const SimpleAtomMatcher mMatcher; + const UidMap& mUidMap; }; } // namespace statsd diff --git a/cmds/statsd/src/matchers/matcher_util.cpp b/cmds/statsd/src/matchers/matcher_util.cpp index 9e88e5d0d8e3..46d9b92ec94c 100644 --- a/cmds/statsd/src/matchers/matcher_util.cpp +++ b/cmds/statsd/src/matchers/matcher_util.cpp @@ -19,7 +19,9 @@ #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h" #include "matchers/LogMatchingTracker.h" #include "matchers/matcher_util.h" +#include "dimension.h" #include "stats_util.h" +#include "field_util.h" #include <log/event_tag_map.h> #include <log/log_event_list.h> @@ -91,127 +93,173 @@ bool combinationMatch(const vector<int>& children, const LogicalOperation& opera return matched; } -bool matchesSimple(const SimpleAtomMatcher& simpleMatcher, const LogEvent& event) { - const int tagId = event.GetTagId(); - - if (simpleMatcher.tag() != tagId) { - return false; - } - // now see if this event is interesting to us -- matches ALL the matchers - // defined in the metrics. - bool allMatched = true; - for (int j = 0; allMatched && j < simpleMatcher.key_value_matcher_size(); j++) { - auto cur = simpleMatcher.key_value_matcher(j); - - // TODO: Check if this key is a magic key (eg package name). - // TODO: Maybe make packages a different type in the config? - int key = cur.key_matcher().key(); +bool IsAttributionUidField(const Field& field) { + return field.child_size() == 1 && field.child(0).field() == 1 + && field.child(0).child_size() == 1 && field.child(0).child(0).field() == 1; +} - const KeyValueMatcher::ValueMatcherCase matcherCase = cur.value_matcher_case(); - if (matcherCase == KeyValueMatcher::ValueMatcherCase::kEqString) { - // String fields - status_t err = NO_ERROR; - const char* val = event.GetString(key, &err); - if (err == NO_ERROR && val != NULL) { - if (!(cur.eq_string() == val)) { - allMatched = false; - break; - } - } else { - allMatched = false; - break; - } - } else if (matcherCase == KeyValueMatcher::ValueMatcherCase::kEqInt || - matcherCase == KeyValueMatcher::ValueMatcherCase::kLtInt || - matcherCase == KeyValueMatcher::ValueMatcherCase::kGtInt || - matcherCase == KeyValueMatcher::ValueMatcherCase::kLteInt || - matcherCase == KeyValueMatcher::ValueMatcherCase::kGteInt) { - // Integer fields - status_t err = NO_ERROR; - int64_t val = event.GetLong(key, &err); - if (err == NO_ERROR) { - if (matcherCase == KeyValueMatcher::ValueMatcherCase::kEqInt) { - if (!(val == cur.eq_int())) { - allMatched = false; - break; - } - } else if (matcherCase == KeyValueMatcher::ValueMatcherCase::kLtInt) { - if (!(val < cur.lt_int())) { - allMatched = false; - break; - } - } else if (matcherCase == KeyValueMatcher::ValueMatcherCase::kGtInt) { - if (!(val > cur.gt_int())) { - allMatched = false; - break; - } - } else if (matcherCase == KeyValueMatcher::ValueMatcherCase::kLteInt) { - if (!(val <= cur.lte_int())) { - allMatched = false; - break; - } - } else if (matcherCase == KeyValueMatcher::ValueMatcherCase::kGteInt) { - if (!(val >= cur.gte_int())) { - allMatched = false; - break; +bool matchesNonRepeatedField( + const UidMap& uidMap, + const FieldValueMap& fieldMap, + const FieldValueMatcher&matcher, + const Field& field) { + if (matcher.value_matcher_case() == + FieldValueMatcher::ValueMatcherCase::VALUE_MATCHER_NOT_SET) { + return !fieldMap.empty() && fieldMap.begin()->first.field() == matcher.field(); + } else if (matcher.value_matcher_case() == FieldValueMatcher::ValueMatcherCase::kMatchesTuple) { + bool allMatched = true; + for (int i = 0; allMatched && i < matcher.matches_tuple().field_value_matcher_size(); ++i) { + const auto& childMatcher = matcher.matches_tuple().field_value_matcher(i); + Field childField = field; + appendLeaf(&childField, childMatcher.field()); + allMatched &= matchFieldSimple(uidMap, fieldMap, childMatcher, childField); + } + return allMatched; + } else { + auto ret = fieldMap.equal_range(field); + int found = 0; + for (auto it = ret.first; it != ret.second; ++it) { + found++; + } + // Not found. + if (found <= 0) { + return false; + } + if (found > 1) { + ALOGE("Found multiple values for optional field."); + return false; + } + bool matched = false; + switch (matcher.value_matcher_case()) { + case FieldValueMatcher::ValueMatcherCase::kEqBool: + // Logd does not support bool, it is int instead. + matched = ((ret.first->second.value_int() > 0) == matcher.eq_bool()); + break; + case FieldValueMatcher::ValueMatcherCase::kEqString: + { + if (IsAttributionUidField(field)) { + const int uid = ret.first->second.value_int(); + std::set<string> packageNames = + uidMap.getAppNamesFromUid(uid, true /* normalize*/); + matched = packageNames.find(matcher.eq_string()) != packageNames.end(); + } else { + matched = (ret.first->second.value_str() == matcher.eq_string()); } - } - } else { - allMatched = false; + } + break; + case FieldValueMatcher::ValueMatcherCase::kEqInt: + matched = (ret.first->second.value_int() == matcher.eq_int()); + break; + case FieldValueMatcher::ValueMatcherCase::kLtInt: + matched = (ret.first->second.value_int() < matcher.lt_int()); + break; + case FieldValueMatcher::ValueMatcherCase::kGtInt: + matched = (ret.first->second.value_int() > matcher.gt_int()); + break; + case FieldValueMatcher::ValueMatcherCase::kLtFloat: + matched = (ret.first->second.value_float() < matcher.lt_float()); + break; + case FieldValueMatcher::ValueMatcherCase::kGtFloat: + matched = (ret.first->second.value_float() > matcher.gt_float()); + break; + case FieldValueMatcher::ValueMatcherCase::kLteInt: + matched = (ret.first->second.value_int() <= matcher.lte_int()); + break; + case FieldValueMatcher::ValueMatcherCase::kGteInt: + matched = (ret.first->second.value_int() >= matcher.gte_int()); + break; + default: break; - } - } else if (matcherCase == KeyValueMatcher::ValueMatcherCase::kEqBool) { - // Boolean fields - status_t err = NO_ERROR; - bool val = event.GetBool(key, &err); - if (err == NO_ERROR) { - if (!(cur.eq_bool() == val)) { - allMatched = false; - break; - } - } else { - allMatched = false; - break; - } - } else if (matcherCase == KeyValueMatcher::ValueMatcherCase::kLtFloat || - matcherCase == KeyValueMatcher::ValueMatcherCase::kGtFloat) { - // Float fields - status_t err = NO_ERROR; - float val = event.GetFloat(key, &err); - if (err == NO_ERROR) { - if (matcherCase == KeyValueMatcher::ValueMatcherCase::kLtFloat) { - if (!(val < cur.lt_float())) { - allMatched = false; - break; + } + return matched; + } +} + +bool matchesRepeatedField(const UidMap& uidMap, const FieldValueMap& fieldMap, + const FieldValueMatcher&matcher, const Field& field) { + if (matcher.position() == Position::FIRST) { + Field first_field = field; + setPositionForLeaf(&first_field, 0); + return matchesNonRepeatedField(uidMap, fieldMap, matcher, first_field); + } else { + auto itLower = fieldMap.lower_bound(field); + if (itLower == fieldMap.end()) { + return false; + } + Field next_field = field; + getNextField(&next_field); + auto itUpper = fieldMap.lower_bound(next_field); + switch (matcher.position()) { + case Position::LAST: + { + itUpper--; + if (itUpper == fieldMap.end()) { + return false; + } else { + Field last_field = field; + int last_index = getPositionByReferenceField(field, itUpper->first); + if (last_index < 0) { + return false; + } + setPositionForLeaf(&last_field, last_index); + return matchesNonRepeatedField(uidMap, fieldMap, matcher, last_field); + } + } + break; + case Position::ANY: + { + std::set<int> indexes; + for (auto it = itLower; it != itUpper; ++it) { + int index = getPositionByReferenceField(field, it->first); + if (index >= 0) { + indexes.insert(index); + } } - } else if (matcherCase == KeyValueMatcher::ValueMatcherCase::kGtFloat) { - if (!(val > cur.gt_float())) { - allMatched = false; - break; + bool matched = false; + for (const int index : indexes) { + Field any_field = field; + setPositionForLeaf(&any_field, index); + matched |= matchesNonRepeatedField(uidMap, fieldMap, matcher, any_field); } - } - } else { - allMatched = false; - break; - } - } else { - // If value matcher is not present, assume that we match. - } + return matched; + } + default: + return false; + } } - return allMatched; + } -vector<KeyValuePair> getDimensionKey(const LogEvent& event, - const std::vector<KeyMatcher>& dimensions) { - vector<KeyValuePair> key; - key.reserve(dimensions.size()); - for (const KeyMatcher& dimension : dimensions) { - KeyValuePair k = event.GetKeyValueProto(dimension.key()); - key.push_back(k); +bool matchFieldSimple(const UidMap& uidMap, const FieldValueMap& fieldMap, + const FieldValueMatcher&matcher, const Field& field) { + if (!matcher.has_position()) { + return matchesNonRepeatedField(uidMap, fieldMap, matcher, field); + } else { + return matchesRepeatedField(uidMap, fieldMap, matcher, field); } - return key; } +bool matchesSimple(const UidMap& uidMap, const SimpleAtomMatcher& simpleMatcher, + const LogEvent& event) { + if (simpleMatcher.field_value_matcher_size() <= 0) { + return event.GetTagId() == simpleMatcher.atom_id(); + } + Field root_field; + root_field.set_field(simpleMatcher.atom_id()); + FieldValueMatcher root_field_matcher; + root_field_matcher.set_field(simpleMatcher.atom_id()); + for (int i = 0; i < simpleMatcher.field_value_matcher_size(); i++) { + *root_field_matcher.mutable_matches_tuple()->add_field_value_matcher() = + simpleMatcher.field_value_matcher(i); + } + return matchFieldSimple(uidMap, event.getFieldValueMap(), root_field_matcher, root_field); +} + +vector<DimensionsValue> getDimensionKeys(const LogEvent& event, const FieldMatcher& matcher) { + vector<DimensionsValue> values; + findDimensionsValues(event.getFieldValueMap(), matcher, &values); + return values; +} } // namespace statsd } // namespace os } // namespace android diff --git a/cmds/statsd/src/matchers/matcher_util.h b/cmds/statsd/src/matchers/matcher_util.h index f54ab3648a19..704cb4c22e00 100644 --- a/cmds/statsd/src/matchers/matcher_util.h +++ b/cmds/statsd/src/matchers/matcher_util.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef MATCHER_UTIL_H -#define MATCHER_UTIL_H +#pragma once #include "logd/LogEvent.h" @@ -28,6 +27,7 @@ #include "frameworks/base/cmds/statsd/src/stats_log.pb.h" #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h" #include "stats_util.h" +#include "packages/UidMap.h" namespace android { namespace os { @@ -42,12 +42,14 @@ enum MatchingState { bool combinationMatch(const std::vector<int>& children, const LogicalOperation& operation, const std::vector<MatchingState>& matcherResults); -bool matchesSimple(const SimpleAtomMatcher& simpleMatcher, const LogEvent& wrapper); +bool matchFieldSimple(const UidMap& uidMap, const FieldValueMap& dimensionsMap, + const FieldValueMatcher& matcher, const Field& field); -std::vector<KeyValuePair> getDimensionKey(const LogEvent& event, - const std::vector<KeyMatcher>& dimensions); +bool matchesSimple(const UidMap& uidMap, + const SimpleAtomMatcher& simpleMatcher, const LogEvent& wrapper); + +std::vector<DimensionsValue> getDimensionKeys(const LogEvent& event, const FieldMatcher& matcher); } // namespace statsd } // namespace os } // namespace android -#endif // MATCHER_UTIL_H diff --git a/cmds/statsd/src/metrics/CountMetricProducer.cpp b/cmds/statsd/src/metrics/CountMetricProducer.cpp index 9031ed018df2..a24364df0fb2 100644 --- a/cmds/statsd/src/metrics/CountMetricProducer.cpp +++ b/cmds/statsd/src/metrics/CountMetricProducer.cpp @@ -20,6 +20,7 @@ #include "CountMetricProducer.h" #include "guardrail/StatsdStats.h" #include "stats_util.h" +#include "stats_log_util.h" #include <limits.h> #include <stdlib.h> @@ -42,7 +43,7 @@ namespace os { namespace statsd { // for StatsLogReport -const int FIELD_ID_NAME = 1; +const int FIELD_ID_ID = 1; const int FIELD_ID_START_REPORT_NANOS = 2; const int FIELD_ID_END_REPORT_NANOS = 3; const int FIELD_ID_COUNT_METRICS = 5; @@ -51,12 +52,6 @@ const int FIELD_ID_DATA = 1; // for CountMetricData const int FIELD_ID_DIMENSION = 1; const int FIELD_ID_BUCKET_INFO = 2; -// for KeyValuePair -const int FIELD_ID_KEY = 1; -const int FIELD_ID_VALUE_STR = 2; -const int FIELD_ID_VALUE_INT = 3; -const int FIELD_ID_VALUE_BOOL = 4; -const int FIELD_ID_VALUE_FLOAT = 5; // for CountBucketInfo const int FIELD_ID_START_BUCKET_NANOS = 1; const int FIELD_ID_END_BUCKET_NANOS = 2; @@ -66,7 +61,7 @@ CountMetricProducer::CountMetricProducer(const ConfigKey& key, const CountMetric const int conditionIndex, const sp<ConditionWizard>& wizard, const uint64_t startTimeNs) - : MetricProducer(metric.name(), key, startTimeNs, conditionIndex, wizard) { + : MetricProducer(metric.id(), key, startTimeNs, conditionIndex, wizard) { // TODO: evaluate initial conditions. and set mConditionMet. if (metric.has_bucket() && metric.bucket().has_bucket_size_millis()) { mBucketSizeNs = metric.bucket().bucket_size_millis() * 1000 * 1000; @@ -75,7 +70,7 @@ CountMetricProducer::CountMetricProducer(const ConfigKey& key, const CountMetric } // TODO: use UidMap if uid->pkg_name is required - mDimension.insert(mDimension.begin(), metric.dimension().begin(), metric.dimension().end()); + mDimensions = metric.dimensions(); if (metric.links().size() > 0) { mConditionLinks.insert(mConditionLinks.begin(), metric.links().begin(), @@ -83,7 +78,7 @@ CountMetricProducer::CountMetricProducer(const ConfigKey& key, const CountMetric mConditionSliced = true; } - VLOG("metric %s created. bucket size %lld start_time: %lld", metric.name().c_str(), + VLOG("metric %lld created. bucket size %lld start_time: %lld", (long long)metric.id(), (long long)mBucketSizeNs, (long long)mStartTimeNs); } @@ -92,43 +87,49 @@ CountMetricProducer::~CountMetricProducer() { } void CountMetricProducer::onSlicedConditionMayChangeLocked(const uint64_t eventTime) { - VLOG("Metric %s onSlicedConditionMayChange", mName.c_str()); + VLOG("Metric %lld onSlicedConditionMayChange", (long long)mMetricId); +} + +void CountMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs, StatsLogReport* report) { + flushIfNeededLocked(dumpTimeNs); + report->set_metric_id(mMetricId); + report->set_start_report_nanos(mStartTimeNs); + + auto count_metrics = report->mutable_count_metrics(); + for (const auto& counter : mPastBuckets) { + CountMetricData* metricData = count_metrics->add_data(); + *metricData->mutable_dimension() = counter.first.getDimensionsValue(); + for (const auto& bucket : counter.second) { + CountBucketInfo* bucketInfo = metricData->add_bucket_info(); + bucketInfo->set_start_bucket_nanos(bucket.mBucketStartNs); + bucketInfo->set_end_bucket_nanos(bucket.mBucketEndNs); + bucketInfo->set_count(bucket.mCount); + } + } } void CountMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs, ProtoOutputStream* protoOutput) { flushIfNeededLocked(dumpTimeNs); - protoOutput->write(FIELD_TYPE_STRING | FIELD_ID_NAME, mName); + protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_ID, (long long)mMetricId); protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_START_REPORT_NANOS, (long long)mStartTimeNs); long long protoToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_ID_COUNT_METRICS); - VLOG("metric %s dump report now...", mName.c_str()); + VLOG("metric %lld dump report now...",(long long)mMetricId); for (const auto& counter : mPastBuckets) { const HashableDimensionKey& hashableKey = counter.first; - const vector<KeyValuePair>& kvs = hashableKey.getKeyValuePairs(); VLOG(" dimension key %s", hashableKey.c_str()); long long wrapperToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DATA); - // First fill dimension (KeyValuePairs). - for (const auto& kv : kvs) { - long long dimensionToken = protoOutput->start( - FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DIMENSION); - protoOutput->write(FIELD_TYPE_INT32 | FIELD_ID_KEY, kv.key()); - if (kv.has_value_str()) { - protoOutput->write(FIELD_TYPE_STRING | FIELD_ID_VALUE_STR, kv.value_str()); - } else if (kv.has_value_int()) { - protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_VALUE_INT, kv.value_int()); - } else if (kv.has_value_bool()) { - protoOutput->write(FIELD_TYPE_BOOL | FIELD_ID_VALUE_BOOL, kv.value_bool()); - } else if (kv.has_value_float()) { - protoOutput->write(FIELD_TYPE_FLOAT | FIELD_ID_VALUE_FLOAT, kv.value_float()); - } - protoOutput->end(dimensionToken); - } + // First fill dimension. + long long dimensionToken = protoOutput->start( + FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DIMENSION); + writeDimensionsValueProtoToStream(hashableKey.getDimensionsValue(), protoOutput); + protoOutput->end(dimensionToken); // Then fill bucket_info (CountBucketInfo). for (const auto& bucket : counter.second) { @@ -157,7 +158,7 @@ void CountMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs, void CountMetricProducer::onConditionChangedLocked(const bool conditionMet, const uint64_t eventTime) { - VLOG("Metric %s onConditionChanged", mName.c_str()); + VLOG("Metric %lld onConditionChanged", (long long)mMetricId); mCondition = conditionMet; } @@ -169,11 +170,11 @@ bool CountMetricProducer::hitGuardRailLocked(const HashableDimensionKey& newKey) // 1. Report the tuple count if the tuple count > soft limit if (mCurrentSlicedCounter->size() > StatsdStats::kDimensionKeySizeSoftLimit - 1) { size_t newTupleCount = mCurrentSlicedCounter->size() + 1; - StatsdStats::getInstance().noteMetricDimensionSize(mConfigKey, mName, newTupleCount); + StatsdStats::getInstance().noteMetricDimensionSize(mConfigKey, mMetricId, newTupleCount); // 2. Don't add more tuples, we are above the allowed threshold. Drop the data. if (newTupleCount > StatsdStats::kDimensionKeySizeHardLimit) { - ALOGE("CountMetric %s dropping data for dimension key %s", mName.c_str(), - newKey.c_str()); + ALOGE("CountMetric %lld dropping data for dimension key %s", + (long long)mMetricId, newKey.c_str()); return true; } } @@ -183,7 +184,7 @@ bool CountMetricProducer::hitGuardRailLocked(const HashableDimensionKey& newKey) void CountMetricProducer::onMatchedLogEventInternalLocked( const size_t matcherIndex, const HashableDimensionKey& eventKey, - const map<string, HashableDimensionKey>& conditionKey, bool condition, + const ConditionKey& conditionKey, bool condition, const LogEvent& event) { uint64_t eventTimeNs = event.GetTimestampNs(); @@ -214,7 +215,7 @@ void CountMetricProducer::onMatchedLogEventInternalLocked( mCurrentSlicedCounter->find(eventKey)->second); } - VLOG("metric %s %s->%lld", mName.c_str(), eventKey.c_str(), + VLOG("metric %lld %s->%lld", (long long)mMetricId, eventKey.c_str(), (long long)(*mCurrentSlicedCounter)[eventKey]); } @@ -233,8 +234,8 @@ void CountMetricProducer::flushIfNeededLocked(const uint64_t& eventTimeNs) { info.mCount = counter.second; auto& bucketList = mPastBuckets[counter.first]; bucketList.push_back(info); - VLOG("metric %s, dump key value: %s -> %lld", mName.c_str(), counter.first.c_str(), - (long long)counter.second); + VLOG("metric %lld, dump key value: %s -> %lld", + (long long)mMetricId, counter.first.c_str(), (long long)counter.second); } for (auto& tracker : mAnomalyTrackers) { @@ -246,7 +247,7 @@ void CountMetricProducer::flushIfNeededLocked(const uint64_t& eventTimeNs) { uint64_t numBucketsForward = (eventTimeNs - mCurrentBucketStartTimeNs) / mBucketSizeNs; mCurrentBucketStartTimeNs = mCurrentBucketStartTimeNs + numBucketsForward * mBucketSizeNs; mCurrentBucketNum += numBucketsForward; - VLOG("metric %s: new bucket start time: %lld", mName.c_str(), + VLOG("metric %lld: new bucket start time: %lld", (long long)mMetricId, (long long)mCurrentBucketStartTimeNs); } diff --git a/cmds/statsd/src/metrics/CountMetricProducer.h b/cmds/statsd/src/metrics/CountMetricProducer.h index e32fc06de353..6087ae502e7b 100644 --- a/cmds/statsd/src/metrics/CountMetricProducer.h +++ b/cmds/statsd/src/metrics/CountMetricProducer.h @@ -51,12 +51,13 @@ public: protected: void onMatchedLogEventInternalLocked( const size_t matcherIndex, const HashableDimensionKey& eventKey, - const std::map<std::string, HashableDimensionKey>& conditionKey, bool condition, + const ConditionKey& conditionKey, bool condition, const LogEvent& event) override; private: void onDumpReportLocked(const uint64_t dumpTimeNs, android::util::ProtoOutputStream* protoOutput) override; + void onDumpReportLocked(const uint64_t dumpTimeNs, StatsLogReport* report) override; // Internal interface to handle condition change. void onConditionChangedLocked(const bool conditionMet, const uint64_t eventTime) override; diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.cpp b/cmds/statsd/src/metrics/DurationMetricProducer.cpp index 1c8f422eca61..99545ae9ddfc 100644 --- a/cmds/statsd/src/metrics/DurationMetricProducer.cpp +++ b/cmds/statsd/src/metrics/DurationMetricProducer.cpp @@ -20,6 +20,7 @@ #include "DurationMetricProducer.h" #include "guardrail/StatsdStats.h" #include "stats_util.h" +#include "stats_log_util.h" #include <limits.h> #include <stdlib.h> @@ -41,7 +42,7 @@ namespace os { namespace statsd { // for StatsLogReport -const int FIELD_ID_NAME = 1; +const int FIELD_ID_ID = 1; const int FIELD_ID_START_REPORT_NANOS = 2; const int FIELD_ID_END_REPORT_NANOS = 3; const int FIELD_ID_DURATION_METRICS = 6; @@ -50,12 +51,6 @@ const int FIELD_ID_DATA = 1; // for DurationMetricData const int FIELD_ID_DIMENSION = 1; const int FIELD_ID_BUCKET_INFO = 2; -// for KeyValuePair -const int FIELD_ID_KEY = 1; -const int FIELD_ID_VALUE_STR = 2; -const int FIELD_ID_VALUE_INT = 3; -const int FIELD_ID_VALUE_BOOL = 4; -const int FIELD_ID_VALUE_FLOAT = 5; // for DurationBucketInfo const int FIELD_ID_START_BUCKET_NANOS = 1; const int FIELD_ID_END_BUCKET_NANOS = 2; @@ -66,15 +61,15 @@ DurationMetricProducer::DurationMetricProducer(const ConfigKey& key, const Durat const size_t stopIndex, const size_t stopAllIndex, const bool nesting, const sp<ConditionWizard>& wizard, - const vector<KeyMatcher>& internalDimension, + const FieldMatcher& internalDimensions, const uint64_t startTimeNs) - : MetricProducer(metric.name(), key, startTimeNs, conditionIndex, wizard), + : MetricProducer(metric.id(), key, startTimeNs, conditionIndex, wizard), mAggregationType(metric.aggregation_type()), mStartIndex(startIndex), mStopIndex(stopIndex), mStopAllIndex(stopAllIndex), mNested(nesting), - mInternalDimension(internalDimension) { + mInternalDimensions(internalDimensions) { // TODO: The following boiler plate code appears in all MetricProducers, but we can't abstract // them in the base class, because the proto generated CountMetric, and DurationMetric are // not related. Maybe we should add a template in the future?? @@ -85,7 +80,7 @@ DurationMetricProducer::DurationMetricProducer(const ConfigKey& key, const Durat } // TODO: use UidMap if uid->pkg_name is required - mDimension.insert(mDimension.begin(), metric.dimension().begin(), metric.dimension().end()); + mDimensions = metric.dimensions(); if (metric.links().size() > 0) { mConditionLinks.insert(mConditionLinks.begin(), metric.links().begin(), @@ -93,7 +88,7 @@ DurationMetricProducer::DurationMetricProducer(const ConfigKey& key, const Durat mConditionSliced = true; } - VLOG("metric %s created. bucket size %lld start_time: %lld", metric.name().c_str(), + VLOG("metric %lld created. bucket size %lld start_time: %lld", (long long)metric.id(), (long long)mBucketSizeNs, (long long)mStartTimeNs); } @@ -101,15 +96,19 @@ DurationMetricProducer::~DurationMetricProducer() { VLOG("~DurationMetric() called"); } -sp<AnomalyTracker> DurationMetricProducer::createAnomalyTracker(const Alert &alert) { +sp<AnomalyTracker> DurationMetricProducer::addAnomalyTracker(const Alert &alert) { + std::lock_guard<std::mutex> lock(mMutex); if (alert.trigger_if_sum_gt() > alert.number_of_buckets() * mBucketSizeNs) { ALOGW("invalid alert: threshold (%lld) > possible recordable value (%d x %lld)", alert.trigger_if_sum_gt(), alert.number_of_buckets(), (long long)mBucketSizeNs); return nullptr; } - // TODO: return a DurationAnomalyTracker (which should sublclass AnomalyTracker) - return new AnomalyTracker(alert, mConfigKey); + sp<DurationAnomalyTracker> anomalyTracker = new DurationAnomalyTracker(alert, mConfigKey); + if (anomalyTracker != nullptr) { + mAnomalyTrackers.push_back(anomalyTracker); + } + return anomalyTracker; } unique_ptr<DurationTracker> DurationMetricProducer::createDurationTracker( @@ -117,17 +116,17 @@ unique_ptr<DurationTracker> DurationMetricProducer::createDurationTracker( switch (mAggregationType) { case DurationMetric_AggregationType_SUM: return make_unique<OringDurationTracker>( - mConfigKey, mName, eventKey, mWizard, mConditionTrackerIndex, mNested, + mConfigKey, mMetricId, eventKey, mWizard, mConditionTrackerIndex, mNested, mCurrentBucketStartTimeNs, mBucketSizeNs, mAnomalyTrackers); case DurationMetric_AggregationType_MAX_SPARSE: return make_unique<MaxDurationTracker>( - mConfigKey, mName, eventKey, mWizard, mConditionTrackerIndex, mNested, + mConfigKey, mMetricId, eventKey, mWizard, mConditionTrackerIndex, mNested, mCurrentBucketStartTimeNs, mBucketSizeNs, mAnomalyTrackers); } } void DurationMetricProducer::onSlicedConditionMayChangeLocked(const uint64_t eventTime) { - VLOG("Metric %s onSlicedConditionMayChange", mName.c_str()); + VLOG("Metric %lld onSlicedConditionMayChange", (long long)mMetricId); flushIfNeededLocked(eventTime); // Now for each of the on-going event, check if the condition has changed for them. for (auto& pair : mCurrentSlicedDuration) { @@ -137,7 +136,7 @@ void DurationMetricProducer::onSlicedConditionMayChangeLocked(const uint64_t eve void DurationMetricProducer::onConditionChangedLocked(const bool conditionMet, const uint64_t eventTime) { - VLOG("Metric %s onConditionChanged", mName.c_str()); + VLOG("Metric %lld onConditionChanged", (long long)mMetricId); mCondition = conditionMet; flushIfNeededLocked(eventTime); // TODO: need to populate the condition change time from the event which triggers the condition @@ -147,40 +146,46 @@ void DurationMetricProducer::onConditionChangedLocked(const bool conditionMet, } } +void DurationMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs, StatsLogReport* report) { + flushIfNeededLocked(dumpTimeNs); + report->set_metric_id(mMetricId); + report->set_start_report_nanos(mStartTimeNs); + + auto duration_metrics = report->mutable_duration_metrics(); + for (const auto& pair : mPastBuckets) { + DurationMetricData* metricData = duration_metrics->add_data(); + *metricData->mutable_dimension() = pair.first.getDimensionsValue(); + for (const auto& bucket : pair.second) { + auto bucketInfo = metricData->add_bucket_info(); + bucketInfo->set_start_bucket_nanos(bucket.mBucketStartNs); + bucketInfo->set_end_bucket_nanos(bucket.mBucketEndNs); + bucketInfo->set_duration_nanos(bucket.mDuration); + } + } +} + void DurationMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs, ProtoOutputStream* protoOutput) { flushIfNeededLocked(dumpTimeNs); - protoOutput->write(FIELD_TYPE_STRING | FIELD_ID_NAME, mName); + protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_ID, (long long)mMetricId); protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_START_REPORT_NANOS, (long long)mStartTimeNs); long long protoToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_ID_DURATION_METRICS); - VLOG("metric %s dump report now...", mName.c_str()); + VLOG("metric %lld dump report now...", (long long)mMetricId); for (const auto& pair : mPastBuckets) { const HashableDimensionKey& hashableKey = pair.first; - const vector<KeyValuePair>& kvs = hashableKey.getKeyValuePairs(); VLOG(" dimension key %s", hashableKey.c_str()); long long wrapperToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DATA); - // First fill dimension (KeyValuePairs). - for (const auto& kv : kvs) { - long long dimensionToken = protoOutput->start( - FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DIMENSION); - protoOutput->write(FIELD_TYPE_INT32 | FIELD_ID_KEY, kv.key()); - if (kv.has_value_str()) { - protoOutput->write(FIELD_TYPE_STRING | FIELD_ID_VALUE_STR, kv.value_str()); - } else if (kv.has_value_int()) { - protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_VALUE_INT, kv.value_int()); - } else if (kv.has_value_bool()) { - protoOutput->write(FIELD_TYPE_BOOL | FIELD_ID_VALUE_BOOL, kv.value_bool()); - } else if (kv.has_value_float()) { - protoOutput->write(FIELD_TYPE_FLOAT | FIELD_ID_VALUE_FLOAT, kv.value_float()); - } - protoOutput->end(dimensionToken); - } + // First fill dimension. + long long dimensionToken = protoOutput->start( + FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DIMENSION); + writeDimensionsValueProtoToStream(hashableKey.getDimensionsValue(), protoOutput); + protoOutput->end(dimensionToken); // Then fill bucket_info (DurationBucketInfo). for (const auto& bucket : pair.second) { @@ -232,11 +237,11 @@ bool DurationMetricProducer::hitGuardRailLocked(const HashableDimensionKey& newK // 1. Report the tuple count if the tuple count > soft limit if (mCurrentSlicedDuration.size() > StatsdStats::kDimensionKeySizeSoftLimit - 1) { size_t newTupleCount = mCurrentSlicedDuration.size() + 1; - StatsdStats::getInstance().noteMetricDimensionSize(mConfigKey, mName, newTupleCount); + StatsdStats::getInstance().noteMetricDimensionSize(mConfigKey, mMetricId, newTupleCount); // 2. Don't add more tuples, we are above the allowed threshold. Drop the data. if (newTupleCount > StatsdStats::kDimensionKeySizeHardLimit) { - ALOGE("DurationMetric %s dropping data for dimension key %s", mName.c_str(), - newKey.c_str()); + ALOGE("DurationMetric %lld dropping data for dimension key %s", + (long long)mMetricId, newKey.c_str()); return true; } } @@ -245,7 +250,7 @@ bool DurationMetricProducer::hitGuardRailLocked(const HashableDimensionKey& newK void DurationMetricProducer::onMatchedLogEventInternalLocked( const size_t matcherIndex, const HashableDimensionKey& eventKey, - const map<string, HashableDimensionKey>& conditionKeys, bool condition, + const ConditionKey& conditionKeys, bool condition, const LogEvent& event) { flushIfNeededLocked(event.GetTimestampNs()); @@ -256,7 +261,6 @@ void DurationMetricProducer::onMatchedLogEventInternalLocked( return; } - HashableDimensionKey atomKey(getDimensionKey(event, mInternalDimension)); if (mCurrentSlicedDuration.find(eventKey) == mCurrentSlicedDuration.end()) { if (hitGuardRailLocked(eventKey)) { @@ -267,11 +271,25 @@ void DurationMetricProducer::onMatchedLogEventInternalLocked( auto it = mCurrentSlicedDuration.find(eventKey); - if (matcherIndex == mStartIndex) { - it->second->noteStart(atomKey, condition, event.GetTimestampNs(), conditionKeys); - } else if (matcherIndex == mStopIndex) { - it->second->noteStop(atomKey, event.GetTimestampNs(), false); + std::vector<DimensionsValue> values = getDimensionKeys(event, mInternalDimensions); + if (values.empty()) { + if (matcherIndex == mStartIndex) { + it->second->noteStart(DEFAULT_DIMENSION_KEY, condition, + event.GetTimestampNs(), conditionKeys); + } else if (matcherIndex == mStopIndex) { + it->second->noteStop(DEFAULT_DIMENSION_KEY, event.GetTimestampNs(), false); + } + } else { + for (const DimensionsValue& value : values) { + if (matcherIndex == mStartIndex) { + it->second->noteStart(HashableDimensionKey(value), condition, + event.GetTimestampNs(), conditionKeys); + } else if (matcherIndex == mStopIndex) { + it->second->noteStop(HashableDimensionKey(value), event.GetTimestampNs(), false); + } + } } + } size_t DurationMetricProducer::byteSizeLocked() const { diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.h b/cmds/statsd/src/metrics/DurationMetricProducer.h index 7044b4b56258..e06b9a14563d 100644 --- a/cmds/statsd/src/metrics/DurationMetricProducer.h +++ b/cmds/statsd/src/metrics/DurationMetricProducer.h @@ -20,6 +20,7 @@ #include <unordered_map> #include <android/util/ProtoOutputStream.h> +#include "../anomaly/DurationAnomalyTracker.h" #include "../condition/ConditionTracker.h" #include "../matchers/matcher_util.h" #include "MetricProducer.h" @@ -41,21 +42,22 @@ public: const int conditionIndex, const size_t startIndex, const size_t stopIndex, const size_t stopAllIndex, const bool nesting, const sp<ConditionWizard>& wizard, - const vector<KeyMatcher>& internalDimension, const uint64_t startTimeNs); + const FieldMatcher& internalDimensions, const uint64_t startTimeNs); virtual ~DurationMetricProducer(); - virtual sp<AnomalyTracker> createAnomalyTracker(const Alert &alert) override; + sp<AnomalyTracker> addAnomalyTracker(const Alert &alert) override; protected: void onMatchedLogEventInternalLocked( const size_t matcherIndex, const HashableDimensionKey& eventKey, - const std::map<std::string, HashableDimensionKey>& conditionKeys, bool condition, + const ConditionKey& conditionKeys, bool condition, const LogEvent& event) override; private: void onDumpReportLocked(const uint64_t dumpTimeNs, android::util::ProtoOutputStream* protoOutput) override; + void onDumpReportLocked(const uint64_t dumpTimeNs, StatsLogReport* report) override; // Internal interface to handle condition change. void onConditionChangedLocked(const bool conditionMet, const uint64_t eventTime) override; @@ -84,7 +86,7 @@ private: const bool mNested; // The dimension from the atom predicate. e.g., uid, wakelock name. - const vector<KeyMatcher> mInternalDimension; + const FieldMatcher mInternalDimensions; // Save the past buckets and we can clear when the StatsLogReport is dumped. // TODO: Add a lock to mPastBuckets. @@ -98,6 +100,9 @@ private: std::unique_ptr<DurationTracker> createDurationTracker( const HashableDimensionKey& eventKey) const; + // This hides the base class's std::vector<sp<AnomalyTracker>> mAnomalyTrackers + std::vector<sp<DurationAnomalyTracker>> mAnomalyTrackers; + // Util function to check whether the specified dimension hits the guardrail. bool hitGuardRailLocked(const HashableDimensionKey& newKey); @@ -105,6 +110,7 @@ private: FRIEND_TEST(DurationMetricTrackerTest, TestNoCondition); FRIEND_TEST(DurationMetricTrackerTest, TestNonSlicedCondition); + FRIEND_TEST(WakelockDurationE2eTest, TestAggregatedPredicates); }; } // namespace statsd diff --git a/cmds/statsd/src/metrics/EventMetricProducer.cpp b/cmds/statsd/src/metrics/EventMetricProducer.cpp index 6a072b076bef..821d8ea48aef 100644 --- a/cmds/statsd/src/metrics/EventMetricProducer.cpp +++ b/cmds/statsd/src/metrics/EventMetricProducer.cpp @@ -41,7 +41,7 @@ namespace os { namespace statsd { // for StatsLogReport -const int FIELD_ID_NAME = 1; +const int FIELD_ID_ID = 1; const int FIELD_ID_START_REPORT_NANOS = 2; const int FIELD_ID_END_REPORT_NANOS = 3; const int FIELD_ID_EVENT_METRICS = 4; @@ -55,7 +55,7 @@ EventMetricProducer::EventMetricProducer(const ConfigKey& key, const EventMetric const int conditionIndex, const sp<ConditionWizard>& wizard, const uint64_t startTimeNs) - : MetricProducer(metric.name(), key, startTimeNs, conditionIndex, wizard) { + : MetricProducer(metric.id(), key, startTimeNs, conditionIndex, wizard) { if (metric.links().size() > 0) { mConditionLinks.insert(mConditionLinks.begin(), metric.links().begin(), metric.links().end()); @@ -64,7 +64,7 @@ EventMetricProducer::EventMetricProducer(const ConfigKey& key, const EventMetric startNewProtoOutputStreamLocked(); - VLOG("metric %s created. bucket size %lld start_time: %lld", metric.name().c_str(), + VLOG("metric %lld created. bucket size %lld start_time: %lld", (long long)metric.id(), (long long)mBucketSizeNs, (long long)mStartTimeNs); } @@ -96,14 +96,19 @@ std::unique_ptr<std::vector<uint8_t>> serializeProtoLocked(ProtoOutputStream& pr return buffer; } +void EventMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs, StatsLogReport* report) { + +} + void EventMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs, ProtoOutputStream* protoOutput) { - protoOutput->write(FIELD_TYPE_STRING | FIELD_ID_NAME, mName); + protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_ID, (long long)mMetricId); protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_START_REPORT_NANOS, (long long)mStartTimeNs); protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_END_REPORT_NANOS, (long long)dumpTimeNs); size_t bufferSize = mProto->size(); - VLOG("metric %s dump report now... proto size: %zu ", mName.c_str(), bufferSize); + VLOG("metric %lld dump report now... proto size: %zu ", + (long long)mMetricId, bufferSize); std::unique_ptr<std::vector<uint8_t>> buffer = serializeProtoLocked(*mProto); protoOutput->write(FIELD_TYPE_MESSAGE | FIELD_ID_EVENT_METRICS, @@ -115,13 +120,13 @@ void EventMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs, void EventMetricProducer::onConditionChangedLocked(const bool conditionMet, const uint64_t eventTime) { - VLOG("Metric %s onConditionChanged", mName.c_str()); + VLOG("Metric %lld onConditionChanged", (long long)mMetricId); mCondition = conditionMet; } void EventMetricProducer::onMatchedLogEventInternalLocked( const size_t matcherIndex, const HashableDimensionKey& eventKey, - const std::map<std::string, HashableDimensionKey>& conditionKey, bool condition, + const ConditionKey& conditionKey, bool condition, const LogEvent& event) { if (!condition) { return; diff --git a/cmds/statsd/src/metrics/EventMetricProducer.h b/cmds/statsd/src/metrics/EventMetricProducer.h index 6120ad8fd6da..a57b07d6648e 100644 --- a/cmds/statsd/src/metrics/EventMetricProducer.h +++ b/cmds/statsd/src/metrics/EventMetricProducer.h @@ -46,11 +46,12 @@ protected: private: void onMatchedLogEventInternalLocked( const size_t matcherIndex, const HashableDimensionKey& eventKey, - const std::map<std::string, HashableDimensionKey>& conditionKey, bool condition, + const ConditionKey& conditionKey, bool condition, const LogEvent& event) override; void onDumpReportLocked(const uint64_t dumpTimeNs, android::util::ProtoOutputStream* protoOutput) override; + void onDumpReportLocked(const uint64_t dumpTimeNs, StatsLogReport* report) override; // Internal interface to handle condition change. void onConditionChangedLocked(const bool conditionMet, const uint64_t eventTime) override; diff --git a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp index 47cca0e23892..eaf1de238e8d 100644 --- a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp +++ b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp @@ -19,6 +19,8 @@ #include "GaugeMetricProducer.h" #include "guardrail/StatsdStats.h" +#include "dimension.h" +#include "stats_log_util.h" #include <cutils/log.h> @@ -42,7 +44,7 @@ namespace os { namespace statsd { // for StatsLogReport -const int FIELD_ID_NAME = 1; +const int FIELD_ID_ID = 1; const int FIELD_ID_START_REPORT_NANOS = 2; const int FIELD_ID_END_REPORT_NANOS = 3; const int FIELD_ID_GAUGE_METRICS = 8; @@ -51,12 +53,6 @@ const int FIELD_ID_DATA = 1; // for GaugeMetricData const int FIELD_ID_DIMENSION = 1; const int FIELD_ID_BUCKET_INFO = 2; -// for KeyValuePair -const int FIELD_ID_KEY = 1; -const int FIELD_ID_VALUE_STR = 2; -const int FIELD_ID_VALUE_INT = 3; -const int FIELD_ID_VALUE_BOOL = 4; -const int FIELD_ID_VALUE_FLOAT = 5; // for GaugeBucketInfo const int FIELD_ID_START_BUCKET_NANOS = 1; const int FIELD_ID_END_BUCKET_NANOS = 2; @@ -67,22 +63,22 @@ GaugeMetricProducer::GaugeMetricProducer(const ConfigKey& key, const GaugeMetric const sp<ConditionWizard>& wizard, const int atomTagId, const int pullTagId, const uint64_t startTimeNs, shared_ptr<StatsPullerManager> statsPullerManager) - : MetricProducer(metric.name(), key, startTimeNs, conditionIndex, wizard), + : MetricProducer(metric.id(), key, startTimeNs, conditionIndex, wizard), mStatsPullerManager(statsPullerManager), mPullTagId(pullTagId), mAtomTagId(atomTagId) { + mCurrentSlicedBucket = std::make_shared<DimToGaugeFieldsMap>(); + mCurrentSlicedBucketForAnomaly = std::make_shared<DimToValMap>(); if (metric.has_bucket() && metric.bucket().has_bucket_size_millis()) { mBucketSizeNs = metric.bucket().bucket_size_millis() * 1000 * 1000; } else { mBucketSizeNs = kDefaultGaugemBucketSizeNs; } - for (int i = 0; i < metric.gauge_fields().field_num_size(); i++) { - mGaugeFields.push_back(metric.gauge_fields().field_num(i)); - } + mFieldFilter = metric.gauge_fields_filter(); // TODO: use UidMap if uid->pkg_name is required - mDimension.insert(mDimension.begin(), metric.dimension().begin(), metric.dimension().end()); + mDimensions = metric.dimensions(); if (metric.links().size() > 0) { mConditionLinks.insert(mConditionLinks.begin(), metric.links().begin(), @@ -93,10 +89,10 @@ GaugeMetricProducer::GaugeMetricProducer(const ConfigKey& key, const GaugeMetric // Kicks off the puller immediately. if (mPullTagId != -1) { mStatsPullerManager->RegisterReceiver(mPullTagId, this, - metric.bucket().bucket_size_millis()); + metric.bucket().bucket_size_millis()); } - VLOG("metric %s created. bucket size %lld start_time: %lld", metric.name().c_str(), + VLOG("metric %lld created. bucket size %lld start_time: %lld", (long long)metric.id(), (long long)mBucketSizeNs, (long long)mStartTimeNs); } @@ -116,40 +112,32 @@ GaugeMetricProducer::~GaugeMetricProducer() { } } +void GaugeMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs, StatsLogReport* report) { + flushIfNeededLocked(dumpTimeNs); +} + void GaugeMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs, ProtoOutputStream* protoOutput) { - VLOG("gauge metric %s dump report now...", mName.c_str()); + VLOG("gauge metric %lld report now...", (long long)mMetricId); flushIfNeededLocked(dumpTimeNs); - protoOutput->write(FIELD_TYPE_STRING | FIELD_ID_NAME, mName); + protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_ID, (long long)mMetricId); protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_START_REPORT_NANOS, (long long)mStartTimeNs); long long protoToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_ID_GAUGE_METRICS); for (const auto& pair : mPastBuckets) { const HashableDimensionKey& hashableKey = pair.first; - const vector<KeyValuePair>& kvs = hashableKey.getKeyValuePairs(); VLOG(" dimension key %s", hashableKey.c_str()); long long wrapperToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DATA); - // First fill dimension (KeyValuePairs). - for (const auto& kv : kvs) { - long long dimensionToken = protoOutput->start( - FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DIMENSION); - protoOutput->write(FIELD_TYPE_INT32 | FIELD_ID_KEY, kv.key()); - if (kv.has_value_str()) { - protoOutput->write(FIELD_TYPE_STRING | FIELD_ID_VALUE_STR, kv.value_str()); - } else if (kv.has_value_int()) { - protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_VALUE_INT, kv.value_int()); - } else if (kv.has_value_bool()) { - protoOutput->write(FIELD_TYPE_BOOL | FIELD_ID_VALUE_BOOL, kv.value_bool()); - } else if (kv.has_value_float()) { - protoOutput->write(FIELD_TYPE_FLOAT | FIELD_ID_VALUE_FLOAT, kv.value_float()); - } - protoOutput->end(dimensionToken); - } + // First fill dimension. + long long dimensionToken = protoOutput->start( + FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DIMENSION); + writeDimensionsValueProtoToStream(hashableKey.getDimensionsValue(), protoOutput); + protoOutput->end(dimensionToken); // Then fill bucket_info (GaugeBucketInfo). for (const auto& bucket : pair.second) { @@ -160,25 +148,11 @@ void GaugeMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs, protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_END_BUCKET_NANOS, (long long)bucket.mBucketEndNs); long long atomToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_ID_ATOM); - long long eventToken = protoOutput->start(FIELD_TYPE_MESSAGE | mAtomTagId); - for (const auto& pair : bucket.mEvent->kv) { - if (pair.has_value_int()) { - protoOutput->write(FIELD_TYPE_INT32 | pair.key(), pair.value_int()); - } else if (pair.has_value_long()) { - protoOutput->write(FIELD_TYPE_INT64 | pair.key(), pair.value_long()); - } else if (pair.has_value_str()) { - protoOutput->write(FIELD_TYPE_STRING | pair.key(), pair.value_str()); - } else if (pair.has_value_long()) { - protoOutput->write(FIELD_TYPE_FLOAT | pair.key(), pair.value_float()); - } else if (pair.has_value_bool()) { - protoOutput->write(FIELD_TYPE_BOOL | pair.key(), pair.value_bool()); - } - } - protoOutput->end(eventToken); + writeFieldValueTreeToStream(*bucket.mGaugeFields, protoOutput); protoOutput->end(atomToken); protoOutput->end(bucketInfoToken); - VLOG("\t bucket [%lld - %lld] content: %s", (long long)bucket.mBucketStartNs, - (long long)bucket.mBucketEndNs, bucket.mEvent->ToString().c_str()); + VLOG("\t bucket [%lld - %lld] includes %d gauge fields.", (long long)bucket.mBucketStartNs, + (long long)bucket.mBucketEndNs, (int)bucket.mGaugeFields->size()); } protoOutput->end(wrapperToken); } @@ -192,7 +166,7 @@ void GaugeMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs, void GaugeMetricProducer::onConditionChangedLocked(const bool conditionMet, const uint64_t eventTime) { - VLOG("Metric %s onConditionChanged", mName.c_str()); + VLOG("Metric %lld onConditionChanged", (long long)mMetricId); flushIfNeededLocked(eventTime); mCondition = conditionMet; @@ -220,21 +194,16 @@ void GaugeMetricProducer::onConditionChangedLocked(const bool conditionMet, } void GaugeMetricProducer::onSlicedConditionMayChangeLocked(const uint64_t eventTime) { - VLOG("Metric %s onSlicedConditionMayChange", mName.c_str()); + VLOG("Metric %lld onSlicedConditionMayChange", (long long)mMetricId); } -shared_ptr<EventKV> GaugeMetricProducer::getGauge(const LogEvent& event) { - shared_ptr<EventKV> ret = make_shared<EventKV>(); - if (mGaugeFields.size() == 0) { - for (int i = 1; i <= event.size(); i++) { - ret->kv.push_back(event.GetKeyValueProto(i)); - } - } else { - for (int i = 0; i < (int)mGaugeFields.size(); i++) { - ret->kv.push_back(event.GetKeyValueProto(mGaugeFields[i])); - } +std::shared_ptr<FieldValueMap> GaugeMetricProducer::getGaugeFields(const LogEvent& event) { + std::shared_ptr<FieldValueMap> gaugeFields = + std::make_shared<FieldValueMap>(event.getFieldValueMap()); + if (!mFieldFilter.include_all()) { + filterFields(mFieldFilter.fields(), gaugeFields.get()); } - return ret; + return gaugeFields; } void GaugeMetricProducer::onDataPulled(const std::vector<std::shared_ptr<LogEvent>>& allData) { @@ -254,11 +223,11 @@ bool GaugeMetricProducer::hitGuardRailLocked(const HashableDimensionKey& newKey) // 1. Report the tuple count if the tuple count > soft limit if (mCurrentSlicedBucket->size() > StatsdStats::kDimensionKeySizeSoftLimit - 1) { size_t newTupleCount = mCurrentSlicedBucket->size() + 1; - StatsdStats::getInstance().noteMetricDimensionSize(mConfigKey, mName, newTupleCount); + StatsdStats::getInstance().noteMetricDimensionSize(mConfigKey, mMetricId, newTupleCount); // 2. Don't add more tuples, we are above the allowed threshold. Drop the data. if (newTupleCount > StatsdStats::kDimensionKeySizeHardLimit) { - ALOGE("GaugeMetric %s dropping data for dimension key %s", mName.c_str(), - newKey.c_str()); + ALOGE("GaugeMetric %lld dropping data for dimension key %s", + (long long)mMetricId, newKey.c_str()); return true; } } @@ -268,7 +237,7 @@ bool GaugeMetricProducer::hitGuardRailLocked(const HashableDimensionKey& newKey) void GaugeMetricProducer::onMatchedLogEventInternalLocked( const size_t matcherIndex, const HashableDimensionKey& eventKey, - const map<string, HashableDimensionKey>& conditionKey, bool condition, + const ConditionKey& conditionKey, bool condition, const LogEvent& event) { if (condition == false) { return; @@ -285,21 +254,21 @@ void GaugeMetricProducer::onMatchedLogEventInternalLocked( if (mCurrentSlicedBucket->find(eventKey) != mCurrentSlicedBucket->end()) { return; } - shared_ptr<EventKV> gauge = getGauge(event); + std::shared_ptr<FieldValueMap> gaugeFields = getGaugeFields(event); if (hitGuardRailLocked(eventKey)) { return; } - (*mCurrentSlicedBucket)[eventKey] = gauge; + (*mCurrentSlicedBucket)[eventKey] = gaugeFields; // Anomaly detection on gauge metric only works when there is one numeric // field specified. if (mAnomalyTrackers.size() > 0) { - if (gauge->kv.size() == 1) { - KeyValuePair pair = gauge->kv[0]; + if (gaugeFields->size() == 1) { + const DimensionsValue& dimensionsValue = gaugeFields->begin()->second; long gaugeVal = 0; - if (pair.has_value_int()) { - gaugeVal = (long)pair.value_int(); - } else if (pair.has_value_long()) { - gaugeVal = pair.value_long(); + if (dimensionsValue.has_value_int()) { + gaugeVal = (long)dimensionsValue.value_int(); + } else if (dimensionsValue.has_value_long()) { + gaugeVal = dimensionsValue.value_long(); } for (auto& tracker : mAnomalyTrackers) { tracker->detectAndDeclareAnomaly(eventTimeNs, mCurrentBucketNum, eventKey, @@ -313,12 +282,12 @@ void GaugeMetricProducer::updateCurrentSlicedBucketForAnomaly() { mCurrentSlicedBucketForAnomaly->clear(); status_t err = NO_ERROR; for (const auto& slice : *mCurrentSlicedBucket) { - KeyValuePair pair = slice.second->kv[0]; + const DimensionsValue& dimensionsValue = slice.second->begin()->second; long gaugeVal = 0; - if (pair.has_value_int()) { - gaugeVal = (long)pair.value_int(); - } else if (pair.has_value_long()) { - gaugeVal = pair.value_long(); + if (dimensionsValue.has_value_int()) { + gaugeVal = (long)dimensionsValue.value_int(); + } else if (dimensionsValue.has_value_long()) { + gaugeVal = dimensionsValue.value_long(); } (*mCurrentSlicedBucketForAnomaly)[slice.first] = gaugeVal; } @@ -342,11 +311,11 @@ void GaugeMetricProducer::flushIfNeededLocked(const uint64_t& eventTimeNs) { info.mBucketNum = mCurrentBucketNum; for (const auto& slice : *mCurrentSlicedBucket) { - info.mEvent = slice.second; + info.mGaugeFields = slice.second; auto& bucketList = mPastBuckets[slice.first]; bucketList.push_back(info); - VLOG("gauge metric %s, dump key value: %s -> %s", mName.c_str(), - slice.first.c_str(), slice.second->ToString().c_str()); + VLOG("gauge metric %lld, dump key value: %s", + (long long)mMetricId, slice.first.c_str()); } // Reset counters @@ -357,13 +326,13 @@ void GaugeMetricProducer::flushIfNeededLocked(const uint64_t& eventTimeNs) { } } - mCurrentSlicedBucket = std::make_shared<DimToEventKVMap>(); + mCurrentSlicedBucket = std::make_shared<DimToGaugeFieldsMap>(); // Adjusts the bucket start time int64_t numBucketsForward = (eventTimeNs - mCurrentBucketStartTimeNs) / mBucketSizeNs; mCurrentBucketStartTimeNs = mCurrentBucketStartTimeNs + numBucketsForward * mBucketSizeNs; mCurrentBucketNum += numBucketsForward; - VLOG("metric %s: new bucket start time: %lld", mName.c_str(), + VLOG("metric %lld: new bucket start time: %lld", (long long)mMetricId, (long long)mCurrentBucketStartTimeNs); } diff --git a/cmds/statsd/src/metrics/GaugeMetricProducer.h b/cmds/statsd/src/metrics/GaugeMetricProducer.h index 19d51e8c517e..2a6401d6dcac 100644 --- a/cmds/statsd/src/metrics/GaugeMetricProducer.h +++ b/cmds/statsd/src/metrics/GaugeMetricProducer.h @@ -35,10 +35,13 @@ namespace statsd { struct GaugeBucket { int64_t mBucketStartNs; int64_t mBucketEndNs; - std::shared_ptr<EventKV> mEvent; + std::shared_ptr<FieldValueMap> mGaugeFields; uint64_t mBucketNum; }; +typedef std::unordered_map<HashableDimensionKey, std::shared_ptr<FieldValueMap>> + DimToGaugeFieldsMap; + // This gauge metric producer first register the puller to automatically pull the gauge at the // beginning of each bucket. If the condition is met, insert it to the bucket info. Otherwise // proactively pull the gauge when the condition is changed to be true. Therefore, the gauge metric @@ -57,12 +60,13 @@ public: protected: void onMatchedLogEventInternalLocked( const size_t matcherIndex, const HashableDimensionKey& eventKey, - const std::map<std::string, HashableDimensionKey>& conditionKey, bool condition, + const ConditionKey& conditionKey, bool condition, const LogEvent& event) override; private: void onDumpReportLocked(const uint64_t dumpTimeNs, android::util::ProtoOutputStream* protoOutput) override; + void onDumpReportLocked(const uint64_t dumpTimeNs, StatsLogReport* report) override; // for testing GaugeMetricProducer(const ConfigKey& key, const GaugeMetric& gaugeMetric, @@ -94,10 +98,10 @@ private: std::unordered_map<HashableDimensionKey, std::vector<GaugeBucket>> mPastBuckets; // The current bucket. - std::shared_ptr<DimToEventKVMap> mCurrentSlicedBucket = std::make_shared<DimToEventKVMap>(); + std::shared_ptr<DimToGaugeFieldsMap> mCurrentSlicedBucket; // The current bucket for anomaly detection. - std::shared_ptr<DimToValMap> mCurrentSlicedBucketForAnomaly = std::make_shared<DimToValMap>(); + std::shared_ptr<DimToValMap> mCurrentSlicedBucketForAnomaly; // Translate Atom based bucket to single numeric value bucket for anomaly void updateCurrentSlicedBucketForAnomaly(); @@ -105,10 +109,10 @@ private: int mAtomTagId; // Whitelist of fields to report. Empty means all are reported. - std::vector<int> mGaugeFields; + FieldFilter mFieldFilter; // apply a whitelist on the original input - std::shared_ptr<EventKV> getGauge(const LogEvent& event); + std::shared_ptr<FieldValueMap> getGaugeFields(const LogEvent& event); // Util function to check whether the specified dimension hits the guardrail. bool hitGuardRailLocked(const HashableDimensionKey& newKey); diff --git a/cmds/statsd/src/metrics/MetricProducer.cpp b/cmds/statsd/src/metrics/MetricProducer.cpp index 528690832f03..d620a7ebee00 100644 --- a/cmds/statsd/src/metrics/MetricProducer.cpp +++ b/cmds/statsd/src/metrics/MetricProducer.cpp @@ -28,24 +28,14 @@ void MetricProducer::onMatchedLogEventLocked(const size_t matcherIndex, const Lo return; } - HashableDimensionKey eventKey; - - if (mDimension.size() > 0) { - vector<KeyValuePair> key = getDimensionKey(event, mDimension); - eventKey = HashableDimensionKey(key); - } else { - eventKey = DEFAULT_DIMENSION_KEY; - } - bool condition; - - map<string, HashableDimensionKey> conditionKeys; + ConditionKey conditionKey; if (mConditionSliced) { for (const auto& link : mConditionLinks) { - HashableDimensionKey conditionKey = getDimensionKeyForCondition(event, link); - conditionKeys[link.condition()] = conditionKey; + conditionKey.insert(std::make_pair(link.condition(), + getDimensionKeysForCondition(event, link))); } - if (mWizard->query(mConditionTrackerIndex, conditionKeys) != ConditionState::kTrue) { + if (mWizard->query(mConditionTrackerIndex, conditionKey) != ConditionState::kTrue) { condition = false; } else { condition = true; @@ -53,7 +43,17 @@ void MetricProducer::onMatchedLogEventLocked(const size_t matcherIndex, const Lo } else { condition = mCondition; } - onMatchedLogEventInternalLocked(matcherIndex, eventKey, conditionKeys, condition, event); + + if (mDimensions.child_size() > 0) { + vector<DimensionsValue> dimensionValues = getDimensionKeys(event, mDimensions); + for (const DimensionsValue& dimensionValue : dimensionValues) { + onMatchedLogEventInternalLocked( + matcherIndex, HashableDimensionKey(dimensionValue), conditionKey, condition, event); + } + } else { + onMatchedLogEventInternalLocked( + matcherIndex, DEFAULT_DIMENSION_KEY, conditionKey, condition, event); + } } } // namespace statsd diff --git a/cmds/statsd/src/metrics/MetricProducer.h b/cmds/statsd/src/metrics/MetricProducer.h index 85ef4ada07d8..3779c4487d23 100644 --- a/cmds/statsd/src/metrics/MetricProducer.h +++ b/cmds/statsd/src/metrics/MetricProducer.h @@ -39,9 +39,9 @@ namespace statsd { // be a no-op. class MetricProducer : public virtual PackageInfoListener { public: - MetricProducer(const std::string& name, const ConfigKey& key, const int64_t startTimeNs, + MetricProducer(const int64_t& metricId, const ConfigKey& key, const int64_t startTimeNs, const int conditionIndex, const sp<ConditionWizard>& wizard) - : mName(name), + : mMetricId(metricId), mConfigKey(key), mStartTimeNs(startTimeNs), mCurrentBucketStartTimeNs(startTimeNs), @@ -50,6 +50,7 @@ public: mConditionSliced(false), mWizard(wizard), mConditionTrackerIndex(conditionIndex){}; + virtual ~MetricProducer(){}; void notifyAppUpgrade(const string& apk, const int uid, const int64_t version) override{ @@ -90,6 +91,10 @@ public: std::lock_guard<std::mutex> lock(mMutex); return onDumpReportLocked(dumpTimeNs, protoOutput); } + void onDumpReport(const uint64_t dumpTimeNs, StatsLogReport* report) { + std::lock_guard<std::mutex> lock(mMutex); + return onDumpReportLocked(dumpTimeNs, report); + } // Returns the memory in bytes currently used to store this metric's data. Does not change // state. @@ -98,13 +103,13 @@ public: return byteSizeLocked(); } - virtual sp<AnomalyTracker> createAnomalyTracker(const Alert &alert) { - return new AnomalyTracker(alert, mConfigKey); - } - - void addAnomalyTracker(sp<AnomalyTracker> tracker) { + virtual sp<AnomalyTracker> addAnomalyTracker(const Alert &alert) { std::lock_guard<std::mutex> lock(mMutex); - mAnomalyTrackers.push_back(tracker); + sp<AnomalyTracker> anomalyTracker = new AnomalyTracker(alert, mConfigKey); + if (anomalyTracker != nullptr) { + mAnomalyTrackers.push_back(anomalyTracker); + } + return anomalyTracker; } int64_t getBuckeSizeInNs() const { @@ -112,14 +117,19 @@ public: return mBucketSizeNs; } + inline const int64_t& getMetricId() { + return mMetricId; + } + protected: virtual void onConditionChangedLocked(const bool condition, const uint64_t eventTime) = 0; virtual void onSlicedConditionMayChangeLocked(const uint64_t eventTime) = 0; virtual void onDumpReportLocked(const uint64_t dumpTimeNs, android::util::ProtoOutputStream* protoOutput) = 0; + virtual void onDumpReportLocked(const uint64_t dumpTimeNs, StatsLogReport* report) = 0; virtual size_t byteSizeLocked() const = 0; - const std::string mName; + const int64_t mMetricId; const ConfigKey mConfigKey; @@ -140,7 +150,7 @@ protected: int mConditionTrackerIndex; - std::vector<KeyMatcher> mDimension; // The dimension defined in statsd_config + FieldMatcher mDimensions; // The dimension defined in statsd_config std::vector<MetricConditionLink> mConditionLinks; @@ -163,7 +173,7 @@ protected: */ virtual void onMatchedLogEventInternalLocked( const size_t matcherIndex, const HashableDimensionKey& eventKey, - const std::map<std::string, HashableDimensionKey>& conditionKey, bool condition, + const ConditionKey& conditionKey, bool condition, const LogEvent& event) = 0; // Consume the parsed stats log entry that already matched the "what" of the metric. diff --git a/cmds/statsd/src/metrics/MetricsManager.cpp b/cmds/statsd/src/metrics/MetricsManager.cpp index 231bd8e4df99..7f0239fd334e 100644 --- a/cmds/statsd/src/metrics/MetricsManager.cpp +++ b/cmds/statsd/src/metrics/MetricsManager.cpp @@ -49,9 +49,9 @@ MetricsManager::MetricsManager(const ConfigKey& key, const StatsdConfig& config, const long timeBaseSec, sp<UidMap> uidMap) : mConfigKey(key), mUidMap(uidMap) { mConfigValid = - initStatsdConfig(key, config, timeBaseSec, mTagIds, mAllAtomMatchers, mAllConditionTrackers, + initStatsdConfig(key, config, *uidMap, timeBaseSec, mTagIds, mAllAtomMatchers, mAllConditionTrackers, mAllMetricProducers, mAllAnomalyTrackers, mConditionToMetricMap, - mTrackerToMetricMap, mTrackerToConditionMap); + mTrackerToMetricMap, mTrackerToConditionMap, mNoReportMetricIds); if (!config.has_log_source()) { // TODO(b/70794411): uncomment the following line and remove the hard coded log source @@ -142,15 +142,25 @@ void MetricsManager::onUidMapReceived() { initLogSourceWhiteList(); } +void MetricsManager::onDumpReport(const uint64_t& dumpTimeStampNs, ConfigMetricsReport* report) { + for (const auto& producer : mAllMetricProducers) { + if (mNoReportMetricIds.find(producer->getMetricId()) == mNoReportMetricIds.end()) { + producer->onDumpReport(dumpTimeStampNs, report->add_metrics()); + } + } +} + void MetricsManager::onDumpReport(ProtoOutputStream* protoOutput) { VLOG("=========================Metric Reports Start=========================="); uint64_t dumpTimeStampNs = time(nullptr) * NS_PER_SEC; // one StatsLogReport per MetricProduer - for (auto& metric : mAllMetricProducers) { - long long token = - protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_METRICS); - metric->onDumpReport(dumpTimeStampNs, protoOutput); - protoOutput->end(token); + for (const auto& producer : mAllMetricProducers) { + if (mNoReportMetricIds.find(producer->getMetricId()) == mNoReportMetricIds.end()) { + long long token = + protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_METRICS); + producer->onDumpReport(dumpTimeStampNs, protoOutput); + protoOutput->end(token); + } } VLOG("=========================Metric Reports End=========================="); } @@ -167,6 +177,22 @@ void MetricsManager::onLogEvent(const LogEvent& event) { VLOG("log source %d not on the whitelist", event.GetUid()); return; } + } else { // Check that app hook fields are valid. + // TODO: Find a way to make these checks easier to maintain if the app hooks get changed. + + // Label is 2nd from last field and must be from [0, 15]. + status_t err = NO_ERROR; + long label = event.GetLong(event.size()-1, &err); + if (err != NO_ERROR || label < 0 || label > 15) { + VLOG("App hook does not have valid label %ld", label); + return; + } + // The state must be from 0,3. This part of code must be manually updated. + long apphookState = event.GetLong(event.size(), &err); + if (err != NO_ERROR || apphookState < 0 || apphookState > 3) { + VLOG("App hook does not have valid state %ld", apphookState); + return; + } } int tagId = event.GetTagId(); @@ -234,7 +260,7 @@ void MetricsManager::onLogEvent(const LogEvent& event) { for (size_t i = 0; i < mAllAtomMatchers.size(); i++) { if (matcherCache[i] == MatchingState::kMatched) { StatsdStats::getInstance().noteMatcherMatched(mConfigKey, - mAllAtomMatchers[i]->getName()); + mAllAtomMatchers[i]->getId()); auto pair = mTrackerToMetricMap.find(i); if (pair != mTrackerToMetricMap.end()) { auto& metricList = pair->second; diff --git a/cmds/statsd/src/metrics/MetricsManager.h b/cmds/statsd/src/metrics/MetricsManager.h index 8faa75d34be9..8175cd2c9889 100644 --- a/cmds/statsd/src/metrics/MetricsManager.h +++ b/cmds/statsd/src/metrics/MetricsManager.h @@ -45,8 +45,9 @@ public: void onLogEvent(const LogEvent& event); - void onAnomalyAlarmFired(const uint64_t timestampNs, - unordered_set<sp<const AnomalyAlarm>, SpHash<AnomalyAlarm>>& anomalySet); + void onAnomalyAlarmFired( + const uint64_t timestampNs, + unordered_set<sp<const AnomalyAlarm>, SpHash<AnomalyAlarm>>& anomalySet); void setAnomalyMonitor(const sp<AnomalyMonitor>& anomalyMonitor); @@ -58,6 +59,7 @@ public: // Config source owner can call onDumpReport() to get all the metrics collected. virtual void onDumpReport(android::util::ProtoOutputStream* protoOutput); + virtual void onDumpReport(const uint64_t& dumpTimeStampNs, ConfigMetricsReport* report); // Computes the total byte size of all metrics managed by a single config source. // Does not change the state. @@ -128,6 +130,12 @@ private: std::unordered_map<int, std::vector<int>> mConditionToMetricMap; void initLogSourceWhiteList(); + + // The metrics that don't need to be uploaded or even reported. + std::set<int64_t> mNoReportMetricIds; + + FRIEND_TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensions); + FRIEND_TEST(MetricConditionLinkE2eTest, TestMultiplePredicatesAndLinks); }; } // namespace statsd diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.cpp b/cmds/statsd/src/metrics/ValueMetricProducer.cpp index 40aed7bcf934..ea90170a53e1 100644 --- a/cmds/statsd/src/metrics/ValueMetricProducer.cpp +++ b/cmds/statsd/src/metrics/ValueMetricProducer.cpp @@ -19,6 +19,7 @@ #include "ValueMetricProducer.h" #include "guardrail/StatsdStats.h" +#include "stats_log_util.h" #include <cutils/log.h> #include <limits.h> @@ -45,7 +46,7 @@ namespace os { namespace statsd { // for StatsLogReport -const int FIELD_ID_NAME = 1; +const int FIELD_ID_ID = 1; const int FIELD_ID_START_REPORT_NANOS = 2; const int FIELD_ID_END_REPORT_NANOS = 3; const int FIELD_ID_VALUE_METRICS = 7; @@ -54,12 +55,6 @@ const int FIELD_ID_DATA = 1; // for ValueMetricData const int FIELD_ID_DIMENSION = 1; const int FIELD_ID_BUCKET_INFO = 2; -// for KeyValuePair -const int FIELD_ID_KEY = 1; -const int FIELD_ID_VALUE_STR = 2; -const int FIELD_ID_VALUE_INT = 3; -const int FIELD_ID_VALUE_BOOL = 4; -const int FIELD_ID_VALUE_FLOAT = 5; // for ValueBucketInfo const int FIELD_ID_START_BUCKET_NANOS = 1; const int FIELD_ID_END_BUCKET_NANOS = 2; @@ -73,7 +68,7 @@ ValueMetricProducer::ValueMetricProducer(const ConfigKey& key, const ValueMetric const sp<ConditionWizard>& wizard, const int pullTagId, const uint64_t startTimeNs, shared_ptr<StatsPullerManager> statsPullerManager) - : MetricProducer(metric.name(), key, startTimeNs, conditionIndex, wizard), + : MetricProducer(metric.id(), key, startTimeNs, conditionIndex, wizard), mValueField(metric.value_field()), mStatsPullerManager(statsPullerManager), mPullTagId(pullTagId) { @@ -84,7 +79,7 @@ ValueMetricProducer::ValueMetricProducer(const ConfigKey& key, const ValueMetric mBucketSizeNs = kDefaultBucketSizeMillis * 1000 * 1000; } - mDimension.insert(mDimension.begin(), metric.dimension().begin(), metric.dimension().end()); + mDimensions = metric.dimensions(); if (metric.links().size() > 0) { mConditionLinks.insert(mConditionLinks.begin(), metric.links().begin(), @@ -97,8 +92,8 @@ ValueMetricProducer::ValueMetricProducer(const ConfigKey& key, const ValueMetric mStatsPullerManager->RegisterReceiver(mPullTagId, this, metric.bucket().bucket_size_millis()); } - VLOG("value metric %s created. bucket size %lld start_time: %lld", metric.name().c_str(), - (long long)mBucketSizeNs, (long long)mStartTimeNs); + VLOG("value metric %lld created. bucket size %lld start_time: %lld", + (long long)metric.id(), (long long)mBucketSizeNs, (long long)mStartTimeNs); } // for testing @@ -118,40 +113,45 @@ ValueMetricProducer::~ValueMetricProducer() { } void ValueMetricProducer::onSlicedConditionMayChangeLocked(const uint64_t eventTime) { - VLOG("Metric %s onSlicedConditionMayChange", mName.c_str()); + VLOG("Metric %lld onSlicedConditionMayChange", (long long)mMetricId); +} + +void ValueMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs, StatsLogReport* report) { + flushIfNeededLocked(dumpTimeNs); + report->set_metric_id(mMetricId); + report->set_start_report_nanos(mStartTimeNs); + auto value_metrics = report->mutable_value_metrics(); + for (const auto& pair : mPastBuckets) { + ValueMetricData* metricData = value_metrics->add_data(); + *metricData->mutable_dimension() = pair.first.getDimensionsValue(); + for (const auto& bucket : pair.second) { + ValueBucketInfo* bucketInfo = metricData->add_bucket_info(); + bucketInfo->set_start_bucket_nanos(bucket.mBucketStartNs); + bucketInfo->set_end_bucket_nanos(bucket.mBucketEndNs); + bucketInfo->set_value(bucket.mValue); + } + } } void ValueMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs, ProtoOutputStream* protoOutput) { - VLOG("metric %s dump report now...", mName.c_str()); + VLOG("metric %lld dump report now...", (long long)mMetricId); flushIfNeededLocked(dumpTimeNs); - protoOutput->write(FIELD_TYPE_STRING | FIELD_ID_NAME, mName); + protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_ID, (long long)mMetricId); protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_START_REPORT_NANOS, (long long)mStartTimeNs); long long protoToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_ID_VALUE_METRICS); for (const auto& pair : mPastBuckets) { const HashableDimensionKey& hashableKey = pair.first; VLOG(" dimension key %s", hashableKey.c_str()); - const vector<KeyValuePair>& kvs = hashableKey.getKeyValuePairs(); long long wrapperToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DATA); - // First fill dimension (KeyValuePairs). - for (const auto& kv : kvs) { - long long dimensionToken = protoOutput->start( - FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DIMENSION); - protoOutput->write(FIELD_TYPE_INT32 | FIELD_ID_KEY, kv.key()); - if (kv.has_value_str()) { - protoOutput->write(FIELD_TYPE_STRING | FIELD_ID_VALUE_STR, kv.value_str()); - } else if (kv.has_value_int()) { - protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_VALUE_INT, kv.value_int()); - } else if (kv.has_value_bool()) { - protoOutput->write(FIELD_TYPE_BOOL | FIELD_ID_VALUE_BOOL, kv.value_bool()); - } else if (kv.has_value_float()) { - protoOutput->write(FIELD_TYPE_FLOAT | FIELD_ID_VALUE_FLOAT, kv.value_float()); - } - protoOutput->end(dimensionToken); - } + // First fill dimension. + long long dimensionToken = protoOutput->start( + FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DIMENSION); + writeDimensionsValueProtoToStream(hashableKey.getDimensionsValue(), protoOutput); + protoOutput->end(dimensionToken); // Then fill bucket_info (ValueBucketInfo). for (const auto& bucket : pair.second) { @@ -171,7 +171,7 @@ void ValueMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs, protoOutput->end(protoToken); protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_END_REPORT_NANOS, (long long)dumpTimeNs); - VLOG("metric %s dump report now...", mName.c_str()); + VLOG("metric %lld dump report now...", (long long)mMetricId); mPastBuckets.clear(); mStartTimeNs = mCurrentBucketStartTimeNs; // TODO: Clear mDimensionKeyMap once the report is dumped. @@ -242,11 +242,11 @@ bool ValueMetricProducer::hitGuardRailLocked(const HashableDimensionKey& newKey) } if (mCurrentSlicedBucket.size() > StatsdStats::kDimensionKeySizeSoftLimit - 1) { size_t newTupleCount = mCurrentSlicedBucket.size() + 1; - StatsdStats::getInstance().noteMetricDimensionSize(mConfigKey, mName, newTupleCount); + StatsdStats::getInstance().noteMetricDimensionSize(mConfigKey, mMetricId, newTupleCount); // 2. Don't add more tuples, we are above the allowed threshold. Drop the data. if (newTupleCount > StatsdStats::kDimensionKeySizeHardLimit) { - ALOGE("ValueMetric %s dropping data for dimension key %s", mName.c_str(), - newKey.c_str()); + ALOGE("ValueMetric %lld dropping data for dimension key %s", + (long long)mMetricId, newKey.c_str()); return true; } } @@ -256,7 +256,7 @@ bool ValueMetricProducer::hitGuardRailLocked(const HashableDimensionKey& newKey) void ValueMetricProducer::onMatchedLogEventInternalLocked( const size_t matcherIndex, const HashableDimensionKey& eventKey, - const map<string, HashableDimensionKey>& conditionKey, bool condition, + const ConditionKey& conditionKey, bool condition, const LogEvent& event) { uint64_t eventTimeNs = event.GetTimestampNs(); if (eventTimeNs < mCurrentBucketStartTimeNs) { @@ -346,7 +346,7 @@ void ValueMetricProducer::flushIfNeededLocked(const uint64_t& eventTimeNs) { if (numBucketsForward > 1) { VLOG("Skipping forward %lld buckets", (long long)numBucketsForward); } - VLOG("metric %s: new bucket start time: %lld", mName.c_str(), + VLOG("metric %lld: new bucket start time: %lld", (long long)mMetricId, (long long)mCurrentBucketStartTimeNs); } diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.h b/cmds/statsd/src/metrics/ValueMetricProducer.h index 2f27e4e52aef..6f3b1d1c67d7 100644 --- a/cmds/statsd/src/metrics/ValueMetricProducer.h +++ b/cmds/statsd/src/metrics/ValueMetricProducer.h @@ -50,12 +50,13 @@ public: protected: void onMatchedLogEventInternalLocked( const size_t matcherIndex, const HashableDimensionKey& eventKey, - const std::map<std::string, HashableDimensionKey>& conditionKey, bool condition, + const ConditionKey& conditionKey, bool condition, const LogEvent& event) override; private: void onDumpReportLocked(const uint64_t dumpTimeNs, android::util::ProtoOutputStream* protoOutput) override; + void onDumpReportLocked(const uint64_t dumpTimeNs, StatsLogReport* report) override; // Internal interface to handle condition change. void onConditionChangedLocked(const bool conditionMet, const uint64_t eventTime) override; diff --git a/cmds/statsd/src/metrics/duration_helper/DurationTracker.h b/cmds/statsd/src/metrics/duration_helper/DurationTracker.h index 3c714b3923e8..842581ed1a9f 100644 --- a/cmds/statsd/src/metrics/duration_helper/DurationTracker.h +++ b/cmds/statsd/src/metrics/duration_helper/DurationTracker.h @@ -17,7 +17,7 @@ #ifndef DURATION_TRACKER_H #define DURATION_TRACKER_H -#include "anomaly/AnomalyTracker.h" +#include "anomaly/DurationAnomalyTracker.h" #include "condition/ConditionWizard.h" #include "config/ConfigKey.h" #include "stats_util.h" @@ -60,12 +60,12 @@ struct DurationBucket { class DurationTracker { public: - DurationTracker(const ConfigKey& key, const string& name, const HashableDimensionKey& eventKey, + DurationTracker(const ConfigKey& key, const int64_t& id, const HashableDimensionKey& eventKey, sp<ConditionWizard> wizard, int conditionIndex, bool nesting, uint64_t currentBucketStartNs, uint64_t bucketSizeNs, - const std::vector<sp<AnomalyTracker>>& anomalyTrackers) + const std::vector<sp<DurationAnomalyTracker>>& anomalyTrackers) : mConfigKey(key), - mName(name), + mTrackerId(id), mEventKey(eventKey), mWizard(wizard), mConditionTrackerIndex(conditionIndex), @@ -94,7 +94,7 @@ public: std::unordered_map<HashableDimensionKey, std::vector<DurationBucket>>* output) = 0; // Predict the anomaly timestamp given the current status. - virtual int64_t predictAnomalyTimestampNs(const AnomalyTracker& anomalyTracker, + virtual int64_t predictAnomalyTimestampNs(const DurationAnomalyTracker& anomalyTracker, const uint64_t currentTimestamp) const = 0; protected: @@ -145,7 +145,7 @@ protected: // A reference to the DurationMetricProducer's config key. const ConfigKey& mConfigKey; - const std::string mName; + const int64_t mTrackerId; HashableDimensionKey mEventKey; @@ -163,7 +163,7 @@ protected: uint64_t mCurrentBucketNum; - std::vector<sp<AnomalyTracker>> mAnomalyTrackers; + std::vector<sp<DurationAnomalyTracker>> mAnomalyTrackers; FRIEND_TEST(OringDurationTrackerTest, TestPredictAnomalyTimestamp); FRIEND_TEST(OringDurationTrackerTest, TestAnomalyDetection); diff --git a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp index 6050f4395a5f..94f98ada7014 100644 --- a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp +++ b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp @@ -24,12 +24,12 @@ namespace android { namespace os { namespace statsd { -MaxDurationTracker::MaxDurationTracker(const ConfigKey& key, const string& name, +MaxDurationTracker::MaxDurationTracker(const ConfigKey& key, const int64_t& id, const HashableDimensionKey& eventKey, sp<ConditionWizard> wizard, int conditionIndex, bool nesting, uint64_t currentBucketStartNs, uint64_t bucketSizeNs, - const std::vector<sp<AnomalyTracker>>& anomalyTrackers) - : DurationTracker(key, name, eventKey, wizard, conditionIndex, nesting, currentBucketStartNs, + const vector<sp<DurationAnomalyTracker>>& anomalyTrackers) + : DurationTracker(key, id, eventKey, wizard, conditionIndex, nesting, currentBucketStartNs, bucketSizeNs, anomalyTrackers) { } @@ -42,12 +42,13 @@ bool MaxDurationTracker::hitGuardRail(const HashableDimensionKey& newKey) { // 1. Report the tuple count if the tuple count > soft limit if (mInfos.size() > StatsdStats::kDimensionKeySizeSoftLimit - 1) { size_t newTupleCount = mInfos.size() + 1; - StatsdStats::getInstance().noteMetricDimensionSize(mConfigKey, mName + mEventKey.toString(), - newTupleCount); + StatsdStats::getInstance().noteMetricDimensionSize( + mConfigKey, hashDimensionsValue(mTrackerId, mEventKey.getDimensionsValue()), + newTupleCount); // 2. Don't add more tuples, we are above the allowed threshold. Drop the data. if (newTupleCount > StatsdStats::kDimensionKeySizeHardLimit) { - ALOGE("MaxDurTracker %s dropping data for dimension key %s", mName.c_str(), - newKey.c_str()); + ALOGE("MaxDurTracker %lld dropping data for dimension key %s", + (long long)mTrackerId, newKey.c_str()); return true; } } @@ -281,7 +282,7 @@ void MaxDurationTracker::noteConditionChanged(const HashableDimensionKey& key, b } } -int64_t MaxDurationTracker::predictAnomalyTimestampNs(const AnomalyTracker& anomalyTracker, +int64_t MaxDurationTracker::predictAnomalyTimestampNs(const DurationAnomalyTracker& anomalyTracker, const uint64_t currentTimestamp) const { ALOGE("Max duration producer does not support anomaly timestamp prediction!!!"); return currentTimestamp; diff --git a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h index 10eddb8bee6e..68c48cb10a4d 100644 --- a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h +++ b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h @@ -28,11 +28,11 @@ namespace statsd { // they stop or bucket expires. class MaxDurationTracker : public DurationTracker { public: - MaxDurationTracker(const ConfigKey& key, const string& name, + MaxDurationTracker(const ConfigKey& key, const int64_t& id, const HashableDimensionKey& eventKey, sp<ConditionWizard> wizard, int conditionIndex, bool nesting, uint64_t currentBucketStartNs, uint64_t bucketSizeNs, - const std::vector<sp<AnomalyTracker>>& anomalyTrackers); + const std::vector<sp<DurationAnomalyTracker>>& anomalyTrackers); void noteStart(const HashableDimensionKey& key, bool condition, const uint64_t eventTime, const ConditionKey& conditionKey) override; void noteStop(const HashableDimensionKey& key, const uint64_t eventTime, @@ -46,7 +46,7 @@ public: void onSlicedConditionMayChange(const uint64_t timestamp) override; void onConditionChanged(bool condition, const uint64_t timestamp) override; - int64_t predictAnomalyTimestampNs(const AnomalyTracker& anomalyTracker, + int64_t predictAnomalyTimestampNs(const DurationAnomalyTracker& anomalyTracker, const uint64_t currentTimestamp) const override; private: diff --git a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp index 5c430961036d..c77d0b70507b 100644 --- a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp +++ b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp @@ -24,13 +24,12 @@ namespace statsd { using std::pair; -OringDurationTracker::OringDurationTracker(const ConfigKey& key, const string& name, - const HashableDimensionKey& eventKey, - sp<ConditionWizard> wizard, int conditionIndex, - bool nesting, uint64_t currentBucketStartNs, - uint64_t bucketSizeNs, - const std::vector<sp<AnomalyTracker>>& anomalyTrackers) - : DurationTracker(key, name, eventKey, wizard, conditionIndex, nesting, currentBucketStartNs, +OringDurationTracker::OringDurationTracker( + const ConfigKey& key, const int64_t& id, const HashableDimensionKey& eventKey, + sp<ConditionWizard> wizard, int conditionIndex, bool nesting, uint64_t currentBucketStartNs, + uint64_t bucketSizeNs, const vector<sp<DurationAnomalyTracker>>& anomalyTrackers) + + : DurationTracker(key, id, eventKey, wizard, conditionIndex, nesting, currentBucketStartNs, bucketSizeNs, anomalyTrackers), mStarted(), mPaused() { @@ -45,12 +44,13 @@ bool OringDurationTracker::hitGuardRail(const HashableDimensionKey& newKey) { } if (mConditionKeyMap.size() > StatsdStats::kDimensionKeySizeSoftLimit - 1) { size_t newTupleCount = mConditionKeyMap.size() + 1; - StatsdStats::getInstance().noteMetricDimensionSize(mConfigKey, mName + mEventKey.toString(), - newTupleCount); + StatsdStats::getInstance().noteMetricDimensionSize( + mConfigKey, hashDimensionsValue(mTrackerId, mEventKey.getDimensionsValue()), + newTupleCount); // 2. Don't add more tuples, we are above the allowed threshold. Drop the data. if (newTupleCount > StatsdStats::kDimensionKeySizeHardLimit) { - ALOGE("OringDurTracker %s dropping data for dimension key %s", mName.c_str(), - newKey.c_str()); + ALOGE("OringDurTracker %lld dropping data for dimension key %s", + (long long)mTrackerId, newKey.c_str()); return true; } } @@ -264,8 +264,8 @@ void OringDurationTracker::onConditionChanged(bool condition, const uint64_t tim } } -int64_t OringDurationTracker::predictAnomalyTimestampNs(const AnomalyTracker& anomalyTracker, - const uint64_t eventTimestampNs) const { +int64_t OringDurationTracker::predictAnomalyTimestampNs( + const DurationAnomalyTracker& anomalyTracker, const uint64_t eventTimestampNs) const { // TODO: Unit-test this and see if it can be done more efficiently (e.g. use int32). // All variables below represent durations (not timestamps). diff --git a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h index b7d3cba7055e..7fe649c436e2 100644 --- a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h +++ b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h @@ -27,11 +27,11 @@ namespace statsd { // Tracks the "Or'd" duration -- if 2 durations are overlapping, they won't be double counted. class OringDurationTracker : public DurationTracker { public: - OringDurationTracker(const ConfigKey& key, const string& name, + OringDurationTracker(const ConfigKey& key, const int64_t& id, const HashableDimensionKey& eventKey, sp<ConditionWizard> wizard, int conditionIndex, bool nesting, uint64_t currentBucketStartNs, uint64_t bucketSizeNs, - const std::vector<sp<AnomalyTracker>>& anomalyTrackers); + const std::vector<sp<DurationAnomalyTracker>>& anomalyTrackers); void noteStart(const HashableDimensionKey& key, bool condition, const uint64_t eventTime, const ConditionKey& conditionKey) override; @@ -46,7 +46,7 @@ public: uint64_t timestampNs, std::unordered_map<HashableDimensionKey, std::vector<DurationBucket>>* output) override; - int64_t predictAnomalyTimestampNs(const AnomalyTracker& anomalyTracker, + int64_t predictAnomalyTimestampNs(const DurationAnomalyTracker& anomalyTracker, const uint64_t currentTimestamp) const override; private: diff --git a/cmds/statsd/src/metrics/metrics_manager_util.cpp b/cmds/statsd/src/metrics/metrics_manager_util.cpp index 5d0e97e24f79..a4ae4d6a7b18 100644 --- a/cmds/statsd/src/metrics/metrics_manager_util.cpp +++ b/cmds/statsd/src/metrics/metrics_manager_util.cpp @@ -38,21 +38,21 @@ namespace android { namespace os { namespace statsd { -bool handleMetricWithLogTrackers(const string what, const int metricIndex, +bool handleMetricWithLogTrackers(const int64_t what, const int metricIndex, const bool usedForDimension, const vector<sp<LogMatchingTracker>>& allAtomMatchers, - const unordered_map<string, int>& logTrackerMap, + const unordered_map<int64_t, int>& logTrackerMap, unordered_map<int, std::vector<int>>& trackerToMetricMap, int& logTrackerIndex) { auto logTrackerIt = logTrackerMap.find(what); if (logTrackerIt == logTrackerMap.end()) { - ALOGW("cannot find the AtomMatcher \"%s\" in config", what.c_str()); + ALOGW("cannot find the AtomMatcher \"%lld\" in config", (long long)what); return false; } - if (usedForDimension && allAtomMatchers[logTrackerIt->second]->getTagIds().size() > 1) { - ALOGE("AtomMatcher \"%s\" has more than one tag ids. When a metric has dimension, " + if (usedForDimension && allAtomMatchers[logTrackerIt->second]->getAtomIds().size() > 1) { + ALOGE("AtomMatcher \"%lld\" has more than one tag ids. When a metric has dimension, " "the \"what\" can only about one atom type.", - what.c_str()); + (long long)what); return false; } logTrackerIndex = logTrackerIt->second; @@ -62,22 +62,22 @@ bool handleMetricWithLogTrackers(const string what, const int metricIndex, } bool handleMetricWithConditions( - const string condition, const int metricIndex, - const unordered_map<string, int>& conditionTrackerMap, + const int64_t condition, const int metricIndex, + const unordered_map<int64_t, int>& conditionTrackerMap, const ::google::protobuf::RepeatedPtrField<::android::os::statsd::MetricConditionLink>& links, vector<sp<ConditionTracker>>& allConditionTrackers, int& conditionIndex, unordered_map<int, std::vector<int>>& conditionToMetricMap) { auto condition_it = conditionTrackerMap.find(condition); if (condition_it == conditionTrackerMap.end()) { - ALOGW("cannot find Predicate \"%s\" in the config", condition.c_str()); + ALOGW("cannot find Predicate \"%lld\" in the config", (long long)condition); return false; } for (const auto& link : links) { auto it = conditionTrackerMap.find(link.condition()); if (it == conditionTrackerMap.end()) { - ALOGW("cannot find Predicate \"%s\" in the config", link.condition().c_str()); + ALOGW("cannot find Predicate \"%lld\" in the config", (long long)link.condition()); return false; } allConditionTrackers[condition_it->second]->setSliced(true); @@ -92,7 +92,8 @@ bool handleMetricWithConditions( return true; } -bool initLogTrackers(const StatsdConfig& config, unordered_map<string, int>& logTrackerMap, +bool initLogTrackers(const StatsdConfig& config, const UidMap& uidMap, + unordered_map<int64_t, int>& logTrackerMap, vector<sp<LogMatchingTracker>>& allAtomMatchers, set<int>& allTagIds) { vector<AtomMatcher> matcherConfigs; const int atomMatcherCount = config.atom_matcher_size(); @@ -106,22 +107,22 @@ bool initLogTrackers(const StatsdConfig& config, unordered_map<string, int>& log switch (logMatcher.contents_case()) { case AtomMatcher::ContentsCase::kSimpleAtomMatcher: allAtomMatchers.push_back(new SimpleLogMatchingTracker( - logMatcher.name(), index, logMatcher.simple_atom_matcher())); + logMatcher.id(), index, logMatcher.simple_atom_matcher(), uidMap)); break; case AtomMatcher::ContentsCase::kCombination: allAtomMatchers.push_back( - new CombinationLogMatchingTracker(logMatcher.name(), index)); + new CombinationLogMatchingTracker(logMatcher.id(), index)); break; default: - ALOGE("Matcher \"%s\" malformed", logMatcher.name().c_str()); + ALOGE("Matcher \"%lld\" malformed", (long long)logMatcher.id()); return false; // continue; } - if (logTrackerMap.find(logMatcher.name()) != logTrackerMap.end()) { + if (logTrackerMap.find(logMatcher.id()) != logTrackerMap.end()) { ALOGE("Duplicate AtomMatcher found!"); return false; } - logTrackerMap[logMatcher.name()] = index; + logTrackerMap[logMatcher.id()] = index; matcherConfigs.push_back(logMatcher); } @@ -131,15 +132,15 @@ bool initLogTrackers(const StatsdConfig& config, unordered_map<string, int>& log return false; } // Collect all the tag ids that are interesting. TagIds exist in leaf nodes only. - const set<int>& tagIds = matcher->getTagIds(); + const set<int>& tagIds = matcher->getAtomIds(); allTagIds.insert(tagIds.begin(), tagIds.end()); } return true; } bool initConditions(const ConfigKey& key, const StatsdConfig& config, - const unordered_map<string, int>& logTrackerMap, - unordered_map<string, int>& conditionTrackerMap, + const unordered_map<int64_t, int>& logTrackerMap, + unordered_map<int64_t, int>& conditionTrackerMap, vector<sp<ConditionTracker>>& allConditionTrackers, unordered_map<int, std::vector<int>>& trackerToConditionMap) { vector<Predicate> conditionConfigs; @@ -153,23 +154,23 @@ bool initConditions(const ConfigKey& key, const StatsdConfig& config, switch (condition.contents_case()) { case Predicate::ContentsCase::kSimplePredicate: { allConditionTrackers.push_back(new SimpleConditionTracker( - key, condition.name(), index, condition.simple_predicate(), logTrackerMap)); + key, condition.id(), index, condition.simple_predicate(), logTrackerMap)); break; } case Predicate::ContentsCase::kCombination: { allConditionTrackers.push_back( - new CombinationConditionTracker(condition.name(), index)); + new CombinationConditionTracker(condition.id(), index)); break; } default: - ALOGE("Predicate \"%s\" malformed", condition.name().c_str()); + ALOGE("Predicate \"%lld\" malformed", (long long)condition.id()); return false; } - if (conditionTrackerMap.find(condition.name()) != conditionTrackerMap.end()) { + if (conditionTrackerMap.find(condition.id()) != conditionTrackerMap.end()) { ALOGE("Duplicate Predicate found!"); return false; } - conditionTrackerMap[condition.name()] = index; + conditionTrackerMap[condition.id()] = index; conditionConfigs.push_back(condition); } @@ -189,14 +190,15 @@ bool initConditions(const ConfigKey& key, const StatsdConfig& config, } bool initMetrics(const ConfigKey& key, const StatsdConfig& config, const long timeBaseSec, - const unordered_map<string, int>& logTrackerMap, - const unordered_map<string, int>& conditionTrackerMap, + const unordered_map<int64_t, int>& logTrackerMap, + const unordered_map<int64_t, int>& conditionTrackerMap, const vector<sp<LogMatchingTracker>>& allAtomMatchers, vector<sp<ConditionTracker>>& allConditionTrackers, vector<sp<MetricProducer>>& allMetricProducers, unordered_map<int, std::vector<int>>& conditionToMetricMap, unordered_map<int, std::vector<int>>& trackerToMetricMap, - unordered_map<string, int>& metricMap) { + unordered_map<int64_t, int>& metricMap, + std::set<int64_t> &noReportMetricIds) { sp<ConditionWizard> wizard = new ConditionWizard(allConditionTrackers); const int allMetricsCount = config.count_metric_size() + config.duration_metric_size() + config.event_metric_size() + config.value_metric_size(); @@ -205,24 +207,27 @@ bool initMetrics(const ConfigKey& key, const StatsdConfig& config, const long ti // Align all buckets to same instant in MIN_BUCKET_SIZE_SEC, so that avoid alarm // clock will not grow very aggressive. New metrics will be delayed up to // MIN_BUCKET_SIZE_SEC before starting. - long currentTimeSec = time(nullptr); - uint64_t startTimeNs = (currentTimeSec - kMinBucketSizeSec - - (currentTimeSec - timeBaseSec) % kMinBucketSizeSec) * - NS_PER_SEC; + // Why not use timeBaseSec directly? +// long currentTimeSec = time(nullptr); +// uint64_t startTimeNs = (currentTimeSec - kMinBucketSizeSec - +// (currentTimeSec - timeBaseSec) % kMinBucketSizeSec) * +// NS_PER_SEC; + + uint64_t startTimeNs = timeBaseSec * NS_PER_SEC; // Build MetricProducers for each metric defined in config. // build CountMetricProducer for (int i = 0; i < config.count_metric_size(); i++) { const CountMetric& metric = config.count_metric(i); if (!metric.has_what()) { - ALOGW("cannot find \"what\" in CountMetric \"%s\"", metric.name().c_str()); + ALOGW("cannot find \"what\" in CountMetric \"%lld\"", (long long)metric.id()); return false; } int metricIndex = allMetricProducers.size(); - metricMap.insert({metric.name(), metricIndex}); + metricMap.insert({metric.id(), metricIndex}); int trackerIndex; - if (!handleMetricWithLogTrackers(metric.what(), metricIndex, metric.dimension_size() > 0, + if (!handleMetricWithLogTrackers(metric.what(), metricIndex, metric.has_dimensions(), allAtomMatchers, logTrackerMap, trackerToMetricMap, trackerIndex)) { return false; @@ -252,7 +257,7 @@ bool initMetrics(const ConfigKey& key, const StatsdConfig& config, const long ti for (int i = 0; i < config.duration_metric_size(); i++) { int metricIndex = allMetricProducers.size(); const DurationMetric& metric = config.duration_metric(i); - metricMap.insert({metric.name(), metricIndex}); + metricMap.insert({metric.id(), metricIndex}); auto what_it = conditionTrackerMap.find(metric.what()); if (what_it == conditionTrackerMap.end()) { @@ -274,7 +279,7 @@ bool initMetrics(const ConfigKey& key, const StatsdConfig& config, const long ti int trackerIndices[3] = {-1, -1, -1}; if (!simplePredicate.has_start() || !handleMetricWithLogTrackers(simplePredicate.start(), metricIndex, - metric.dimension_size() > 0, allAtomMatchers, + metric.has_dimensions(), allAtomMatchers, logTrackerMap, trackerToMetricMap, trackerIndices[0])) { ALOGE("Duration metrics must specify a valid the start event matcher"); return false; @@ -282,21 +287,19 @@ bool initMetrics(const ConfigKey& key, const StatsdConfig& config, const long ti if (simplePredicate.has_stop() && !handleMetricWithLogTrackers(simplePredicate.stop(), metricIndex, - metric.dimension_size() > 0, allAtomMatchers, + metric.has_dimensions(), allAtomMatchers, logTrackerMap, trackerToMetricMap, trackerIndices[1])) { return false; } if (simplePredicate.has_stop_all() && !handleMetricWithLogTrackers(simplePredicate.stop_all(), metricIndex, - metric.dimension_size() > 0, allAtomMatchers, + metric.has_dimensions(), allAtomMatchers, logTrackerMap, trackerToMetricMap, trackerIndices[2])) { return false; } - vector<KeyMatcher> internalDimension; - internalDimension.insert(internalDimension.begin(), simplePredicate.dimension().begin(), - simplePredicate.dimension().end()); + FieldMatcher internalDimensions = simplePredicate.dimensions(); int conditionIndex = -1; @@ -316,7 +319,7 @@ bool initMetrics(const ConfigKey& key, const StatsdConfig& config, const long ti sp<MetricProducer> durationMetric = new DurationMetricProducer( key, metric, conditionIndex, trackerIndices[0], trackerIndices[1], - trackerIndices[2], nesting, wizard, internalDimension, startTimeNs); + trackerIndices[2], nesting, wizard, internalDimensions, startTimeNs); allMetricProducers.push_back(durationMetric); } @@ -325,8 +328,8 @@ bool initMetrics(const ConfigKey& key, const StatsdConfig& config, const long ti for (int i = 0; i < config.event_metric_size(); i++) { int metricIndex = allMetricProducers.size(); const EventMetric& metric = config.event_metric(i); - metricMap.insert({metric.name(), metricIndex}); - if (!metric.has_name() || !metric.has_what()) { + metricMap.insert({metric.id(), metricIndex}); + if (!metric.has_id() || !metric.has_what()) { ALOGW("cannot find the metric name or what in config"); return false; } @@ -361,14 +364,14 @@ bool initMetrics(const ConfigKey& key, const StatsdConfig& config, const long ti for (int i = 0; i < config.value_metric_size(); i++) { const ValueMetric& metric = config.value_metric(i); if (!metric.has_what()) { - ALOGW("cannot find \"what\" in ValueMetric \"%s\"", metric.name().c_str()); + ALOGW("cannot find \"what\" in ValueMetric \"%lld\"", (long long)metric.id()); return false; } int metricIndex = allMetricProducers.size(); - metricMap.insert({metric.name(), metricIndex}); + metricMap.insert({metric.id(), metricIndex}); int trackerIndex; - if (!handleMetricWithLogTrackers(metric.what(), metricIndex, metric.dimension_size() > 0, + if (!handleMetricWithLogTrackers(metric.what(), metricIndex, metric.has_dimensions(), allAtomMatchers, logTrackerMap, trackerToMetricMap, trackerIndex)) { return false; @@ -376,10 +379,10 @@ bool initMetrics(const ConfigKey& key, const StatsdConfig& config, const long ti sp<LogMatchingTracker> atomMatcher = allAtomMatchers.at(trackerIndex); // If it is pulled atom, it should be simple matcher with one tagId. - if (atomMatcher->getTagIds().size() != 1) { + if (atomMatcher->getAtomIds().size() != 1) { return false; } - int atomTagId = *(atomMatcher->getTagIds().begin()); + int atomTagId = *(atomMatcher->getAtomIds().begin()); int pullTagId = statsPullerManager.PullerForMatcherExists(atomTagId) ? atomTagId : -1; int conditionIndex = -1; @@ -406,27 +409,27 @@ bool initMetrics(const ConfigKey& key, const StatsdConfig& config, const long ti for (int i = 0; i < config.gauge_metric_size(); i++) { const GaugeMetric& metric = config.gauge_metric(i); if (!metric.has_what()) { - ALOGW("cannot find \"what\" in GaugeMetric \"%s\"", metric.name().c_str()); + ALOGW("cannot find \"what\" in GaugeMetric \"%lld\"", (long long)metric.id()); return false; } - if ((!metric.gauge_fields().has_include_all() || - (metric.gauge_fields().include_all() == false)) && - metric.gauge_fields().field_num_size() == 0) { - ALOGW("Incorrect field filter setting in GaugeMetric %s", metric.name().c_str()); + if ((!metric.gauge_fields_filter().has_include_all() || + (metric.gauge_fields_filter().include_all() == false)) && + !hasLeafNode(metric.gauge_fields_filter().fields())) { + ALOGW("Incorrect field filter setting in GaugeMetric %lld", (long long)metric.id()); return false; } - if ((metric.gauge_fields().has_include_all() && - metric.gauge_fields().include_all() == true) && - metric.gauge_fields().field_num_size() > 0) { - ALOGW("Incorrect field filter setting in GaugeMetric %s", metric.name().c_str()); + if ((metric.gauge_fields_filter().has_include_all() && + metric.gauge_fields_filter().include_all() == true) && + hasLeafNode(metric.gauge_fields_filter().fields())) { + ALOGW("Incorrect field filter setting in GaugeMetric %lld", (long long)metric.id()); return false; } int metricIndex = allMetricProducers.size(); - metricMap.insert({metric.name(), metricIndex}); + metricMap.insert({metric.id(), metricIndex}); int trackerIndex; - if (!handleMetricWithLogTrackers(metric.what(), metricIndex, metric.dimension_size() > 0, + if (!handleMetricWithLogTrackers(metric.what(), metricIndex, metric.has_dimensions(), allAtomMatchers, logTrackerMap, trackerToMetricMap, trackerIndex)) { return false; @@ -434,10 +437,10 @@ bool initMetrics(const ConfigKey& key, const StatsdConfig& config, const long ti sp<LogMatchingTracker> atomMatcher = allAtomMatchers.at(trackerIndex); // If it is pulled atom, it should be simple matcher with one tagId. - if (atomMatcher->getTagIds().size() != 1) { + if (atomMatcher->getAtomIds().size() != 1) { return false; } - int atomTagId = *(atomMatcher->getTagIds().begin()); + int atomTagId = *(atomMatcher->getAtomIds().begin()); int pullTagId = statsPullerManager.PullerForMatcherExists(atomTagId) ? atomTagId : -1; int conditionIndex = -1; @@ -459,18 +462,28 @@ bool initMetrics(const ConfigKey& key, const StatsdConfig& config, const long ti key, metric, conditionIndex, wizard, pullTagId, atomTagId, startTimeNs); allMetricProducers.push_back(gaugeProducer); } + for (int i = 0; i < config.no_report_metric_size(); ++i) { + const auto no_report_metric = config.no_report_metric(i); + if (metricMap.find(no_report_metric) == metricMap.end()) { + ALOGW("no_report_metric %lld not exist", no_report_metric); + return false; + } + noReportMetricIds.insert(no_report_metric); + } return true; } -bool initAlerts(const StatsdConfig& config, const unordered_map<string, int>& metricProducerMap, +bool initAlerts(const StatsdConfig& config, + const unordered_map<int64_t, int>& metricProducerMap, vector<sp<MetricProducer>>& allMetricProducers, vector<sp<AnomalyTracker>>& allAnomalyTrackers) { + unordered_map<int64_t, int> anomalyTrackerMap; for (int i = 0; i < config.alert_size(); i++) { const Alert& alert = config.alert(i); - const auto& itr = metricProducerMap.find(alert.metric_name()); + const auto& itr = metricProducerMap.find(alert.metric_id()); if (itr == metricProducerMap.end()) { - ALOGW("alert \"%s\" has unknown metric name: \"%s\"", alert.name().c_str(), - alert.metric_name().c_str()); + ALOGW("alert \"%lld\" has unknown metric id: \"%lld\"", (long long)alert.id(), + (long long)alert.metric_id()); return false; } if (alert.trigger_if_sum_gt() < 0 || alert.number_of_buckets() <= 0) { @@ -480,28 +493,48 @@ bool initAlerts(const StatsdConfig& config, const unordered_map<string, int>& me } const int metricIndex = itr->second; sp<MetricProducer> metric = allMetricProducers[metricIndex]; - sp<AnomalyTracker> anomalyTracker = metric->createAnomalyTracker(alert); + sp<AnomalyTracker> anomalyTracker = metric->addAnomalyTracker(alert); if (anomalyTracker != nullptr) { - metric->addAnomalyTracker(anomalyTracker); + anomalyTrackerMap.insert(std::make_pair(alert.id(), allAnomalyTrackers.size())); allAnomalyTrackers.push_back(anomalyTracker); } } + for (int i = 0; i < config.subscription_size(); ++i) { + const Subscription& subscription = config.subscription(i); + if (subscription.subscriber_information_case() == + Subscription::SubscriberInformationCase::SUBSCRIBER_INFORMATION_NOT_SET) { + ALOGW("subscription \"%lld\" has no subscriber info.\"", + (long long)subscription.id()); + return false; + } + const auto& itr = anomalyTrackerMap.find(subscription.rule_id()); + if (itr == anomalyTrackerMap.end()) { + ALOGW("subscription \"%lld\" has unknown rule id: \"%lld\"", + (long long)subscription.id(), (long long)subscription.rule_id()); + return false; + } + const int anomalyTrackerIndex = itr->second; + allAnomalyTrackers[anomalyTrackerIndex]->addSubscription(subscription); + } return true; } -bool initStatsdConfig(const ConfigKey& key, const StatsdConfig& config, const long timeBaseSec, set<int>& allTagIds, +bool initStatsdConfig(const ConfigKey& key, const StatsdConfig& config, + const UidMap& uidMap, + const long timeBaseSec, set<int>& allTagIds, vector<sp<LogMatchingTracker>>& allAtomMatchers, vector<sp<ConditionTracker>>& allConditionTrackers, vector<sp<MetricProducer>>& allMetricProducers, vector<sp<AnomalyTracker>>& allAnomalyTrackers, unordered_map<int, std::vector<int>>& conditionToMetricMap, unordered_map<int, std::vector<int>>& trackerToMetricMap, - unordered_map<int, std::vector<int>>& trackerToConditionMap) { - unordered_map<string, int> logTrackerMap; - unordered_map<string, int> conditionTrackerMap; - unordered_map<string, int> metricProducerMap; + unordered_map<int, std::vector<int>>& trackerToConditionMap, + std::set<int64_t> &noReportMetricIds) { + unordered_map<int64_t, int> logTrackerMap; + unordered_map<int64_t, int> conditionTrackerMap; + unordered_map<int64_t, int> metricProducerMap; - if (!initLogTrackers(config, logTrackerMap, allAtomMatchers, allTagIds)) { + if (!initLogTrackers(config, uidMap, logTrackerMap, allAtomMatchers, allTagIds)) { ALOGE("initLogMatchingTrackers failed"); return false; } @@ -515,7 +548,7 @@ bool initStatsdConfig(const ConfigKey& key, const StatsdConfig& config, const lo if (!initMetrics(key, config, timeBaseSec, logTrackerMap, conditionTrackerMap, allAtomMatchers, allConditionTrackers, allMetricProducers, conditionToMetricMap, - trackerToMetricMap, metricProducerMap)) { + trackerToMetricMap, metricProducerMap, noReportMetricIds)) { ALOGE("initMetricProducers failed"); return false; } diff --git a/cmds/statsd/src/metrics/metrics_manager_util.h b/cmds/statsd/src/metrics/metrics_manager_util.h index 333733234699..4f19ada5b022 100644 --- a/cmds/statsd/src/metrics/metrics_manager_util.h +++ b/cmds/statsd/src/metrics/metrics_manager_util.h @@ -43,7 +43,8 @@ namespace statsd { // [allAtomMatchers]: should store the sp to all the LogMatchingTracker // [allTagIds]: contains the set of all interesting tag ids to this config. bool initLogTrackers(const StatsdConfig& config, - std::unordered_map<std::string, int>& logTrackerMap, + const UidMap& uidMap, + std::unordered_map<int64_t, int>& logTrackerMap, std::vector<sp<LogMatchingTracker>>& allAtomMatchers, std::set<int>& allTagIds); @@ -58,8 +59,8 @@ bool initLogTrackers(const StatsdConfig& config, // [trackerToConditionMap]: contain the mapping from index of // log tracker to condition trackers that use the log tracker bool initConditions(const ConfigKey& key, const StatsdConfig& config, - const std::unordered_map<std::string, int>& logTrackerMap, - std::unordered_map<std::string, int>& conditionTrackerMap, + const std::unordered_map<int64_t, int>& logTrackerMap, + std::unordered_map<int64_t, int>& conditionTrackerMap, std::vector<sp<ConditionTracker>>& allConditionTrackers, std::unordered_map<int, std::vector<int>>& trackerToConditionMap, std::unordered_map<int, std::vector<MetricConditionLink>>& eventConditionLinks); @@ -78,25 +79,29 @@ bool initConditions(const ConfigKey& key, const StatsdConfig& config, // [trackerToMetricMap]: contains the mapping from log tracker to MetricProducer index. bool initMetrics( const ConfigKey& key, const StatsdConfig& config, const long timeBaseSec, - const std::unordered_map<std::string, int>& logTrackerMap, - const std::unordered_map<std::string, int>& conditionTrackerMap, + const std::unordered_map<int64_t, int>& logTrackerMap, + const std::unordered_map<int64_t, int>& conditionTrackerMap, const std::unordered_map<int, std::vector<MetricConditionLink>>& eventConditionLinks, const vector<sp<LogMatchingTracker>>& allAtomMatchers, vector<sp<ConditionTracker>>& allConditionTrackers, std::vector<sp<MetricProducer>>& allMetricProducers, std::unordered_map<int, std::vector<int>>& conditionToMetricMap, - std::unordered_map<int, std::vector<int>>& trackerToMetricMap); + std::unordered_map<int, std::vector<int>>& trackerToMetricMap, + std::set<int64_t> &noReportMetricIds); // Initialize MetricsManager from StatsdConfig. // Parameters are the members of MetricsManager. See MetricsManager for declaration. -bool initStatsdConfig(const ConfigKey& key, const StatsdConfig& config, const long timeBaseSec, std::set<int>& allTagIds, +bool initStatsdConfig(const ConfigKey& key, const StatsdConfig& config, + const UidMap& uidMap, + const long timeBaseSec, std::set<int>& allTagIds, std::vector<sp<LogMatchingTracker>>& allAtomMatchers, std::vector<sp<ConditionTracker>>& allConditionTrackers, std::vector<sp<MetricProducer>>& allMetricProducers, vector<sp<AnomalyTracker>>& allAnomalyTrackers, std::unordered_map<int, std::vector<int>>& conditionToMetricMap, std::unordered_map<int, std::vector<int>>& trackerToMetricMap, - std::unordered_map<int, std::vector<int>>& trackerToConditionMap); + std::unordered_map<int, std::vector<int>>& trackerToConditionMap, + std::set<int64_t> &noReportMetricIds); } // namespace statsd } // namespace os diff --git a/cmds/statsd/src/packages/UidMap.cpp b/cmds/statsd/src/packages/UidMap.cpp index 21a9cf319375..416b87b36c54 100644 --- a/cmds/statsd/src/packages/UidMap.cpp +++ b/cmds/statsd/src/packages/UidMap.cpp @@ -404,4 +404,4 @@ set<int32_t> UidMap::getAppUid(const string& package) const { } // namespace statsd } // namespace os -} // namespace android +} // namespace android
\ No newline at end of file diff --git a/cmds/statsd/src/packages/UidMap.h b/cmds/statsd/src/packages/UidMap.h index a9aec94a932e..02dea54f1b50 100644 --- a/cmds/statsd/src/packages/UidMap.h +++ b/cmds/statsd/src/packages/UidMap.h @@ -168,4 +168,3 @@ private: } // namespace statsd } // namespace os } // namespace android - diff --git a/cmds/statsd/src/stats_log.proto b/cmds/statsd/src/stats_log.proto index 3c85c57ab037..0b369bb2fdbc 100644 --- a/cmds/statsd/src/stats_log.proto +++ b/cmds/statsd/src/stats_log.proto @@ -24,8 +24,14 @@ option java_outer_classname = "StatsLog"; import "frameworks/base/cmds/statsd/src/atoms.proto"; -message KeyValuePair { - optional int32 key = 1; +message Field { + optional int32 field = 1; + optional int32 position_index = 2 [default = -1]; + repeated Field child = 3; +} + +message DimensionsValue { + optional int32 field = 1; oneof value { string value_str = 2; @@ -33,9 +39,14 @@ message KeyValuePair { int64 value_long = 4; bool value_bool = 5; float value_float = 6; + DimensionsValueTuple value_tuple = 7; } } +message DimensionsValueTuple { + repeated DimensionsValue dimensions_value = 1; +} + message EventMetricData { optional int64 timestamp_nanos = 1; @@ -51,7 +62,7 @@ message CountBucketInfo { } message CountMetricData { - repeated KeyValuePair dimension = 1; + optional DimensionsValue dimension = 1; repeated CountBucketInfo bucket_info = 2; } @@ -65,7 +76,7 @@ message DurationBucketInfo { } message DurationMetricData { - repeated KeyValuePair dimension = 1; + optional DimensionsValue dimension = 1; repeated DurationBucketInfo bucket_info = 2; } @@ -79,7 +90,7 @@ message ValueBucketInfo { } message ValueMetricData { - repeated KeyValuePair dimension = 1; + optional DimensionsValue dimension = 1; repeated ValueBucketInfo bucket_info = 2; } @@ -93,7 +104,7 @@ message GaugeBucketInfo { } message GaugeMetricData { - repeated KeyValuePair dimension = 1; + optional DimensionsValue dimension = 1; repeated GaugeBucketInfo bucket_info = 2; } @@ -126,7 +137,7 @@ message UidMapping { } message StatsLogReport { - optional string metric_name = 1; + optional int64 metric_id = 1; optional int64 start_report_nanos = 2; @@ -167,7 +178,7 @@ message ConfigMetricsReport { message ConfigMetricsReportList { message ConfigKey { optional int32 uid = 1; - optional string name = 2; + optional int64 id = 2; } optional ConfigKey config_key = 1; @@ -180,28 +191,28 @@ message StatsdStatsReport { optional int32 stats_end_time_sec = 2; message MatcherStats { - optional string name = 1; + optional int64 id = 1; optional int32 matched_times = 2; } message ConditionStats { - optional string name = 1; + optional int64 id = 1; optional int32 max_tuple_counts = 2; } message MetricStats { - optional string name = 1; + optional int64 id = 1; optional int32 max_tuple_counts = 2; } message AlertStats { - optional string name = 1; + optional int64 id = 1; optional int32 alerted_times = 2; } message ConfigStats { optional int32 uid = 1; - optional string name = 2; + optional int64 id = 2; optional int32 creation_time_sec = 3; optional int32 deletion_time_sec = 4; optional int32 metric_count = 5; diff --git a/cmds/statsd/src/stats_log_util.cpp b/cmds/statsd/src/stats_log_util.cpp new file mode 100644 index 000000000000..476e11724318 --- /dev/null +++ b/cmds/statsd/src/stats_log_util.cpp @@ -0,0 +1,227 @@ +/* + * Copyright (C) 2017 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. + */ + +#include "stats_log_util.h" + +#include <set> +#include <stack> +#include <utils/Log.h> + +using android::util::FIELD_COUNT_REPEATED; +using android::util::FIELD_TYPE_BOOL; +using android::util::FIELD_TYPE_FLOAT; +using android::util::FIELD_TYPE_INT32; +using android::util::FIELD_TYPE_INT64; +using android::util::FIELD_TYPE_MESSAGE; +using android::util::FIELD_TYPE_STRING; +using android::util::ProtoOutputStream; + +namespace android { +namespace os { +namespace statsd { + +// for DimensionsValue Proto +const int DIMENSIONS_VALUE_FIELD = 1; +const int DIMENSIONS_VALUE_VALUE_STR = 2; +const int DIMENSIONS_VALUE_VALUE_INT = 3; +const int DIMENSIONS_VALUE_VALUE_LONG = 4; +const int DIMENSIONS_VALUE_VALUE_BOOL = 5; +const int DIMENSIONS_VALUE_VALUE_FLOAT = 6; +const int DIMENSIONS_VALUE_VALUE_TUPLE = 7; + +// for MessageValue Proto +const int FIELD_ID_FIELD_VALUE_IN_MESSAGE_VALUE_PROTO = 1; + +void writeDimensionsValueProtoToStream(const DimensionsValue& dimensionsValue, + ProtoOutputStream* protoOutput) { + protoOutput->write(FIELD_TYPE_INT32 | DIMENSIONS_VALUE_FIELD, dimensionsValue.field()); + switch (dimensionsValue.value_case()) { + case DimensionsValue::ValueCase::kValueStr: + protoOutput->write(FIELD_TYPE_STRING | DIMENSIONS_VALUE_VALUE_STR, + dimensionsValue.value_str()); + break; + case DimensionsValue::ValueCase::kValueInt: + protoOutput->write(FIELD_TYPE_INT32 | DIMENSIONS_VALUE_VALUE_INT, + dimensionsValue.value_int()); + break; + case DimensionsValue::ValueCase::kValueLong: + protoOutput->write(FIELD_TYPE_INT64 | DIMENSIONS_VALUE_VALUE_LONG, + dimensionsValue.value_long()); + break; + case DimensionsValue::ValueCase::kValueBool: + protoOutput->write(FIELD_TYPE_BOOL | DIMENSIONS_VALUE_VALUE_BOOL, + dimensionsValue.value_bool()); + break; + case DimensionsValue::ValueCase::kValueFloat: + protoOutput->write(FIELD_TYPE_FLOAT | DIMENSIONS_VALUE_VALUE_FLOAT, + dimensionsValue.value_float()); + break; + case DimensionsValue::ValueCase::kValueTuple: + { + long long tupleToken = protoOutput->start( + FIELD_TYPE_MESSAGE | DIMENSIONS_VALUE_VALUE_TUPLE); + for (int i = 0; i < dimensionsValue.value_tuple().dimensions_value_size(); ++i) { + long long token = protoOutput->start( + FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED + | FIELD_ID_FIELD_VALUE_IN_MESSAGE_VALUE_PROTO); + writeDimensionsValueProtoToStream( + dimensionsValue.value_tuple().dimensions_value(i), protoOutput); + protoOutput->end(token); + } + protoOutput->end(tupleToken); + } + break; + default: + break; + } +} + +// for Field Proto +const int FIELD_FIELD = 1; +const int FIELD_POSITION_INDEX = 2; +const int FIELD_CHILD = 3; + +void writeFieldProtoToStream( + const Field& field, util::ProtoOutputStream* protoOutput) { + protoOutput->write(FIELD_TYPE_INT32 | FIELD_FIELD, field.field()); + if (field.has_position_index()) { + protoOutput->write(FIELD_TYPE_INT32 | FIELD_POSITION_INDEX, field.position_index()); + } + for (int i = 0; i < field.child_size(); ++i) { + long long childToken = protoOutput->start( + FIELD_TYPE_MESSAGE| FIELD_COUNT_REPEATED | FIELD_CHILD); + writeFieldProtoToStream(field.child(i), protoOutput); + protoOutput->end(childToken); + } +} + +namespace { + +void addOrUpdateChildrenMap( + const Field& root, + const Field& node, + std::map<Field, std::set<Field, FieldCmp>, FieldCmp> *childrenMap) { + Field parentNode = root; + if (node.has_position_index()) { + appendLeaf(&parentNode, node.field(), node.position_index()); + } else { + appendLeaf(&parentNode, node.field()); + } + if (childrenMap->find(parentNode) == childrenMap->end()) { + childrenMap->insert(std::make_pair(parentNode, std::set<Field, FieldCmp>{})); + } + auto it = childrenMap->find(parentNode); + for (int i = 0; i < node.child_size(); ++i) { + auto child = node.child(i); + Field childNode = parentNode; + if (child.has_position_index()) { + appendLeaf(&childNode, child.field(), child.position_index()); + } else { + appendLeaf(&childNode, child.field()); + } + it->second.insert(childNode); + addOrUpdateChildrenMap(parentNode, child, childrenMap); + } +} + +void addOrUpdateChildrenMap( + const Field& field, + std::map<Field, std::set<Field, FieldCmp>, FieldCmp> *childrenMap) { + Field root; + addOrUpdateChildrenMap(root, field, childrenMap); +} + +} // namespace + +void writeFieldValueTreeToStream(const FieldValueMap &fieldValueMap, + util::ProtoOutputStream* protoOutput) { + std::map<Field, std::set<Field, FieldCmp>, FieldCmp> childrenMap; + // Rebuild the field tree. + for (auto it = fieldValueMap.begin(); it != fieldValueMap.end(); ++it) { + addOrUpdateChildrenMap(it->first, &childrenMap); + } + std::stack<std::pair<long long, Field>> tokenStack; + // Iterate over the node tree to fill the Atom proto. + for (auto it = childrenMap.begin(); it != childrenMap.end(); ++it) { + const Field* nodeLeaf = getSingleLeaf(&it->first); + const int fieldNum = nodeLeaf->field(); + while (!tokenStack.empty()) { + auto currentMsgNode = tokenStack.top().second; + auto currentMsgNodeChildrenIt = childrenMap.find(currentMsgNode); + if (currentMsgNodeChildrenIt->second.find(it->first) == + currentMsgNodeChildrenIt->second.end()) { + protoOutput->end(tokenStack.top().first); + tokenStack.pop(); + } else { + break; + } + } + if (it->second.size() == 0) { + auto itValue = fieldValueMap.find(it->first); + if (itValue != fieldValueMap.end()) { + const DimensionsValue& value = itValue->second; + switch (value.value_case()) { + case DimensionsValue::ValueCase::kValueStr: + protoOutput->write(FIELD_TYPE_STRING | fieldNum, + value.value_str()); + break; + case DimensionsValue::ValueCase::kValueInt: + protoOutput->write(FIELD_TYPE_INT32 | fieldNum, + value.value_int()); + break; + case DimensionsValue::ValueCase::kValueLong: + protoOutput->write(FIELD_TYPE_INT64 | fieldNum, + value.value_long()); + break; + case DimensionsValue::ValueCase::kValueBool: + protoOutput->write(FIELD_TYPE_BOOL | fieldNum, + value.value_bool()); + break; + case DimensionsValue::ValueCase::kValueFloat: + protoOutput->write(FIELD_TYPE_FLOAT | fieldNum, + value.value_float()); + break; + // This would not happen as the node has no child. + case DimensionsValue::ValueCase::kValueTuple: + break; + default: + break; + } + } else { + ALOGE("Leaf node value not found. This should never happen."); + } + } else { + long long token; + if (nodeLeaf->has_position_index()) { + token = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | fieldNum); + } else { + token = protoOutput->start(FIELD_TYPE_MESSAGE | fieldNum); + } + tokenStack.push(std::make_pair(token, it->first)); + } + } + + while (!tokenStack.empty()) { + protoOutput->end(tokenStack.top().first); + tokenStack.pop(); + } + + +} + +} // namespace statsd +} // namespace os +} // namespace android
\ No newline at end of file diff --git a/cmds/statsd/src/stats_log_util.h b/cmds/statsd/src/stats_log_util.h new file mode 100644 index 000000000000..1f8186083d12 --- /dev/null +++ b/cmds/statsd/src/stats_log_util.h @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2017 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. + */ + +#pragma once + +#include <android/util/ProtoOutputStream.h> +#include "frameworks/base/cmds/statsd/src/stats_log.pb.h" +#include "field_util.h" + +namespace android { +namespace os { +namespace statsd { + +// Helper function to write DimensionsValue proto to ProtoOutputStream. +void writeDimensionsValueProtoToStream( + const DimensionsValue& fieldValue, util::ProtoOutputStream* protoOutput); + +// Helper function to write Field proto to ProtoOutputStream. +void writeFieldProtoToStream( + const Field& field, util::ProtoOutputStream* protoOutput); + +// Helper function to construct the field value tree and write to ProtoOutputStream +void writeFieldValueTreeToStream(const FieldValueMap &fieldValueMap, + util::ProtoOutputStream* protoOutput); + +} // namespace statsd +} // namespace os +} // namespace android
\ No newline at end of file diff --git a/cmds/statsd/src/stats_util.h b/cmds/statsd/src/stats_util.h index 1cdf031c7858..160b1f40243f 100644 --- a/cmds/statsd/src/stats_util.h +++ b/cmds/statsd/src/stats_util.h @@ -27,45 +27,15 @@ namespace android { namespace os { namespace statsd { -const HashableDimensionKey DEFAULT_DIMENSION_KEY = HashableDimensionKey(vector<KeyValuePair>()); +const HashableDimensionKey DEFAULT_DIMENSION_KEY = HashableDimensionKey(); // Minimum bucket size in seconds const long kMinBucketSizeSec = 5 * 60; -typedef std::map<std::string, HashableDimensionKey> ConditionKey; +typedef std::map<int64_t, std::vector<HashableDimensionKey>> ConditionKey; typedef std::unordered_map<HashableDimensionKey, int64_t> DimToValMap; -/* - * In memory rep for LogEvent. Uses much less memory than LogEvent - */ -typedef struct EventKV { - std::vector<KeyValuePair> kv; - string ToString() const { - std::ostringstream result; - result << "{ "; - const size_t N = kv.size(); - for (size_t i = 0; i < N; i++) { - result << " "; - result << (i + 1); - result << "->"; - const auto& pair = kv[i]; - if (pair.has_value_int()) { - result << pair.value_int(); - } else if (pair.has_value_long()) { - result << pair.value_long(); - } else if (pair.has_value_float()) { - result << pair.value_float(); - } else if (pair.has_value_str()) { - result << pair.value_str().c_str(); - } - } - result << " }"; - return result.str(); - } -} EventKV; - -typedef std::unordered_map<HashableDimensionKey, std::shared_ptr<EventKV>> DimToEventKVMap; } // namespace statsd } // namespace os } // namespace android diff --git a/cmds/statsd/src/statsd_config.proto b/cmds/statsd/src/statsd_config.proto index 4729f6acc621..6ac0e2863898 100644 --- a/cmds/statsd/src/statsd_config.proto +++ b/cmds/statsd/src/statsd_config.proto @@ -22,30 +22,52 @@ package android.os.statsd; option java_package = "com.android.internal.os"; option java_outer_classname = "StatsdConfigProto"; -message KeyMatcher { - optional int32 key = 1; +enum Position { + POSITION_UNKNOWN = 0; + FIRST = 1; + LAST = 2; + ANY = 3; +} + +message FieldMatcher { + optional int32 field = 1; - optional bool as_package_name = 2 [default = false]; + optional Position position = 2; + + repeated FieldMatcher child = 3; } -message KeyValueMatcher { - optional KeyMatcher key_matcher = 1; +message FieldValueMatcher { + // Field id, as specified in the atom proto message. + optional int32 field = 1; + + // For repeated fields, specifies the position in the array. + // FIRST and LAST mean that if the values are found at the first + // or last position, it's a match. ANY means that if the values are found + // anywhere in the array, then it's a match. + optional Position position = 2; oneof value_matcher { - bool eq_bool = 2; - string eq_string = 3; - int32 eq_int = 4; + bool eq_bool = 3; + string eq_string = 4; + int32 eq_int = 5; - int64 lt_int = 5; - int64 gt_int = 6; - float lt_float = 7; - float gt_float = 8; + int64 lt_int = 6; + int64 gt_int = 7; + float lt_float = 8; + float gt_float = 9; - int64 lte_int = 9; - int64 gte_int = 10; + int64 lte_int = 10; + int64 gte_int = 11; + + MessageMatcher matches_tuple = 12; } } +message MessageMatcher { + repeated FieldValueMatcher field_value_matcher = 1; +} + enum LogicalOperation { LOGICAL_OPERATION_UNSPECIFIED = 0; AND = 1; @@ -56,18 +78,18 @@ enum LogicalOperation { } message SimpleAtomMatcher { - optional int32 tag = 1; + optional int32 atom_id = 1; - repeated KeyValueMatcher key_value_matcher = 2; + repeated FieldValueMatcher field_value_matcher = 2; } message AtomMatcher { - optional string name = 1; + optional int64 id = 1; message Combination { optional LogicalOperation operation = 1; - repeated string matcher = 2; + repeated int64 matcher = 2; } oneof contents { SimpleAtomMatcher simple_atom_matcher = 2; @@ -76,13 +98,13 @@ message AtomMatcher { } message SimplePredicate { - optional string start = 1; + optional int64 start = 1; - optional string stop = 2; + optional int64 stop = 2; optional bool count_nesting = 3 [default = true]; - optional string stop_all = 4; + optional int64 stop_all = 4; enum InitialValue { UNKNOWN = 0; @@ -90,16 +112,16 @@ message SimplePredicate { } optional InitialValue initial_value = 5 [default = FALSE]; - repeated KeyMatcher dimension = 6; + optional FieldMatcher dimensions = 6; } message Predicate { - optional string name = 1; + optional int64 id = 1; message Combination { optional LogicalOperation operation = 1; - repeated string predicate = 2; + repeated int64 predicate = 2; } oneof contents { @@ -113,36 +135,36 @@ message Bucket { } message MetricConditionLink { - optional string condition = 1; + optional int64 condition = 1; - repeated KeyMatcher key_in_what = 2; + optional FieldMatcher dimensions_in_what = 2; - repeated KeyMatcher key_in_condition = 3; + optional FieldMatcher dimensions_in_condition = 3; } message FieldFilter { - optional bool include_all = 1; - repeated int32 field_num = 2; + optional bool include_all = 1 [default = false]; + optional FieldMatcher fields = 2; } message EventMetric { - optional string name = 1; + optional int64 id = 1; - optional string what = 2; + optional int64 what = 2; - optional string condition = 3; + optional int64 condition = 3; repeated MetricConditionLink links = 4; } message CountMetric { - optional string name = 1; + optional int64 id = 1; - optional string what = 2; + optional int64 what = 2; - optional string condition = 3; + optional int64 condition = 3; - repeated KeyMatcher dimension = 4; + optional FieldMatcher dimensions = 4; optional Bucket bucket = 5; @@ -150,11 +172,11 @@ message CountMetric { } message DurationMetric { - optional string name = 1; + optional int64 id = 1; - optional string what = 2; + optional int64 what = 2; - optional string condition = 3; + optional int64 condition = 3; repeated MetricConditionLink links = 4; @@ -165,21 +187,21 @@ message DurationMetric { } optional AggregationType aggregation_type = 5 [default = SUM]; - repeated KeyMatcher dimension = 6; + optional FieldMatcher dimensions = 6; optional Bucket bucket = 7; } message GaugeMetric { - optional string name = 1; + optional int64 id = 1; - optional string what = 2; + optional int64 what = 2; - optional FieldFilter gauge_fields = 3; + optional FieldFilter gauge_fields_filter = 3; - optional string condition = 4; + optional int64 condition = 4; - repeated KeyMatcher dimension = 5; + optional FieldMatcher dimensions = 5; optional Bucket bucket = 6; @@ -187,15 +209,15 @@ message GaugeMetric { } message ValueMetric { - optional string name = 1; + optional int64 id = 1; - optional string what = 2; + optional int64 what = 2; optional int32 value_field = 3; - optional string condition = 4; + optional int64 condition = 4; - repeated KeyMatcher dimension = 5; + optional FieldMatcher dimensions = 5; optional Bucket bucket = 6; @@ -206,20 +228,15 @@ message ValueMetric { } message Alert { - optional string name = 1; + optional int64 id = 1; - optional string metric_name = 2; + optional int64 metric_id = 2; - message IncidentdDetails { - repeated int32 section = 1; - } - optional IncidentdDetails incidentd_details = 3; - - optional int32 number_of_buckets = 4; + optional int32 number_of_buckets = 3; - optional int32 refractory_period_secs = 5; + optional int32 refractory_period_secs = 4; - optional int64 trigger_if_sum_gt = 6; + optional int64 trigger_if_sum_gt = 5; } message AllowedLogSource { @@ -227,8 +244,40 @@ message AllowedLogSource { repeated string package = 2; } +message Alarm { + optional int64 id = 1; + optional int64 offset_millis = 2; + optional int64 period_millis = 3; +} + +message IncidentdDetails { + repeated int32 section = 1; +} + +message PerfettoDetails { + optional int32 perfetto_stuff = 1; +} + +message Subscription { + optional int64 id = 1; + + enum RuleType { + RULE_TYPE_UNSPECIFIED = 0; + ALARM = 1; + ALERT = 2; + } + optional RuleType rule_type = 2; + + optional int64 rule_id = 3; + + oneof subscriber_information { + IncidentdDetails incidentd_details = 4; + PerfettoDetails perfetto_details = 5; + } +} + message StatsdConfig { - optional string name = 1; + optional int64 id = 1; repeated EventMetric event_metric = 2; @@ -246,5 +295,11 @@ message StatsdConfig { repeated Alert alert = 9; - optional AllowedLogSource log_source = 10; + repeated Alarm alarm = 10; + + repeated Subscription subscription = 11; + + optional AllowedLogSource log_source = 12; + + repeated int64 no_report_metric = 13; } diff --git a/cmds/statsd/src/storage/StorageManager.cpp b/cmds/statsd/src/storage/StorageManager.cpp index 9919abf7532a..c542db2312ea 100644 --- a/cmds/statsd/src/storage/StorageManager.cpp +++ b/cmds/statsd/src/storage/StorageManager.cpp @@ -124,7 +124,7 @@ void StorageManager::sendBroadcast(const char* path, } if (index < 2) continue; - sendBroadcast(ConfigKey(uid, configName)); + sendBroadcast(ConfigKey(uid, StrToInt64(configName))); } } @@ -198,6 +198,7 @@ void StorageManager::readConfigFromDisk(map<ConfigKey, StatsdConfig>& configsMap index++; } if (index < 2) continue; + string file_name = StringPrintf("%s/%s", STATS_SERVICE_DIR, name); VLOG("full file %s", file_name.c_str()); int fd = open(file_name.c_str(), O_RDONLY | O_CLOEXEC); @@ -206,7 +207,7 @@ void StorageManager::readConfigFromDisk(map<ConfigKey, StatsdConfig>& configsMap if (android::base::ReadFdToString(fd, &content)) { StatsdConfig config; if (config.ParseFromString(content)) { - configsMap[ConfigKey(uid, configName)] = config; + configsMap[ConfigKey(uid, StrToInt64(configName))] = config; VLOG("map key uid=%d|name=%s", uid, name); } } diff --git a/cmds/statsd/tests/ConfigManager_test.cpp b/cmds/statsd/tests/ConfigManager_test.cpp index 3d923e246e71..3eac5d213c22 100644 --- a/cmds/statsd/tests/ConfigManager_test.cpp +++ b/cmds/statsd/tests/ConfigManager_test.cpp @@ -14,6 +14,7 @@ #include "src/config/ConfigManager.h" #include "src/metrics/MetricsManager.h" +#include "statsd_test_util.h" #include <gmock/gmock.h> #include <gtest/gtest.h> @@ -31,7 +32,7 @@ namespace os { namespace statsd { static ostream& operator<<(ostream& os, const StatsdConfig& config) { - return os << "StatsdConfig{name=" << config.name().c_str() << "}"; + return os << "StatsdConfig{id=" << config.id() << "}"; } } // namespace statsd @@ -50,19 +51,21 @@ public: /** * Validate that the ConfigKey is the one we wanted. */ -MATCHER_P2(ConfigKeyEq, uid, name, "") { - return arg.GetUid() == uid && arg.GetName() == name; +MATCHER_P2(ConfigKeyEq, uid, id, "") { + return arg.GetUid() == uid && (long long)arg.GetId() == (long long)id; } /** * Validate that the StatsdConfig is the one we wanted. */ -MATCHER_P(StatsdConfigEq, name, "") { - return arg.name() == name; +MATCHER_P(StatsdConfigEq, id, 0) { + return (long long)arg.id() == (long long)id; } +const int64_t testConfigId = 12345; + TEST(ConfigManagerTest, TestFakeConfig) { - auto metricsManager = std::make_unique<MetricsManager>(ConfigKey(0, "test"), + auto metricsManager = std::make_unique<MetricsManager>(ConfigKey(0, testConfigId), build_fake_config(), 1000, new UidMap()); EXPECT_TRUE(metricsManager->isConfigValid()); } @@ -77,13 +80,13 @@ TEST(ConfigManagerTest, TestAddUpdateRemove) { manager->AddListener(listener); StatsdConfig config91; - config91.set_name("91"); + config91.set_id(91); StatsdConfig config92; - config92.set_name("92"); + config92.set_id(92); StatsdConfig config93; - config93.set_name("93"); + config93.set_id(93); StatsdConfig config94; - config94.set_name("94"); + config94.set_id(94); { InSequence s; @@ -91,42 +94,46 @@ TEST(ConfigManagerTest, TestAddUpdateRemove) { manager->Startup(); // Add another one - EXPECT_CALL(*(listener.get()), OnConfigUpdated(ConfigKeyEq(1, "zzz"), StatsdConfigEq("91"))) + EXPECT_CALL(*(listener.get()), OnConfigUpdated(ConfigKeyEq(1, StringToId("zzz")), + StatsdConfigEq(91))) .RetiresOnSaturation(); - manager->UpdateConfig(ConfigKey(1, "zzz"), config91); + manager->UpdateConfig(ConfigKey(1, StringToId("zzz")), config91); // Update It - EXPECT_CALL(*(listener.get()), OnConfigUpdated(ConfigKeyEq(1, "zzz"), StatsdConfigEq("92"))) + EXPECT_CALL(*(listener.get()), OnConfigUpdated(ConfigKeyEq(1, StringToId("zzz")), + StatsdConfigEq(92))) .RetiresOnSaturation(); - manager->UpdateConfig(ConfigKey(1, "zzz"), config92); + manager->UpdateConfig(ConfigKey(1, StringToId("zzz")), config92); // Add one with the same uid but a different name - EXPECT_CALL(*(listener.get()), OnConfigUpdated(ConfigKeyEq(1, "yyy"), StatsdConfigEq("93"))) + EXPECT_CALL(*(listener.get()), OnConfigUpdated(ConfigKeyEq(1, StringToId("yyy")), + StatsdConfigEq(93))) .RetiresOnSaturation(); - manager->UpdateConfig(ConfigKey(1, "yyy"), config93); + manager->UpdateConfig(ConfigKey(1, StringToId("yyy")), config93); // Add one with the same name but a different uid - EXPECT_CALL(*(listener.get()), OnConfigUpdated(ConfigKeyEq(2, "zzz"), StatsdConfigEq("94"))) + EXPECT_CALL(*(listener.get()), OnConfigUpdated(ConfigKeyEq(2, StringToId("zzz")), + StatsdConfigEq(94))) .RetiresOnSaturation(); - manager->UpdateConfig(ConfigKey(2, "zzz"), config94); + manager->UpdateConfig(ConfigKey(2, StringToId("zzz")), config94); // Remove (1,yyy) - EXPECT_CALL(*(listener.get()), OnConfigRemoved(ConfigKeyEq(1, "yyy"))) + EXPECT_CALL(*(listener.get()), OnConfigRemoved(ConfigKeyEq(1, StringToId("yyy")))) .RetiresOnSaturation(); - manager->RemoveConfig(ConfigKey(1, "yyy")); + manager->RemoveConfig(ConfigKey(1, StringToId("yyy"))); // Remove (2,zzz) - EXPECT_CALL(*(listener.get()), OnConfigRemoved(ConfigKeyEq(2, "zzz"))) + EXPECT_CALL(*(listener.get()), OnConfigRemoved(ConfigKeyEq(2, StringToId("zzz")))) .RetiresOnSaturation(); - manager->RemoveConfig(ConfigKey(2, "zzz")); + manager->RemoveConfig(ConfigKey(2, StringToId("zzz"))); // Remove (1,zzz) - EXPECT_CALL(*(listener.get()), OnConfigRemoved(ConfigKeyEq(1, "zzz"))) + EXPECT_CALL(*(listener.get()), OnConfigRemoved(ConfigKeyEq(1, StringToId("zzz")))) .RetiresOnSaturation(); - manager->RemoveConfig(ConfigKey(1, "zzz")); + manager->RemoveConfig(ConfigKey(1, StringToId("zzz"))); // Remove (2,zzz) again and we shouldn't get the callback - manager->RemoveConfig(ConfigKey(2, "zzz")); + manager->RemoveConfig(ConfigKey(2, StringToId("zzz"))); } } @@ -142,16 +149,16 @@ TEST(ConfigManagerTest, TestRemoveUid) { StatsdConfig config; EXPECT_CALL(*(listener.get()), OnConfigUpdated(_, _)).Times(5); - EXPECT_CALL(*(listener.get()), OnConfigRemoved(ConfigKeyEq(2, "xxx"))); - EXPECT_CALL(*(listener.get()), OnConfigRemoved(ConfigKeyEq(2, "yyy"))); - EXPECT_CALL(*(listener.get()), OnConfigRemoved(ConfigKeyEq(2, "zzz"))); + EXPECT_CALL(*(listener.get()), OnConfigRemoved(ConfigKeyEq(2, StringToId("xxx")))); + EXPECT_CALL(*(listener.get()), OnConfigRemoved(ConfigKeyEq(2, StringToId("yyy")))); + EXPECT_CALL(*(listener.get()), OnConfigRemoved(ConfigKeyEq(2, StringToId("zzz")))); manager->Startup(); - manager->UpdateConfig(ConfigKey(1, "aaa"), config); - manager->UpdateConfig(ConfigKey(2, "xxx"), config); - manager->UpdateConfig(ConfigKey(2, "yyy"), config); - manager->UpdateConfig(ConfigKey(2, "zzz"), config); - manager->UpdateConfig(ConfigKey(3, "bbb"), config); + manager->UpdateConfig(ConfigKey(1, StringToId("aaa")), config); + manager->UpdateConfig(ConfigKey(2, StringToId("xxx")), config); + manager->UpdateConfig(ConfigKey(2, StringToId("yyy")), config); + manager->UpdateConfig(ConfigKey(2, StringToId("zzz")), config); + manager->UpdateConfig(ConfigKey(3, StringToId("bbb")), config); manager->RemoveConfigs(2); } diff --git a/cmds/statsd/tests/LogEntryMatcher_test.cpp b/cmds/statsd/tests/LogEntryMatcher_test.cpp index 1ec91558f790..111b4ba7a32c 100644 --- a/cmds/statsd/tests/LogEntryMatcher_test.cpp +++ b/cmds/statsd/tests/LogEntryMatcher_test.cpp @@ -32,65 +32,313 @@ const int FIELD_ID_1 = 1; const int FIELD_ID_2 = 2; const int FIELD_ID_3 = 2; +const int ATTRIBUTION_UID_FIELD_ID = 1; +const int ATTRIBUTION_TAG_FIELD_ID = 2; + // Private API from liblog. extern "C" void android_log_rewind(android_log_context ctx); #ifdef __ANDROID__ TEST(AtomMatcherTest, TestSimpleMatcher) { + UidMap uidMap; + // Set up the matcher AtomMatcher matcher; auto simpleMatcher = matcher.mutable_simple_atom_matcher(); - simpleMatcher->set_tag(TAG_ID); + simpleMatcher->set_atom_id(TAG_ID); LogEvent event(TAG_ID, 0); + EXPECT_TRUE(event.write(11)); event.init(); // Test - EXPECT_TRUE(matchesSimple(*simpleMatcher, event)); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + + // Wrong tag id. + simpleMatcher->set_atom_id(TAG_ID + 1); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); +} + +TEST(AtomMatcherTest, TestAttributionMatcher) { + UidMap uidMap; + AttributionNode attribution_node1; + attribution_node1.set_uid(1111); + attribution_node1.set_tag("location1"); + + AttributionNode attribution_node2; + attribution_node2.set_uid(2222); + attribution_node2.set_tag("location2"); + + AttributionNode attribution_node3; + attribution_node3.set_uid(3333); + attribution_node3.set_tag("location3"); + std::vector<AttributionNode> attribution_nodes = + { attribution_node1, attribution_node2, attribution_node3 }; + + // Set up the event + LogEvent event(TAG_ID, 0); + event.write(attribution_nodes); + event.write("some value"); + // Convert to a LogEvent + event.init(); + + // Set up the matcher + AtomMatcher matcher; + auto simpleMatcher = matcher.mutable_simple_atom_matcher(); + simpleMatcher->set_atom_id(TAG_ID); + + // Match first node. + auto attributionMatcher = simpleMatcher->add_field_value_matcher(); + attributionMatcher->set_field(FIELD_ID_1); + attributionMatcher->set_position(Position::FIRST); + attributionMatcher->mutable_matches_tuple()->add_field_value_matcher()->set_field( + ATTRIBUTION_TAG_FIELD_ID); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string("tag"); + + auto fieldMatcher = simpleMatcher->add_field_value_matcher(); + fieldMatcher->set_field(FIELD_ID_2); + fieldMatcher->set_eq_string("some value"); + + // Tag not matched. + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0) + ->set_eq_string("location3"); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0) + ->set_eq_string("location1"); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + + // Match last node. + attributionMatcher->set_position(Position::LAST); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0) + ->set_eq_string("location3"); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + + // Match any node. + attributionMatcher->set_position(Position::ANY); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0) + ->set_eq_string("location1"); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0) + ->set_eq_string("location2"); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0) + ->set_eq_string("location3"); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0) + ->set_eq_string("location4"); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); + + // Attribution match but primitive field not match. + attributionMatcher->set_position(Position::ANY); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0) + ->set_eq_string("location2"); + fieldMatcher->set_eq_string("wrong value"); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); + + fieldMatcher->set_eq_string("some value"); + + // Uid match. + attributionMatcher->set_position(Position::ANY); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_field( + ATTRIBUTION_UID_FIELD_ID); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string("pkg0"); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); + + uidMap.updateMap({1111, 1111, 2222, 3333, 3333} /* uid list */, + {1, 1, 2, 1, 2} /* version list */, + {android::String16("pkg0"), android::String16("pkg1"), + android::String16("pkg1"), android::String16("Pkg2"), + android::String16("PkG3")} /* package name list */); + + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0) + ->set_eq_string("pkg3"); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0) + ->set_eq_string("pkg2"); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0) + ->set_eq_string("pkg1"); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0) + ->set_eq_string("pkg0"); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + + attributionMatcher->set_position(Position::FIRST); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0) + ->set_eq_string("pkg0"); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0) + ->set_eq_string("pkg3"); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0) + ->set_eq_string("pkg2"); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0) + ->set_eq_string("pkg1"); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + + attributionMatcher->set_position(Position::LAST); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0) + ->set_eq_string("pkg0"); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0) + ->set_eq_string("pkg3"); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0) + ->set_eq_string("pkg2"); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0) + ->set_eq_string("pkg1"); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); + + // Uid + tag. + attributionMatcher->set_position(Position::ANY); + attributionMatcher->mutable_matches_tuple()->add_field_value_matcher()->set_field( + ATTRIBUTION_TAG_FIELD_ID); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0) + ->set_eq_string("pkg0"); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1) + ->set_eq_string("location1"); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0) + ->set_eq_string("pkg1"); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1) + ->set_eq_string("location1"); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0) + ->set_eq_string("pkg1"); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1) + ->set_eq_string("location2"); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0) + ->set_eq_string("pkg2"); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1) + ->set_eq_string("location3"); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0) + ->set_eq_string("pkg3"); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1) + ->set_eq_string("location3"); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0) + ->set_eq_string("pkg3"); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1) + ->set_eq_string("location1"); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); + + attributionMatcher->set_position(Position::FIRST); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0) + ->set_eq_string("pkg0"); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1) + ->set_eq_string("location1"); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0) + ->set_eq_string("pkg1"); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1) + ->set_eq_string("location1"); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0) + ->set_eq_string("pkg1"); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1) + ->set_eq_string("location2"); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0) + ->set_eq_string("pkg2"); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1) + ->set_eq_string("location3"); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0) + ->set_eq_string("pkg3"); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1) + ->set_eq_string("location3"); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0) + ->set_eq_string("pkg3"); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1) + ->set_eq_string("location1"); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); + + attributionMatcher->set_position(Position::LAST); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0) + ->set_eq_string("pkg0"); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1) + ->set_eq_string("location1"); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0) + ->set_eq_string("pkg1"); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1) + ->set_eq_string("location1"); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0) + ->set_eq_string("pkg1"); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1) + ->set_eq_string("location2"); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0) + ->set_eq_string("pkg2"); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1) + ->set_eq_string("location3"); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0) + ->set_eq_string("pkg3"); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1) + ->set_eq_string("location3"); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0) + ->set_eq_string("pkg3"); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1) + ->set_eq_string("location1"); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); } TEST(AtomMatcherTest, TestBoolMatcher) { + UidMap uidMap; // Set up the matcher AtomMatcher matcher; auto simpleMatcher = matcher.mutable_simple_atom_matcher(); - simpleMatcher->set_tag(TAG_ID); - auto keyValue1 = simpleMatcher->add_key_value_matcher(); - keyValue1->mutable_key_matcher()->set_key(FIELD_ID_1); - auto keyValue2 = simpleMatcher->add_key_value_matcher(); - keyValue2->mutable_key_matcher()->set_key(FIELD_ID_2); + simpleMatcher->set_atom_id(TAG_ID); + auto keyValue1 = simpleMatcher->add_field_value_matcher(); + keyValue1->set_field(FIELD_ID_1); + auto keyValue2 = simpleMatcher->add_field_value_matcher(); + keyValue2->set_field(FIELD_ID_2); // Set up the event LogEvent event(TAG_ID, 0); - event.write(true); - event.write(false); + EXPECT_TRUE(event.write(true)); + EXPECT_TRUE(event.write(false)); // Convert to a LogEvent event.init(); // Test keyValue1->set_eq_bool(true); keyValue2->set_eq_bool(false); - EXPECT_TRUE(matchesSimple(*simpleMatcher, event)); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); keyValue1->set_eq_bool(false); keyValue2->set_eq_bool(false); - EXPECT_FALSE(matchesSimple(*simpleMatcher, event)); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); - keyValue1->set_eq_bool(true); - keyValue2->set_eq_bool(false); - EXPECT_TRUE(matchesSimple(*simpleMatcher, event)); + keyValue1->set_eq_bool(false); + keyValue2->set_eq_bool(true); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); keyValue1->set_eq_bool(true); keyValue2->set_eq_bool(true); - EXPECT_FALSE(matchesSimple(*simpleMatcher, event)); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); } TEST(AtomMatcherTest, TestStringMatcher) { + UidMap uidMap; // Set up the matcher AtomMatcher matcher; auto simpleMatcher = matcher.mutable_simple_atom_matcher(); - simpleMatcher->set_tag(TAG_ID); - auto keyValue = simpleMatcher->add_key_value_matcher(); - keyValue->mutable_key_matcher()->set_key(FIELD_ID_1); + simpleMatcher->set_atom_id(TAG_ID); + auto keyValue = simpleMatcher->add_field_value_matcher(); + keyValue->set_field(FIELD_ID_1); keyValue->set_eq_string("some value"); // Set up the event @@ -100,18 +348,19 @@ TEST(AtomMatcherTest, TestStringMatcher) { event.init(); // Test - EXPECT_TRUE(matchesSimple(*simpleMatcher, event)); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); } TEST(AtomMatcherTest, TestMultiFieldsMatcher) { + UidMap uidMap; // Set up the matcher AtomMatcher matcher; auto simpleMatcher = matcher.mutable_simple_atom_matcher(); - simpleMatcher->set_tag(TAG_ID); - auto keyValue1 = simpleMatcher->add_key_value_matcher(); - keyValue1->mutable_key_matcher()->set_key(FIELD_ID_1); - auto keyValue2 = simpleMatcher->add_key_value_matcher(); - keyValue2->mutable_key_matcher()->set_key(FIELD_ID_2); + simpleMatcher->set_atom_id(TAG_ID); + auto keyValue1 = simpleMatcher->add_field_value_matcher(); + keyValue1->set_field(FIELD_ID_1); + auto keyValue2 = simpleMatcher->add_field_value_matcher(); + keyValue2->set_field(FIELD_ID_2); // Set up the event LogEvent event(TAG_ID, 0); @@ -124,25 +373,26 @@ TEST(AtomMatcherTest, TestMultiFieldsMatcher) { // Test keyValue1->set_eq_int(2); keyValue2->set_eq_int(3); - EXPECT_TRUE(matchesSimple(*simpleMatcher, event)); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); keyValue1->set_eq_int(2); keyValue2->set_eq_int(4); - EXPECT_FALSE(matchesSimple(*simpleMatcher, event)); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); keyValue1->set_eq_int(4); keyValue2->set_eq_int(3); - EXPECT_FALSE(matchesSimple(*simpleMatcher, event)); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); } TEST(AtomMatcherTest, TestIntComparisonMatcher) { + UidMap uidMap; // Set up the matcher AtomMatcher matcher; auto simpleMatcher = matcher.mutable_simple_atom_matcher(); - simpleMatcher->set_tag(TAG_ID); - auto keyValue = simpleMatcher->add_key_value_matcher(); - keyValue->mutable_key_matcher()->set_key(FIELD_ID_1); + simpleMatcher->set_atom_id(TAG_ID); + auto keyValue = simpleMatcher->add_field_value_matcher(); + keyValue->set_field(FIELD_ID_1); // Set up the event LogEvent event(TAG_ID, 0); @@ -153,82 +403,83 @@ TEST(AtomMatcherTest, TestIntComparisonMatcher) { // eq_int keyValue->set_eq_int(10); - EXPECT_FALSE(matchesSimple(*simpleMatcher, event)); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); keyValue->set_eq_int(11); - EXPECT_TRUE(matchesSimple(*simpleMatcher, event)); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); keyValue->set_eq_int(12); - EXPECT_FALSE(matchesSimple(*simpleMatcher, event)); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); // lt_int keyValue->set_lt_int(10); - EXPECT_FALSE(matchesSimple(*simpleMatcher, event)); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); keyValue->set_lt_int(11); - EXPECT_FALSE(matchesSimple(*simpleMatcher, event)); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); keyValue->set_lt_int(12); - EXPECT_TRUE(matchesSimple(*simpleMatcher, event)); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); // lte_int keyValue->set_lte_int(10); - EXPECT_FALSE(matchesSimple(*simpleMatcher, event)); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); keyValue->set_lte_int(11); - EXPECT_TRUE(matchesSimple(*simpleMatcher, event)); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); keyValue->set_lte_int(12); - EXPECT_TRUE(matchesSimple(*simpleMatcher, event)); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); // gt_int keyValue->set_gt_int(10); - EXPECT_TRUE(matchesSimple(*simpleMatcher, event)); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); keyValue->set_gt_int(11); - EXPECT_FALSE(matchesSimple(*simpleMatcher, event)); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); keyValue->set_gt_int(12); - EXPECT_FALSE(matchesSimple(*simpleMatcher, event)); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); // gte_int keyValue->set_gte_int(10); - EXPECT_TRUE(matchesSimple(*simpleMatcher, event)); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); keyValue->set_gte_int(11); - EXPECT_TRUE(matchesSimple(*simpleMatcher, event)); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); keyValue->set_gte_int(12); - EXPECT_FALSE(matchesSimple(*simpleMatcher, event)); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); } TEST(AtomMatcherTest, TestFloatComparisonMatcher) { + UidMap uidMap; // Set up the matcher AtomMatcher matcher; auto simpleMatcher = matcher.mutable_simple_atom_matcher(); - simpleMatcher->set_tag(TAG_ID); + simpleMatcher->set_atom_id(TAG_ID); - auto keyValue = simpleMatcher->add_key_value_matcher(); - keyValue->mutable_key_matcher()->set_key(FIELD_ID_1); + auto keyValue = simpleMatcher->add_field_value_matcher(); + keyValue->set_field(FIELD_ID_1); LogEvent event1(TAG_ID, 0); keyValue->set_lt_float(10.0); event1.write(10.1f); event1.init(); - EXPECT_FALSE(matchesSimple(*simpleMatcher, event1)); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event1)); LogEvent event2(TAG_ID, 0); event2.write(9.9f); event2.init(); - EXPECT_TRUE(matchesSimple(*simpleMatcher, event2)); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event2)); LogEvent event3(TAG_ID, 0); event3.write(10.1f); event3.init(); keyValue->set_gt_float(10.0); - EXPECT_TRUE(matchesSimple(*simpleMatcher, event3)); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event3)); LogEvent event4(TAG_ID, 0); event4.write(9.9f); event4.init(); - EXPECT_FALSE(matchesSimple(*simpleMatcher, event4)); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event4)); } // Helper for the composite matchers. void addSimpleMatcher(SimpleAtomMatcher* simpleMatcher, int tag, int key, int val) { - simpleMatcher->set_tag(tag); - auto keyValue = simpleMatcher->add_key_value_matcher(); - keyValue->mutable_key_matcher()->set_key(key); + simpleMatcher->set_atom_id(tag); + auto keyValue = simpleMatcher->add_field_value_matcher(); + keyValue->set_field(key); keyValue->set_eq_int(val); } diff --git a/cmds/statsd/tests/LogEvent_test.cpp b/cmds/statsd/tests/LogEvent_test.cpp new file mode 100644 index 000000000000..fd28460e8e01 --- /dev/null +++ b/cmds/statsd/tests/LogEvent_test.cpp @@ -0,0 +1,573 @@ +// Copyright (C) 2017 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. + +#include <gtest/gtest.h> +#include <log/log_event_list.h> +#include "src/logd/LogEvent.h" + +#ifdef __ANDROID__ + +namespace android { +namespace os { +namespace statsd { + +TEST(LogEventTest, testEmptyEvent) { + const int32_t TAG_ID = 123; + LogEvent event(TAG_ID, 0); + event.init(); + + DimensionsValue dimensionsValue; + EXPECT_FALSE(event.GetSimpleAtomDimensionsValueProto(234, &dimensionsValue)); + FieldMatcher dimensions; + dimensions.set_field(event.GetTagId()); + EXPECT_FALSE(event.GetAtomDimensionsValueProto(dimensions, &dimensionsValue)); + + dimensions.add_child()->set_field(3); + dimensions.mutable_child(0)->set_position(Position::FIRST); + EXPECT_FALSE(event.GetAtomDimensionsValueProto(dimensions, &dimensionsValue)); + + dimensions.mutable_child(0)->set_position(Position::ANY); + EXPECT_FALSE(event.GetAtomDimensionsValueProto(dimensions, &dimensionsValue)); + + dimensions.mutable_child(0)->set_position(Position::LAST); + EXPECT_FALSE(event.GetAtomDimensionsValueProto(dimensions, &dimensionsValue)); +} + +TEST(LogEventTest, testRepeatedAttributionNode) { + const int32_t TAG_ID = 123; + LogEvent event(TAG_ID, 0); + AttributionNode attribution_node1; + attribution_node1.set_uid(1111); + attribution_node1.set_tag("locationService"); + + AttributionNode attribution_node2; + attribution_node2.set_uid(2222); + attribution_node2.set_tag("locationService2"); + + AttributionNode attribution_node3; + attribution_node3.set_uid(3333); + attribution_node3.set_tag("locationService3"); + std::vector<AttributionNode> attribution_nodes = + {attribution_node1, attribution_node2, attribution_node3}; + + // 1nd field: int32. + EXPECT_TRUE(event.write(int32_t(11))); + // 2rd field: float. + EXPECT_TRUE(event.write(3.45f)); + // Here it assume that the atom proto contains a repeated AttributionNode field. + // 3rd field: attribution node. This is repeated field. + EXPECT_TRUE(event.write(attribution_nodes)); + // 4th field: bool. + EXPECT_TRUE(event.write(true)); + // 5th field: long. + EXPECT_TRUE(event.write(uint64_t(1234))); + + event.init(); + + DimensionsValue dimensionsValue; + // Query single primitive fields. + EXPECT_TRUE(event.GetSimpleAtomDimensionsValueProto(1, &dimensionsValue)); + EXPECT_EQ(dimensionsValue.field(), event.GetTagId()); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value_size(), 1); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).field(), 1); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_int(), 11); + + EXPECT_TRUE(event.GetSimpleAtomDimensionsValueProto(2, &dimensionsValue)); + EXPECT_EQ(dimensionsValue.field(), event.GetTagId()); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value_size(), 1); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).field(), 2); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_float(), 3.45f); + + EXPECT_TRUE(event.GetSimpleAtomDimensionsValueProto(4, &dimensionsValue)); + EXPECT_EQ(dimensionsValue.field(), event.GetTagId()); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value_size(), 1); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).field(), 4); + // The bool value is stored in value_int field as logD does not support bool. + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_int(), true); + + EXPECT_TRUE(event.GetSimpleAtomDimensionsValueProto(5, &dimensionsValue)); + EXPECT_EQ(dimensionsValue.field(), event.GetTagId()); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value_size(), 1); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).field(), 5); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_long(), long(1234)); + + // First attribution. + FieldMatcher first_uid_dimensions; + first_uid_dimensions.set_field(event.GetTagId()); + first_uid_dimensions.add_child()->set_field(3); + first_uid_dimensions.mutable_child(0)->set_position(Position::FIRST); + first_uid_dimensions.mutable_child(0)->add_child()->set_field(1); + EXPECT_TRUE(event.GetAtomDimensionsValueProto(first_uid_dimensions, &dimensionsValue)); + EXPECT_EQ(dimensionsValue.field(), event.GetTagId()); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value_size(), 1); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).field(), 3); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple() + .dimensions_value_size(), 1); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple() + .dimensions_value(0).field(), 1); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple() + .dimensions_value(0).value_int(), 1111); + + FieldMatcher first_tag_dimensions = first_uid_dimensions; + first_tag_dimensions.mutable_child(0)->mutable_child(0)->set_field(2); + EXPECT_TRUE(event.GetAtomDimensionsValueProto(first_tag_dimensions, &dimensionsValue)); + EXPECT_EQ(dimensionsValue.field(), event.GetTagId()); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value_size(), 1); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).field(), 3); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple() + .dimensions_value_size(), 1); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple() + .dimensions_value(0).field(), 2); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple() + .dimensions_value(0).value_str(), "locationService"); + + FieldMatcher first_attribution_dimensions = first_uid_dimensions; + first_attribution_dimensions.mutable_child(0)->add_child()->set_field(2); + EXPECT_TRUE(event.GetAtomDimensionsValueProto(first_attribution_dimensions, &dimensionsValue)); + EXPECT_EQ(dimensionsValue.field(), event.GetTagId()); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value_size(), 1); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).field(), 3); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple() + .dimensions_value_size(), 2); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple() + .dimensions_value(0).field(), 1); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple() + .dimensions_value(0).value_int(), 1111); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple() + .dimensions_value(1).field(), 2); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple() + .dimensions_value(1).value_str(), "locationService"); + + FieldMatcher last_attribution_dimensions = first_attribution_dimensions; + last_attribution_dimensions.mutable_child(0)->set_position(Position::LAST); + EXPECT_TRUE(event.GetAtomDimensionsValueProto(last_attribution_dimensions, &dimensionsValue)); + EXPECT_EQ(dimensionsValue.field(), event.GetTagId()); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value_size(), 1); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).field(), 3); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple() + .dimensions_value_size(), 2); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple() + .dimensions_value(0).field(), 1); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple() + .dimensions_value(0).value_int(), 3333); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple() + .dimensions_value(1).field(), 2); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple() + .dimensions_value(1).value_str(), "locationService3"); + + FieldMatcher any_attribution_dimensions = first_attribution_dimensions; + any_attribution_dimensions.mutable_child(0)->set_position(Position::ANY); + std::vector<DimensionsValue> dimensionsValues; + event.GetAtomDimensionsValueProtos(any_attribution_dimensions, &dimensionsValues); + EXPECT_EQ(dimensionsValues.size(), 3u); + EXPECT_EQ(dimensionsValues[0].field(), event.GetTagId()); + EXPECT_EQ(dimensionsValues[0].value_tuple().dimensions_value_size(), 1); + EXPECT_EQ(dimensionsValues[0].value_tuple().dimensions_value(0).field(), 3); + EXPECT_EQ(dimensionsValues[0].value_tuple().dimensions_value(0).value_tuple() + .dimensions_value_size(), 2); + EXPECT_EQ(dimensionsValues[0].value_tuple().dimensions_value(0).value_tuple() + .dimensions_value(0).field(), 1); + EXPECT_EQ(dimensionsValues[0].value_tuple().dimensions_value(0).value_tuple() + .dimensions_value(0).value_int(), 1111); + EXPECT_EQ(dimensionsValues[0].value_tuple().dimensions_value(0).value_tuple() + .dimensions_value(1).field(), 2); + EXPECT_EQ(dimensionsValues[0].value_tuple().dimensions_value(0).value_tuple() + .dimensions_value(1).value_str(), "locationService"); + EXPECT_EQ(dimensionsValues[1].field(), event.GetTagId()); + EXPECT_EQ(dimensionsValues[1].value_tuple().dimensions_value_size(), 1); + EXPECT_EQ(dimensionsValues[1].value_tuple().dimensions_value(0).field(), 3); + EXPECT_EQ(dimensionsValues[1].value_tuple().dimensions_value(0).value_tuple() + .dimensions_value_size(), 2); + EXPECT_EQ(dimensionsValues[1].value_tuple().dimensions_value(0).value_tuple() + .dimensions_value(0).field(), 1); + EXPECT_EQ(dimensionsValues[1].value_tuple().dimensions_value(0).value_tuple() + .dimensions_value(0).value_int(), 2222); + EXPECT_EQ(dimensionsValues[1].value_tuple().dimensions_value(0).value_tuple() + .dimensions_value(1).field(), 2); + EXPECT_EQ(dimensionsValues[1].value_tuple().dimensions_value(0).value_tuple() + .dimensions_value(1).value_str(), "locationService2"); + EXPECT_EQ(dimensionsValues[2].field(), event.GetTagId()); + EXPECT_EQ(dimensionsValues[2].value_tuple().dimensions_value_size(), 1); + EXPECT_EQ(dimensionsValues[2].value_tuple().dimensions_value(0).field(), 3); + EXPECT_EQ(dimensionsValues[2].value_tuple().dimensions_value(0).value_tuple() + .dimensions_value_size(), 2); + EXPECT_EQ(dimensionsValues[2].value_tuple().dimensions_value(0).value_tuple() + .dimensions_value(0).field(), 1); + EXPECT_EQ(dimensionsValues[2].value_tuple().dimensions_value(0).value_tuple() + .dimensions_value(0).value_int(), 3333); + EXPECT_EQ(dimensionsValues[2].value_tuple().dimensions_value(0).value_tuple() + .dimensions_value(1).field(), 2); + EXPECT_EQ(dimensionsValues[2].value_tuple().dimensions_value(0).value_tuple() + .dimensions_value(1).value_str(), "locationService3"); + + FieldMatcher mixed_dimensions = any_attribution_dimensions; + mixed_dimensions.add_child()->set_field(1000); + mixed_dimensions.add_child()->set_field(6); // missing field. + mixed_dimensions.add_child()->set_field(3); // position not set. + mixed_dimensions.add_child()->set_field(5); + mixed_dimensions.add_child()->set_field(1); + dimensionsValues.clear(); + event.GetAtomDimensionsValueProtos(mixed_dimensions, &dimensionsValues); + EXPECT_EQ(dimensionsValues.size(), 3u); + EXPECT_EQ(dimensionsValues[0].field(), event.GetTagId()); + EXPECT_EQ(dimensionsValues[0].value_tuple().dimensions_value_size(), 3); + EXPECT_EQ(dimensionsValues[0].value_tuple().dimensions_value(0).field(), 3); + EXPECT_EQ(dimensionsValues[0].value_tuple().dimensions_value(0).value_tuple().dimensions_value_size(), 2); + EXPECT_EQ(dimensionsValues[0].value_tuple().dimensions_value(0).value_tuple().dimensions_value(0).field(), 1); + EXPECT_EQ(dimensionsValues[0].value_tuple().dimensions_value(0).value_tuple().dimensions_value(0).value_int(), + 1111); + EXPECT_EQ(dimensionsValues[0].value_tuple().dimensions_value(0).value_tuple().dimensions_value(1).field(), 2); + EXPECT_EQ(dimensionsValues[0].value_tuple().dimensions_value(0).value_tuple().dimensions_value(1).value_str(), + "locationService"); + EXPECT_EQ(dimensionsValues[0].value_tuple().dimensions_value(1).field(), 5); + EXPECT_EQ(dimensionsValues[0].value_tuple().dimensions_value(1).value_long(), long(1234)); + EXPECT_EQ(dimensionsValues[0].value_tuple().dimensions_value(2).field(), 1); + EXPECT_EQ(dimensionsValues[0].value_tuple().dimensions_value(2).value_int(), 11); + + EXPECT_EQ(dimensionsValues[1].field(), event.GetTagId()); + EXPECT_EQ(dimensionsValues[1].value_tuple().dimensions_value_size(), 3); + EXPECT_EQ(dimensionsValues[1].value_tuple().dimensions_value(0).field(), 3); + EXPECT_EQ(dimensionsValues[1].value_tuple().dimensions_value(0).value_tuple().dimensions_value_size(), 2); + EXPECT_EQ(dimensionsValues[1].value_tuple().dimensions_value(0).value_tuple().dimensions_value(0).field(), 1); + EXPECT_EQ(dimensionsValues[1].value_tuple().dimensions_value(0).value_tuple().dimensions_value(0).value_int(), + 2222); + EXPECT_EQ(dimensionsValues[1].value_tuple().dimensions_value(0).value_tuple().dimensions_value(1).field(), 2); + EXPECT_EQ(dimensionsValues[1].value_tuple().dimensions_value(0).value_tuple().dimensions_value(1).value_str(), + "locationService2"); + EXPECT_EQ(dimensionsValues[1].value_tuple().dimensions_value(1).field(), 5); + EXPECT_EQ(dimensionsValues[1].value_tuple().dimensions_value(1).value_long(), long(1234)); + EXPECT_EQ(dimensionsValues[1].value_tuple().dimensions_value(2).field(), 1); + EXPECT_EQ(dimensionsValues[1].value_tuple().dimensions_value(2).value_int(), 11); + + EXPECT_EQ(dimensionsValues[2].field(), event.GetTagId()); + EXPECT_EQ(dimensionsValues[2].value_tuple().dimensions_value_size(), 3); + EXPECT_EQ(dimensionsValues[2].value_tuple().dimensions_value(0).field(), 3); + EXPECT_EQ(dimensionsValues[2].value_tuple().dimensions_value(0).value_tuple().dimensions_value_size(), 2); + EXPECT_EQ(dimensionsValues[2].value_tuple().dimensions_value(0).value_tuple().dimensions_value(0).field(), 1); + EXPECT_EQ(dimensionsValues[2].value_tuple().dimensions_value(0).value_tuple().dimensions_value(0).value_int(), + 3333); + EXPECT_EQ(dimensionsValues[2].value_tuple().dimensions_value(0).value_tuple().dimensions_value(1).field(), 2); + EXPECT_EQ(dimensionsValues[2].value_tuple().dimensions_value(0).value_tuple().dimensions_value(1).value_str(), + "locationService3"); + EXPECT_EQ(dimensionsValues[2].value_tuple().dimensions_value(1).field(), 5); + EXPECT_EQ(dimensionsValues[2].value_tuple().dimensions_value(1).value_long(), long(1234)); + EXPECT_EQ(dimensionsValues[2].value_tuple().dimensions_value(2).field(), 1); + EXPECT_EQ(dimensionsValues[2].value_tuple().dimensions_value(2).value_int(), 11); + + FieldMatcher wrong_dimensions = mixed_dimensions; + // Wrong tagId. + wrong_dimensions.set_field(event.GetTagId() + 100); + dimensionsValues.clear(); + event.GetAtomDimensionsValueProtos(wrong_dimensions, &dimensionsValues); + EXPECT_TRUE(dimensionsValues.empty()); +} + +TEST(LogEventTest, testMessageField) { + const int32_t TAG_ID = 123; + LogEvent event(TAG_ID, 0); + AttributionNode attribution_node1; + attribution_node1.set_uid(1111); + attribution_node1.set_tag("locationService"); + + AttributionNode attribution_node2; + attribution_node2.set_uid(2222); + attribution_node2.set_tag("locationService2"); + + // 1nd field: int32. + EXPECT_TRUE(event.write(int32_t(11))); + // 2rd field: float. + EXPECT_TRUE(event.write(3.45f)); + // Here it assume that the atom proto contains two optional AttributionNode fields. + // 3rd field: attribution node. This is not repeated field. + EXPECT_TRUE(event.write(attribution_node1)); + // 4th field: another attribution field. This is not repeated field. + EXPECT_TRUE(event.write(attribution_node2)); + // 5th field: bool. + EXPECT_TRUE(event.write(true)); + // 6th field: long. + EXPECT_TRUE(event.write(uint64_t(1234))); + + event.init(); + + FieldMatcher uid_dimensions1; + uid_dimensions1.set_field(event.GetTagId()); + uid_dimensions1.add_child()->set_field(3); + uid_dimensions1.mutable_child(0)->add_child()->set_field(1); + + FieldMatcher tag_dimensions1; + tag_dimensions1.set_field(event.GetTagId()); + tag_dimensions1.add_child()->set_field(3); + tag_dimensions1.mutable_child(0)->add_child()->set_field(2); + + FieldMatcher attribution_dimensions1; + attribution_dimensions1.set_field(event.GetTagId()); + attribution_dimensions1.add_child()->set_field(3); + attribution_dimensions1.mutable_child(0)->add_child()->set_field(1); + attribution_dimensions1.mutable_child(0)->add_child()->set_field(2); + + FieldMatcher uid_dimensions2 = uid_dimensions1; + uid_dimensions2.mutable_child(0)->set_field(4); + + FieldMatcher tag_dimensions2 = tag_dimensions1; + tag_dimensions2.mutable_child(0)->set_field(4); + + FieldMatcher attribution_dimensions2 = attribution_dimensions1; + attribution_dimensions2.mutable_child(0)->set_field(4); + + FieldMatcher mixed_dimensions = attribution_dimensions1; + mixed_dimensions.add_child()->set_field(4); + mixed_dimensions.mutable_child(1)->add_child()->set_field(1); + mixed_dimensions.add_child()->set_field(1000); + mixed_dimensions.add_child()->set_field(5); + mixed_dimensions.add_child()->set_field(1); + + DimensionsValue dimensionsValue; + + // Query single primitive fields. + EXPECT_TRUE(event.GetSimpleAtomDimensionsValueProto(1, &dimensionsValue)); + EXPECT_EQ(dimensionsValue.field(), event.GetTagId()); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value_size(), 1); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).field(), 1); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_int(), 11); + + EXPECT_TRUE(event.GetSimpleAtomDimensionsValueProto(2, &dimensionsValue)); + EXPECT_EQ(dimensionsValue.field(), event.GetTagId()); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value_size(), 1); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).field(), 2); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_float(), 3.45f); + + EXPECT_TRUE(event.GetSimpleAtomDimensionsValueProto(5, &dimensionsValue)); + EXPECT_EQ(dimensionsValue.field(), event.GetTagId()); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value_size(), 1); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).field(), 5); + // The bool value is stored in value_int field as logD does not support bool. + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_int(), true); + + EXPECT_TRUE(event.GetSimpleAtomDimensionsValueProto(6, &dimensionsValue)); + EXPECT_EQ(dimensionsValue.field(), event.GetTagId()); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value_size(), 1); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).field(), 6); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_long(), long(1234)); + + // Query atom field 3: attribution node uid field only. + EXPECT_TRUE(event.GetAtomDimensionsValueProto(uid_dimensions1, &dimensionsValue)); + EXPECT_EQ(dimensionsValue.field(), event.GetTagId()); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value_size(), 1); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).field(), 3); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple() + .dimensions_value_size(), 1); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple() + .dimensions_value(0).field(), 1); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple() + .dimensions_value(0).value_int(), 1111); + + // Query atom field 3: attribution node tag field only. + EXPECT_TRUE(event.GetAtomDimensionsValueProto(tag_dimensions1, &dimensionsValue)); + EXPECT_EQ(dimensionsValue.field(), event.GetTagId()); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value_size(), 1); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).field(), 3); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple() + .dimensions_value_size(), 1); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple() + .dimensions_value(0).field(), 2); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple() + .dimensions_value(0).value_str(), "locationService"); + + // Query atom field 3: attribution node uid + tag fields. + EXPECT_TRUE(event.GetAtomDimensionsValueProto(attribution_dimensions1, &dimensionsValue)); + EXPECT_EQ(dimensionsValue.field(), event.GetTagId()); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value_size(), 1); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).field(), 3); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple() + .dimensions_value_size(), 2); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple() + .dimensions_value(0).field(), 1); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple() + .dimensions_value(0).value_int(), 1111); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple() + .dimensions_value(1).field(), 2); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple() + .dimensions_value(1).value_str(), "locationService"); + + // Query atom field 4: attribution node uid field only. + EXPECT_TRUE(event.GetAtomDimensionsValueProto(uid_dimensions2, &dimensionsValue)); + EXPECT_EQ(dimensionsValue.field(), event.GetTagId()); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value_size(), 1); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).field(), 4); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple() + .dimensions_value_size(), 1); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple() + .dimensions_value(0).field(), 1); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple() + .dimensions_value(0).value_int(), 2222); + + // Query atom field 4: attribution node tag field only. + EXPECT_TRUE(event.GetAtomDimensionsValueProto(tag_dimensions2, &dimensionsValue)); + EXPECT_EQ(dimensionsValue.field(), event.GetTagId()); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value_size(), 1); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).field(), 4); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple() + .dimensions_value_size(), 1); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple() + .dimensions_value(0).field(), 2); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple() + .dimensions_value(0).value_str(), "locationService2"); + + // Query atom field 4: attribution node uid + tag fields. + EXPECT_TRUE(event.GetAtomDimensionsValueProto(attribution_dimensions2, &dimensionsValue)); + EXPECT_EQ(dimensionsValue.field(), event.GetTagId()); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value_size(), 1); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).field(), 4); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple() + .dimensions_value_size(), 2); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple() + .dimensions_value(0).field(), 1); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple() + .dimensions_value(0).value_int(), 2222); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple() + .dimensions_value(1).field(), 2); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple() + .dimensions_value(1).value_str(), "locationService2"); + + // Query multiple fields: + // 1/ Field 3: attribution uid + tag. + // 2/ Field 4: attribution uid only. + // 3/ Field not exist. + // 4/ Primitive fields #5 + // 5/ Primitive fields #1 + EXPECT_TRUE(event.GetAtomDimensionsValueProto(mixed_dimensions, &dimensionsValue)); + EXPECT_EQ(dimensionsValue.field(), event.GetTagId()); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value_size(), 4); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).field(), 3); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple() + .dimensions_value_size(), 2); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple() + .dimensions_value(0).field(), 1); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple() + .dimensions_value(0).value_int(), 1111); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple() + .dimensions_value(1).field(), 2); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple() + .dimensions_value(1).value_str(), "locationService"); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(1).field(), 4); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(1).value_tuple() + .dimensions_value_size(), 1); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(1).value_tuple() + .dimensions_value(0).field(), 1); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(1).value_tuple() + .dimensions_value(0).value_int(), 2222); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(2).field(), 5); + // The bool value is stored in value_int field as logD does not support bool. + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(2).value_int(), true); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(3).field(), 1); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(3).value_int(), 11); +} + +TEST(LogEventTest, testAllPrimitiveFields) { + const int32_t TAG_ID = 123; + LogEvent event(TAG_ID, 0); + + // 1nd field: int32. + EXPECT_TRUE(event.write(int32_t(11))); + // 2rd field: float. + EXPECT_TRUE(event.write(3.45f)); + // 3th field: string. + EXPECT_TRUE(event.write("test")); + // 4th field: bool. + EXPECT_TRUE(event.write(true)); + // 5th field: bool. + EXPECT_TRUE(event.write(false)); + // 6th field: long. + EXPECT_TRUE(event.write(uint64_t(1234))); + + event.init(); + + DimensionsValue dimensionsValue; + EXPECT_TRUE(event.GetSimpleAtomDimensionsValueProto(1, &dimensionsValue)); + EXPECT_EQ(dimensionsValue.field(), event.GetTagId()); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value_size(), 1); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).field(), 1); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_int(), 11); + + EXPECT_TRUE(event.GetSimpleAtomDimensionsValueProto(2, &dimensionsValue)); + EXPECT_EQ(dimensionsValue.field(), event.GetTagId()); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value_size(), 1); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).field(), 2); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_float(), 3.45f); + + EXPECT_TRUE(event.GetSimpleAtomDimensionsValueProto(3, &dimensionsValue)); + EXPECT_EQ(dimensionsValue.field(), event.GetTagId()); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value_size(), 1); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).field(), 3); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_str(), "test"); + + EXPECT_TRUE(event.GetSimpleAtomDimensionsValueProto(4, &dimensionsValue)); + EXPECT_EQ(dimensionsValue.field(), event.GetTagId()); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value_size(), 1); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).field(), 4); + // The bool value is stored in value_int field as logD does not support bool. + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_int(), true); + + EXPECT_TRUE(event.GetSimpleAtomDimensionsValueProto(5, &dimensionsValue)); + EXPECT_EQ(dimensionsValue.field(), event.GetTagId()); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value_size(), 1); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).field(), 5); + // The bool value is stored in value_int field as logD does not support bool. + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_int(), false); + + EXPECT_TRUE(event.GetSimpleAtomDimensionsValueProto(6, &dimensionsValue)); + EXPECT_EQ(dimensionsValue.field(), event.GetTagId()); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value_size(), 1); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).field(), 6); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_long(), long(1234)); + + // Field not exist. + EXPECT_FALSE(event.GetSimpleAtomDimensionsValueProto(7, &dimensionsValue)); +} + +TEST(LogEventTest, testWriteAtomProtoToStream) { + AttributionNode attribution_node1; + attribution_node1.set_uid(1111); + attribution_node1.set_tag("locationService"); + + AttributionNode attribution_node2; + attribution_node2.set_uid(2222); + attribution_node2.set_tag("locationService2"); + + AttributionNode attribution_node3; + attribution_node3.set_uid(3333); + attribution_node3.set_tag("locationService3"); + std::vector<AttributionNode> attribution_nodes = + {attribution_node1, attribution_node2, attribution_node3}; + + LogEvent event(1, 0); + EXPECT_TRUE(event.write("222")); + EXPECT_TRUE(event.write(attribution_nodes)); + EXPECT_TRUE(event.write(345)); + EXPECT_TRUE(event.write(attribution_node3)); + EXPECT_TRUE(event.write("hello")); + event.init(); + + util::ProtoOutputStream protoOutput; + // For now only see whether it will crash. + // TODO(yanglu): test parsing from stream. + event.ToProto(protoOutput); +} + +} // namespace statsd +} // namespace os +} // namespace android +#else +GTEST_LOG_(INFO) << "This test does nothing.\n"; +#endif
\ No newline at end of file diff --git a/cmds/statsd/tests/MetricsManager_test.cpp b/cmds/statsd/tests/MetricsManager_test.cpp index 3c8ccabed3ae..96ee9c584e18 100644 --- a/cmds/statsd/tests/MetricsManager_test.cpp +++ b/cmds/statsd/tests/MetricsManager_test.cpp @@ -21,6 +21,7 @@ #include "src/metrics/MetricProducer.h" #include "src/metrics/ValueMetricProducer.h" #include "src/metrics/metrics_manager_util.h" +#include "statsd_test_util.h" #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h" @@ -40,52 +41,54 @@ using android::os::statsd::Predicate; // TODO: ADD MORE TEST CASES. -const ConfigKey kConfigKey(0, "test"); +const ConfigKey kConfigKey(0, 12345); const long timeBaseSec = 1000; StatsdConfig buildGoodConfig() { StatsdConfig config; - config.set_name("12345"); + config.set_id(12345); AtomMatcher* eventMatcher = config.add_atom_matcher(); - eventMatcher->set_name("SCREEN_IS_ON"); + eventMatcher->set_id(StringToId("SCREEN_IS_ON")); SimpleAtomMatcher* simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher(); - simpleAtomMatcher->set_tag(2 /*SCREEN_STATE_CHANGE*/); - simpleAtomMatcher->add_key_value_matcher()->mutable_key_matcher()->set_key( + simpleAtomMatcher->set_atom_id(2 /*SCREEN_STATE_CHANGE*/); + simpleAtomMatcher->add_field_value_matcher()->set_field( 1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE*/); - simpleAtomMatcher->mutable_key_value_matcher(0)->set_eq_int( + simpleAtomMatcher->mutable_field_value_matcher(0)->set_eq_int( 2 /*SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_ON*/); eventMatcher = config.add_atom_matcher(); - eventMatcher->set_name("SCREEN_IS_OFF"); + eventMatcher->set_id(StringToId("SCREEN_IS_OFF")); simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher(); - simpleAtomMatcher->set_tag(2 /*SCREEN_STATE_CHANGE*/); - simpleAtomMatcher->add_key_value_matcher()->mutable_key_matcher()->set_key( + simpleAtomMatcher->set_atom_id(2 /*SCREEN_STATE_CHANGE*/); + simpleAtomMatcher->add_field_value_matcher()->set_field( 1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE*/); - simpleAtomMatcher->mutable_key_value_matcher(0)->set_eq_int( + simpleAtomMatcher->mutable_field_value_matcher(0)->set_eq_int( 1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_OFF*/); eventMatcher = config.add_atom_matcher(); - eventMatcher->set_name("SCREEN_ON_OR_OFF"); + eventMatcher->set_id(StringToId("SCREEN_ON_OR_OFF")); AtomMatcher_Combination* combination = eventMatcher->mutable_combination(); combination->set_operation(LogicalOperation::OR); - combination->add_matcher("SCREEN_IS_ON"); - combination->add_matcher("SCREEN_IS_OFF"); + combination->add_matcher(StringToId("SCREEN_IS_ON")); + combination->add_matcher(StringToId("SCREEN_IS_OFF")); CountMetric* metric = config.add_count_metric(); - metric->set_name("3"); - metric->set_what("SCREEN_IS_ON"); + metric->set_id(3); + metric->set_what(StringToId("SCREEN_IS_ON")); metric->mutable_bucket()->set_bucket_size_millis(30 * 1000L); - KeyMatcher* keyMatcher = metric->add_dimension(); - keyMatcher->set_key(1); + metric->mutable_dimensions()->set_field(2 /*SCREEN_STATE_CHANGE*/); + metric->mutable_dimensions()->add_child()->set_field(1); + + config.add_no_report_metric(3); auto alert = config.add_alert(); - alert->set_name("3"); - alert->set_metric_name("3"); + alert->set_id(3); + alert->set_metric_id(3); alert->set_number_of_buckets(10); alert->set_refractory_period_secs(100); alert->set_trigger_if_sum_gt(100); @@ -94,47 +97,47 @@ StatsdConfig buildGoodConfig() { StatsdConfig buildCircleMatchers() { StatsdConfig config; - config.set_name("12345"); + config.set_id(12345); AtomMatcher* eventMatcher = config.add_atom_matcher(); - eventMatcher->set_name("SCREEN_IS_ON"); + eventMatcher->set_id(StringToId("SCREEN_IS_ON")); SimpleAtomMatcher* simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher(); - simpleAtomMatcher->set_tag(2 /*SCREEN_STATE_CHANGE*/); - simpleAtomMatcher->add_key_value_matcher()->mutable_key_matcher()->set_key( + simpleAtomMatcher->set_atom_id(2 /*SCREEN_STATE_CHANGE*/); + simpleAtomMatcher->add_field_value_matcher()->set_field( 1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE*/); - simpleAtomMatcher->mutable_key_value_matcher(0)->set_eq_int( + simpleAtomMatcher->mutable_field_value_matcher(0)->set_eq_int( 2 /*SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_ON*/); eventMatcher = config.add_atom_matcher(); - eventMatcher->set_name("SCREEN_ON_OR_OFF"); + eventMatcher->set_id(StringToId("SCREEN_ON_OR_OFF")); AtomMatcher_Combination* combination = eventMatcher->mutable_combination(); combination->set_operation(LogicalOperation::OR); - combination->add_matcher("SCREEN_IS_ON"); + combination->add_matcher(StringToId("SCREEN_IS_ON")); // Circle dependency - combination->add_matcher("SCREEN_ON_OR_OFF"); + combination->add_matcher(StringToId("SCREEN_ON_OR_OFF")); return config; } StatsdConfig buildAlertWithUnknownMetric() { StatsdConfig config; - config.set_name("12345"); + config.set_id(12345); AtomMatcher* eventMatcher = config.add_atom_matcher(); - eventMatcher->set_name("SCREEN_IS_ON"); + eventMatcher->set_id(StringToId("SCREEN_IS_ON")); CountMetric* metric = config.add_count_metric(); - metric->set_name("3"); - metric->set_what("SCREEN_IS_ON"); + metric->set_id(3); + metric->set_what(StringToId("SCREEN_IS_ON")); metric->mutable_bucket()->set_bucket_size_millis(30 * 1000L); - KeyMatcher* keyMatcher = metric->add_dimension(); - keyMatcher->set_key(1); + metric->mutable_dimensions()->set_field(2 /*SCREEN_STATE_CHANGE*/); + metric->mutable_dimensions()->add_child()->set_field(1); auto alert = config.add_alert(); - alert->set_name("3"); - alert->set_metric_name("2"); + alert->set_id(3); + alert->set_metric_id(2); alert->set_number_of_buckets(10); alert->set_refractory_period_secs(100); alert->set_trigger_if_sum_gt(100); @@ -143,82 +146,82 @@ StatsdConfig buildAlertWithUnknownMetric() { StatsdConfig buildMissingMatchers() { StatsdConfig config; - config.set_name("12345"); + config.set_id(12345); AtomMatcher* eventMatcher = config.add_atom_matcher(); - eventMatcher->set_name("SCREEN_IS_ON"); + eventMatcher->set_id(StringToId("SCREEN_IS_ON")); SimpleAtomMatcher* simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher(); - simpleAtomMatcher->set_tag(2 /*SCREEN_STATE_CHANGE*/); - simpleAtomMatcher->add_key_value_matcher()->mutable_key_matcher()->set_key( + simpleAtomMatcher->set_atom_id(2 /*SCREEN_STATE_CHANGE*/); + simpleAtomMatcher->add_field_value_matcher()->set_field( 1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE*/); - simpleAtomMatcher->mutable_key_value_matcher(0)->set_eq_int( + simpleAtomMatcher->mutable_field_value_matcher(0)->set_eq_int( 2 /*SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_ON*/); eventMatcher = config.add_atom_matcher(); - eventMatcher->set_name("SCREEN_ON_OR_OFF"); + eventMatcher->set_id(StringToId("SCREEN_ON_OR_OFF")); AtomMatcher_Combination* combination = eventMatcher->mutable_combination(); combination->set_operation(LogicalOperation::OR); - combination->add_matcher("SCREEN_IS_ON"); + combination->add_matcher(StringToId("SCREEN_IS_ON")); // undefined matcher - combination->add_matcher("ABC"); + combination->add_matcher(StringToId("ABC")); return config; } StatsdConfig buildMissingPredicate() { StatsdConfig config; - config.set_name("12345"); + config.set_id(12345); CountMetric* metric = config.add_count_metric(); - metric->set_name("3"); - metric->set_what("SCREEN_EVENT"); + metric->set_id(3); + metric->set_what(StringToId("SCREEN_EVENT")); metric->mutable_bucket()->set_bucket_size_millis(30 * 1000L); - metric->set_condition("SOME_CONDITION"); + metric->set_condition(StringToId("SOME_CONDITION")); AtomMatcher* eventMatcher = config.add_atom_matcher(); - eventMatcher->set_name("SCREEN_EVENT"); + eventMatcher->set_id(StringToId("SCREEN_EVENT")); SimpleAtomMatcher* simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher(); - simpleAtomMatcher->set_tag(2); + simpleAtomMatcher->set_atom_id(2); return config; } StatsdConfig buildDimensionMetricsWithMultiTags() { StatsdConfig config; - config.set_name("12345"); + config.set_id(12345); AtomMatcher* eventMatcher = config.add_atom_matcher(); - eventMatcher->set_name("BATTERY_VERY_LOW"); + eventMatcher->set_id(StringToId("BATTERY_VERY_LOW")); SimpleAtomMatcher* simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher(); - simpleAtomMatcher->set_tag(2); + simpleAtomMatcher->set_atom_id(2); eventMatcher = config.add_atom_matcher(); - eventMatcher->set_name("BATTERY_VERY_VERY_LOW"); + eventMatcher->set_id(StringToId("BATTERY_VERY_VERY_LOW")); simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher(); - simpleAtomMatcher->set_tag(3); + simpleAtomMatcher->set_atom_id(3); eventMatcher = config.add_atom_matcher(); - eventMatcher->set_name("BATTERY_LOW"); + eventMatcher->set_id(StringToId("BATTERY_LOW")); AtomMatcher_Combination* combination = eventMatcher->mutable_combination(); combination->set_operation(LogicalOperation::OR); - combination->add_matcher("BATTERY_VERY_LOW"); - combination->add_matcher("BATTERY_VERY_VERY_LOW"); + combination->add_matcher(StringToId("BATTERY_VERY_LOW")); + combination->add_matcher(StringToId("BATTERY_VERY_VERY_LOW")); // Count process state changes, slice by uid, while SCREEN_IS_OFF CountMetric* metric = config.add_count_metric(); - metric->set_name("3"); - metric->set_what("BATTERY_LOW"); + metric->set_id(3); + metric->set_what(StringToId("BATTERY_LOW")); metric->mutable_bucket()->set_bucket_size_millis(30 * 1000L); - KeyMatcher* keyMatcher = metric->add_dimension(); - keyMatcher->set_key(1); + // This case is interesting. We want to dimension across two atoms. + metric->mutable_dimensions()->add_child()->set_field(1); auto alert = config.add_alert(); - alert->set_name("3"); - alert->set_metric_name("3"); + alert->set_id(103); + alert->set_metric_id(3); alert->set_number_of_buckets(10); alert->set_refractory_period_secs(100); alert->set_trigger_if_sum_gt(100); @@ -227,46 +230,47 @@ StatsdConfig buildDimensionMetricsWithMultiTags() { StatsdConfig buildCirclePredicates() { StatsdConfig config; - config.set_name("12345"); + config.set_id(12345); AtomMatcher* eventMatcher = config.add_atom_matcher(); - eventMatcher->set_name("SCREEN_IS_ON"); + eventMatcher->set_id(StringToId("SCREEN_IS_ON")); SimpleAtomMatcher* simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher(); - simpleAtomMatcher->set_tag(2 /*SCREEN_STATE_CHANGE*/); - simpleAtomMatcher->add_key_value_matcher()->mutable_key_matcher()->set_key( + simpleAtomMatcher->set_atom_id(2 /*SCREEN_STATE_CHANGE*/); + simpleAtomMatcher->add_field_value_matcher()->set_field( 1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE*/); - simpleAtomMatcher->mutable_key_value_matcher(0)->set_eq_int( + simpleAtomMatcher->mutable_field_value_matcher(0)->set_eq_int( 2 /*SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_ON*/); eventMatcher = config.add_atom_matcher(); - eventMatcher->set_name("SCREEN_IS_OFF"); + eventMatcher->set_id(StringToId("SCREEN_IS_OFF")); simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher(); - simpleAtomMatcher->set_tag(2 /*SCREEN_STATE_CHANGE*/); - simpleAtomMatcher->add_key_value_matcher()->mutable_key_matcher()->set_key( + simpleAtomMatcher->set_atom_id(2 /*SCREEN_STATE_CHANGE*/); + simpleAtomMatcher->add_field_value_matcher()->set_field( 1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE*/); - simpleAtomMatcher->mutable_key_value_matcher(0)->set_eq_int( + simpleAtomMatcher->mutable_field_value_matcher(0)->set_eq_int( 1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_OFF*/); auto condition = config.add_predicate(); - condition->set_name("SCREEN_IS_ON"); + condition->set_id(StringToId("SCREEN_IS_ON")); SimplePredicate* simplePredicate = condition->mutable_simple_predicate(); - simplePredicate->set_start("SCREEN_IS_ON"); - simplePredicate->set_stop("SCREEN_IS_OFF"); + simplePredicate->set_start(StringToId("SCREEN_IS_ON")); + simplePredicate->set_stop(StringToId("SCREEN_IS_OFF")); condition = config.add_predicate(); - condition->set_name("SCREEN_IS_EITHER_ON_OFF"); + condition->set_id(StringToId("SCREEN_IS_EITHER_ON_OFF")); Predicate_Combination* combination = condition->mutable_combination(); combination->set_operation(LogicalOperation::OR); - combination->add_predicate("SCREEN_IS_ON"); - combination->add_predicate("SCREEN_IS_EITHER_ON_OFF"); + combination->add_predicate(StringToId("SCREEN_IS_ON")); + combination->add_predicate(StringToId("SCREEN_IS_EITHER_ON_OFF")); return config; } TEST(MetricsManagerTest, TestGoodConfig) { + UidMap uidMap; StatsdConfig config = buildGoodConfig(); set<int> allTagIds; vector<sp<LogMatchingTracker>> allAtomMatchers; @@ -276,15 +280,19 @@ TEST(MetricsManagerTest, TestGoodConfig) { unordered_map<int, std::vector<int>> conditionToMetricMap; unordered_map<int, std::vector<int>> trackerToMetricMap; unordered_map<int, std::vector<int>> trackerToConditionMap; + std::set<int64_t> noReportMetricIds; - EXPECT_TRUE(initStatsdConfig(kConfigKey, config, timeBaseSec, allTagIds, allAtomMatchers, + EXPECT_TRUE(initStatsdConfig(kConfigKey, config, uidMap, timeBaseSec, allTagIds, allAtomMatchers, allConditionTrackers, allMetricProducers, allAnomalyTrackers, - conditionToMetricMap, trackerToMetricMap, trackerToConditionMap)); + conditionToMetricMap, trackerToMetricMap, trackerToConditionMap, + noReportMetricIds)); EXPECT_EQ(1u, allMetricProducers.size()); EXPECT_EQ(1u, allAnomalyTrackers.size()); + EXPECT_EQ(1u, noReportMetricIds.size()); } TEST(MetricsManagerTest, TestDimensionMetricsWithMultiTags) { + UidMap uidMap; StatsdConfig config = buildDimensionMetricsWithMultiTags(); set<int> allTagIds; vector<sp<LogMatchingTracker>> allAtomMatchers; @@ -294,13 +302,16 @@ TEST(MetricsManagerTest, TestDimensionMetricsWithMultiTags) { unordered_map<int, std::vector<int>> conditionToMetricMap; unordered_map<int, std::vector<int>> trackerToMetricMap; unordered_map<int, std::vector<int>> trackerToConditionMap; + std::set<int64_t> noReportMetricIds; - EXPECT_FALSE(initStatsdConfig(kConfigKey, config, timeBaseSec, allTagIds, allAtomMatchers, + EXPECT_FALSE(initStatsdConfig(kConfigKey, config, uidMap, timeBaseSec, allTagIds, allAtomMatchers, allConditionTrackers, allMetricProducers, allAnomalyTrackers, - conditionToMetricMap, trackerToMetricMap, trackerToConditionMap)); + conditionToMetricMap, trackerToMetricMap, trackerToConditionMap, + noReportMetricIds)); } TEST(MetricsManagerTest, TestCircleLogMatcherDependency) { + UidMap uidMap; StatsdConfig config = buildCircleMatchers(); set<int> allTagIds; vector<sp<LogMatchingTracker>> allAtomMatchers; @@ -310,13 +321,16 @@ TEST(MetricsManagerTest, TestCircleLogMatcherDependency) { unordered_map<int, std::vector<int>> conditionToMetricMap; unordered_map<int, std::vector<int>> trackerToMetricMap; unordered_map<int, std::vector<int>> trackerToConditionMap; + std::set<int64_t> noReportMetricIds; - EXPECT_FALSE(initStatsdConfig(kConfigKey, config, timeBaseSec, allTagIds, allAtomMatchers, + EXPECT_FALSE(initStatsdConfig(kConfigKey, config, uidMap, timeBaseSec, allTagIds, allAtomMatchers, allConditionTrackers, allMetricProducers, allAnomalyTrackers, - conditionToMetricMap, trackerToMetricMap, trackerToConditionMap)); + conditionToMetricMap, trackerToMetricMap, trackerToConditionMap, + noReportMetricIds)); } TEST(MetricsManagerTest, TestMissingMatchers) { + UidMap uidMap; StatsdConfig config = buildMissingMatchers(); set<int> allTagIds; vector<sp<LogMatchingTracker>> allAtomMatchers; @@ -326,12 +340,15 @@ TEST(MetricsManagerTest, TestMissingMatchers) { unordered_map<int, std::vector<int>> conditionToMetricMap; unordered_map<int, std::vector<int>> trackerToMetricMap; unordered_map<int, std::vector<int>> trackerToConditionMap; - EXPECT_FALSE(initStatsdConfig(kConfigKey, config, timeBaseSec, allTagIds, allAtomMatchers, + std::set<int64_t> noReportMetricIds; + EXPECT_FALSE(initStatsdConfig(kConfigKey, config, uidMap, timeBaseSec, allTagIds, allAtomMatchers, allConditionTrackers, allMetricProducers, allAnomalyTrackers, - conditionToMetricMap, trackerToMetricMap, trackerToConditionMap)); + conditionToMetricMap, trackerToMetricMap, trackerToConditionMap, + noReportMetricIds)); } TEST(MetricsManagerTest, TestMissingPredicate) { + UidMap uidMap; StatsdConfig config = buildMissingPredicate(); set<int> allTagIds; vector<sp<LogMatchingTracker>> allAtomMatchers; @@ -341,12 +358,15 @@ TEST(MetricsManagerTest, TestMissingPredicate) { unordered_map<int, std::vector<int>> conditionToMetricMap; unordered_map<int, std::vector<int>> trackerToMetricMap; unordered_map<int, std::vector<int>> trackerToConditionMap; - EXPECT_FALSE(initStatsdConfig(kConfigKey, config, timeBaseSec, allTagIds, allAtomMatchers, + std::set<int64_t> noReportMetricIds; + EXPECT_FALSE(initStatsdConfig(kConfigKey, config, uidMap, timeBaseSec, allTagIds, allAtomMatchers, allConditionTrackers, allMetricProducers, allAnomalyTrackers, - conditionToMetricMap, trackerToMetricMap, trackerToConditionMap)); + conditionToMetricMap, trackerToMetricMap, trackerToConditionMap, + noReportMetricIds)); } TEST(MetricsManagerTest, TestCirclePredicateDependency) { + UidMap uidMap; StatsdConfig config = buildCirclePredicates(); set<int> allTagIds; vector<sp<LogMatchingTracker>> allAtomMatchers; @@ -356,13 +376,16 @@ TEST(MetricsManagerTest, TestCirclePredicateDependency) { unordered_map<int, std::vector<int>> conditionToMetricMap; unordered_map<int, std::vector<int>> trackerToMetricMap; unordered_map<int, std::vector<int>> trackerToConditionMap; + std::set<int64_t> noReportMetricIds; - EXPECT_FALSE(initStatsdConfig(kConfigKey, config, timeBaseSec, allTagIds, allAtomMatchers, + EXPECT_FALSE(initStatsdConfig(kConfigKey, config, uidMap, timeBaseSec, allTagIds, allAtomMatchers, allConditionTrackers, allMetricProducers, allAnomalyTrackers, - conditionToMetricMap, trackerToMetricMap, trackerToConditionMap)); + conditionToMetricMap, trackerToMetricMap, trackerToConditionMap, + noReportMetricIds)); } TEST(MetricsManagerTest, testAlertWithUnknownMetric) { + UidMap uidMap; StatsdConfig config = buildAlertWithUnknownMetric(); set<int> allTagIds; vector<sp<LogMatchingTracker>> allAtomMatchers; @@ -372,10 +395,12 @@ TEST(MetricsManagerTest, testAlertWithUnknownMetric) { unordered_map<int, std::vector<int>> conditionToMetricMap; unordered_map<int, std::vector<int>> trackerToMetricMap; unordered_map<int, std::vector<int>> trackerToConditionMap; + std::set<int64_t> noReportMetricIds; - EXPECT_FALSE(initStatsdConfig(kConfigKey, config, timeBaseSec, allTagIds, allAtomMatchers, + EXPECT_FALSE(initStatsdConfig(kConfigKey, config, uidMap, timeBaseSec, allTagIds, allAtomMatchers, allConditionTrackers, allMetricProducers, allAnomalyTrackers, - conditionToMetricMap, trackerToMetricMap, trackerToConditionMap)); + conditionToMetricMap, trackerToMetricMap, trackerToConditionMap, + noReportMetricIds)); } #else diff --git a/cmds/statsd/tests/StatsLogProcessor_test.cpp b/cmds/statsd/tests/StatsLogProcessor_test.cpp index 9b96bb75743a..5d053e25003d 100644 --- a/cmds/statsd/tests/StatsLogProcessor_test.cpp +++ b/cmds/statsd/tests/StatsLogProcessor_test.cpp @@ -41,7 +41,7 @@ using android::util::ProtoOutputStream; */ class MockMetricsManager : public MetricsManager { public: - MockMetricsManager() : MetricsManager(ConfigKey(1, "key"), StatsdConfig(), 1000, new UidMap()) { + MockMetricsManager() : MetricsManager(ConfigKey(1, 12345), StatsdConfig(), 1000, new UidMap()) { } MOCK_METHOD0(byteSize, size_t()); @@ -52,11 +52,11 @@ TEST(StatsLogProcessorTest, TestRateLimitByteSize) { sp<UidMap> m = new UidMap(); sp<AnomalyMonitor> anomalyMonitor; // Construct the processor with a dummy sendBroadcast function that does nothing. - StatsLogProcessor p(m, anomalyMonitor, [](const ConfigKey& key) {}); + StatsLogProcessor p(m, anomalyMonitor, 0, [](const ConfigKey& key) {}); MockMetricsManager mockMetricsManager; - ConfigKey key(100, "key"); + ConfigKey key(100, 12345); // Expect only the first flush to trigger a check for byte size since the last two are // rate-limited. EXPECT_CALL(mockMetricsManager, byteSize()).Times(1); @@ -69,12 +69,12 @@ TEST(StatsLogProcessorTest, TestRateLimitBroadcast) { sp<UidMap> m = new UidMap(); sp<AnomalyMonitor> anomalyMonitor; int broadcastCount = 0; - StatsLogProcessor p(m, anomalyMonitor, + StatsLogProcessor p(m, anomalyMonitor, 0, [&broadcastCount](const ConfigKey& key) { broadcastCount++; }); MockMetricsManager mockMetricsManager; - ConfigKey key(100, "key"); + ConfigKey key(100, 12345); EXPECT_CALL(mockMetricsManager, byteSize()) .Times(2) .WillRepeatedly(Return(int(StatsdStats::kMaxMetricsBytesPerConfig * .95))); @@ -93,12 +93,12 @@ TEST(StatsLogProcessorTest, TestDropWhenByteSizeTooLarge) { sp<UidMap> m = new UidMap(); sp<AnomalyMonitor> anomalyMonitor; int broadcastCount = 0; - StatsLogProcessor p(m, anomalyMonitor, + StatsLogProcessor p(m, anomalyMonitor, 0, [&broadcastCount](const ConfigKey& key) { broadcastCount++; }); MockMetricsManager mockMetricsManager; - ConfigKey key(100, "key"); + ConfigKey key(100, 12345); EXPECT_CALL(mockMetricsManager, byteSize()) .Times(1) .WillRepeatedly(Return(int(StatsdStats::kMaxMetricsBytesPerConfig * 1.2))); diff --git a/cmds/statsd/tests/UidMap_test.cpp b/cmds/statsd/tests/UidMap_test.cpp index 3fa96d392927..945af2746eae 100644 --- a/cmds/statsd/tests/UidMap_test.cpp +++ b/cmds/statsd/tests/UidMap_test.cpp @@ -18,6 +18,7 @@ #include "guardrail/StatsdStats.h" #include "logd/LogEvent.h" #include "statslog.h" +#include "statsd_test_util.h" #include <gtest/gtest.h> @@ -37,7 +38,7 @@ TEST(UidMapTest, TestIsolatedUID) { sp<UidMap> m = new UidMap(); sp<AnomalyMonitor> anomalyMonitor; // Construct the processor with a dummy sendBroadcast function that does nothing. - StatsLogProcessor p(m, anomalyMonitor, [](const ConfigKey& key) {}); + StatsLogProcessor p(m, anomalyMonitor, 0, [](const ConfigKey& key) {}); LogEvent addEvent(android::util::ISOLATED_UID_CHANGED, 1); addEvent.write(100); // parent UID addEvent.write(101); // isolated UID @@ -156,8 +157,8 @@ TEST(UidMapTest, TestUpdateApp) { TEST(UidMapTest, TestClearingOutput) { UidMap m; - ConfigKey config1(1, "config1"); - ConfigKey config2(1, "config2"); + ConfigKey config1(1, StringToId("config1")); + ConfigKey config2(1, StringToId("config2")); m.OnConfigUpdated(config1); @@ -211,7 +212,7 @@ TEST(UidMapTest, TestClearingOutput) { TEST(UidMapTest, TestMemoryComputed) { UidMap m; - ConfigKey config1(1, "config1"); + ConfigKey config1(1, StringToId("config1")); m.OnConfigUpdated(config1); size_t startBytes = m.mBytesUsed; @@ -241,7 +242,7 @@ TEST(UidMapTest, TestMemoryGuardrail) { UidMap m; string buf; - ConfigKey config1(1, "config1"); + ConfigKey config1(1, StringToId("config1")); m.OnConfigUpdated(config1); size_t startBytes = m.mBytesUsed; @@ -273,4 +274,4 @@ GTEST_LOG_(INFO) << "This test does nothing.\n"; } // namespace statsd } // namespace os -} // namespace android +} // namespace android
\ No newline at end of file diff --git a/cmds/statsd/tests/anomaly/AnomalyTracker_test.cpp b/cmds/statsd/tests/anomaly/AnomalyTracker_test.cpp index f62171d57a5a..751180db0890 100644 --- a/cmds/statsd/tests/anomaly/AnomalyTracker_test.cpp +++ b/cmds/statsd/tests/anomaly/AnomalyTracker_test.cpp @@ -31,17 +31,13 @@ namespace android { namespace os { namespace statsd { -const ConfigKey kConfigKey(0, "test"); +const ConfigKey kConfigKey(0, 12345); HashableDimensionKey getMockDimensionKey(int key, string value) { - KeyValuePair pair; - pair.set_key(key); - pair.set_value_str(value); - - vector<KeyValuePair> pairs; - pairs.push_back(pair); - - return HashableDimensionKey(pairs); + DimensionsValue dimensionsValue; + dimensionsValue.set_field(key); + dimensionsValue.set_value_str(value); + return HashableDimensionKey(dimensionsValue); } void AddValueToBucket(const std::vector<std::pair<HashableDimensionKey, long>>& key_value_pair_list, @@ -89,7 +85,7 @@ TEST(AnomalyTrackerTest, TestConsecutiveBuckets) { EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, -1LL); EXPECT_FALSE(anomalyTracker.detectAnomaly(0, *bucket0)); anomalyTracker.detectAndDeclareAnomaly(eventTimestamp0, 0, *bucket0); - EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, -1L); + EXPECT_EQ(anomalyTracker.mLastAnomalyTimestampNs, -1L); // Adds past bucket #0 anomalyTracker.addPastBucket(bucket0, 0); @@ -100,7 +96,7 @@ TEST(AnomalyTrackerTest, TestConsecutiveBuckets) { EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 0LL); EXPECT_FALSE(anomalyTracker.detectAnomaly(1, *bucket1)); anomalyTracker.detectAndDeclareAnomaly(eventTimestamp1, 1, *bucket1); - EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, -1L); + EXPECT_EQ(anomalyTracker.mLastAnomalyTimestampNs, -1L); // Adds past bucket #0 again. The sum does not change. anomalyTracker.addPastBucket(bucket0, 0); @@ -111,7 +107,7 @@ TEST(AnomalyTrackerTest, TestConsecutiveBuckets) { EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 0LL); EXPECT_FALSE(anomalyTracker.detectAnomaly(1, *bucket1)); anomalyTracker.detectAndDeclareAnomaly(eventTimestamp1 + 1, 1, *bucket1); - EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, -1L); + EXPECT_EQ(anomalyTracker.mLastAnomalyTimestampNs, -1L); // Adds past bucket #1. anomalyTracker.addPastBucket(bucket1, 1); @@ -122,7 +118,7 @@ TEST(AnomalyTrackerTest, TestConsecutiveBuckets) { EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyC), 1LL); EXPECT_TRUE(anomalyTracker.detectAnomaly(2, *bucket2)); anomalyTracker.detectAndDeclareAnomaly(eventTimestamp2, 2, *bucket2); - EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp2); + EXPECT_EQ(anomalyTracker.mLastAnomalyTimestampNs, eventTimestamp2); // Adds past bucket #1 again. Nothing changes. anomalyTracker.addPastBucket(bucket1, 1); @@ -133,7 +129,7 @@ TEST(AnomalyTrackerTest, TestConsecutiveBuckets) { EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyC), 1LL); EXPECT_TRUE(anomalyTracker.detectAnomaly(2, *bucket2)); anomalyTracker.detectAndDeclareAnomaly(eventTimestamp2 + 1, 2, *bucket2); - EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp2); + EXPECT_EQ(anomalyTracker.mLastAnomalyTimestampNs, eventTimestamp2); // Adds past bucket #2. anomalyTracker.addPastBucket(bucket2, 2); @@ -144,7 +140,7 @@ TEST(AnomalyTrackerTest, TestConsecutiveBuckets) { EXPECT_TRUE(anomalyTracker.detectAnomaly(3, *bucket3)); anomalyTracker.detectAndDeclareAnomaly(eventTimestamp3, 3, *bucket3); // Within refractory period. - EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp2); + EXPECT_EQ(anomalyTracker.mLastAnomalyTimestampNs, eventTimestamp2); // Adds bucket #3. anomalyTracker.addPastBucket(bucket3, 3L); @@ -154,7 +150,7 @@ TEST(AnomalyTrackerTest, TestConsecutiveBuckets) { EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 1LL); EXPECT_FALSE(anomalyTracker.detectAnomaly(4, *bucket4)); anomalyTracker.detectAndDeclareAnomaly(eventTimestamp4, 4, *bucket4); - EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp2); + EXPECT_EQ(anomalyTracker.mLastAnomalyTimestampNs, eventTimestamp2); // Adds bucket #4. anomalyTracker.addPastBucket(bucket4, 4); @@ -164,7 +160,7 @@ TEST(AnomalyTrackerTest, TestConsecutiveBuckets) { EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 1LL); EXPECT_TRUE(anomalyTracker.detectAnomaly(5, *bucket5)); anomalyTracker.detectAndDeclareAnomaly(eventTimestamp5, 5, *bucket5); - EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp5); + EXPECT_EQ(anomalyTracker.mLastAnomalyTimestampNs, eventTimestamp5); // Adds bucket #5. anomalyTracker.addPastBucket(bucket5, 5); @@ -175,7 +171,7 @@ TEST(AnomalyTrackerTest, TestConsecutiveBuckets) { EXPECT_TRUE(anomalyTracker.detectAnomaly(6, *bucket6)); // Within refractory period. anomalyTracker.detectAndDeclareAnomaly(eventTimestamp6, 6, *bucket6); - EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp5); + EXPECT_EQ(anomalyTracker.mLastAnomalyTimestampNs, eventTimestamp5); } TEST(AnomalyTrackerTest, TestSparseBuckets) { @@ -210,7 +206,7 @@ TEST(AnomalyTrackerTest, TestSparseBuckets) { EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL); EXPECT_FALSE(anomalyTracker.detectAnomaly(9, *bucket9)); anomalyTracker.detectAndDeclareAnomaly(eventTimestamp1, 9, *bucket9); - EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, -1); + EXPECT_EQ(anomalyTracker.mLastAnomalyTimestampNs, -1); // Add past bucket #9 anomalyTracker.addPastBucket(bucket9, 9); @@ -224,7 +220,7 @@ TEST(AnomalyTrackerTest, TestSparseBuckets) { EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 15L); anomalyTracker.detectAndDeclareAnomaly(eventTimestamp2, 16, *bucket16); EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL); - EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp2); + EXPECT_EQ(anomalyTracker.mLastAnomalyTimestampNs, eventTimestamp2); EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 15L); // Add past bucket #16 @@ -237,7 +233,7 @@ TEST(AnomalyTrackerTest, TestSparseBuckets) { EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 4LL); // Within refractory period. anomalyTracker.detectAndDeclareAnomaly(eventTimestamp3, 18, *bucket18); - EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp2); + EXPECT_EQ(anomalyTracker.mLastAnomalyTimestampNs, eventTimestamp2); EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 1UL); EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 4LL); @@ -253,7 +249,7 @@ TEST(AnomalyTrackerTest, TestSparseBuckets) { EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 1LL); EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyC), 1LL); anomalyTracker.detectAndDeclareAnomaly(eventTimestamp4, 20, *bucket20); - EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp4); + EXPECT_EQ(anomalyTracker.mLastAnomalyTimestampNs, eventTimestamp4); // Add bucket #18 again. Nothing changes. anomalyTracker.addPastBucket(bucket18, 18); @@ -267,7 +263,7 @@ TEST(AnomalyTrackerTest, TestSparseBuckets) { EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyC), 1LL); anomalyTracker.detectAndDeclareAnomaly(eventTimestamp4 + 1, 20, *bucket20); // Within refractory period. - EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp4); + EXPECT_EQ(anomalyTracker.mLastAnomalyTimestampNs, eventTimestamp4); // Add past bucket #20 anomalyTracker.addPastBucket(bucket20, 20); @@ -279,7 +275,7 @@ TEST(AnomalyTrackerTest, TestSparseBuckets) { EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 24L); EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL); anomalyTracker.detectAndDeclareAnomaly(eventTimestamp5, 25, *bucket25); - EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp4); + EXPECT_EQ(anomalyTracker.mLastAnomalyTimestampNs, eventTimestamp4); // Add past bucket #25 anomalyTracker.addPastBucket(bucket25, 25); @@ -291,7 +287,7 @@ TEST(AnomalyTrackerTest, TestSparseBuckets) { EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL); anomalyTracker.detectAndDeclareAnomaly(eventTimestamp6, 28, *bucket28); EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL); - EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp4); + EXPECT_EQ(anomalyTracker.mLastAnomalyTimestampNs, eventTimestamp4); // Updates current bucket #28. (*bucket28)[keyE] = 5; @@ -300,7 +296,7 @@ TEST(AnomalyTrackerTest, TestSparseBuckets) { EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL); anomalyTracker.detectAndDeclareAnomaly(eventTimestamp6 + 7, 28, *bucket28); EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL); - EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp6 + 7); + EXPECT_EQ(anomalyTracker.mLastAnomalyTimestampNs, eventTimestamp6 + 7); } } // namespace statsd diff --git a/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp b/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp index eb0fafe781c6..819f2bebf327 100644 --- a/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp +++ b/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp @@ -11,12 +11,15 @@ // 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. + #include "src/condition/SimpleConditionTracker.h" +#include "tests/statsd_test_util.h" #include <gmock/gmock.h> #include <gtest/gtest.h> #include <stdio.h> #include <vector> +#include <numeric> using std::map; using std::unordered_map; @@ -28,17 +31,24 @@ namespace android { namespace os { namespace statsd { -const ConfigKey kConfigKey(0, "test"); +const ConfigKey kConfigKey(0, 12345); + +const int ATTRIBUTION_NODE_FIELD_ID = 1; +const int ATTRIBUTION_UID_FIELD_ID = 1; +const int TAG_ID = 1; SimplePredicate getWakeLockHeldCondition(bool countNesting, bool defaultFalse, - bool outputSlicedUid) { + bool outputSlicedUid, Position position) { SimplePredicate simplePredicate; - simplePredicate.set_start("WAKE_LOCK_ACQUIRE"); - simplePredicate.set_stop("WAKE_LOCK_RELEASE"); - simplePredicate.set_stop_all("RELEASE_ALL"); + simplePredicate.set_start(StringToId("WAKE_LOCK_ACQUIRE")); + simplePredicate.set_stop(StringToId("WAKE_LOCK_RELEASE")); + simplePredicate.set_stop_all(StringToId("RELEASE_ALL")); if (outputSlicedUid) { - KeyMatcher* keyMatcher = simplePredicate.add_dimension(); - keyMatcher->set_key(1); + simplePredicate.mutable_dimensions()->set_field(TAG_ID); + simplePredicate.mutable_dimensions()->add_child()->set_field(ATTRIBUTION_NODE_FIELD_ID); + simplePredicate.mutable_dimensions()->mutable_child(0)->set_position(position); + simplePredicate.mutable_dimensions()->mutable_child(0)->add_child()->set_field( + ATTRIBUTION_UID_FIELD_ID); } simplePredicate.set_count_nesting(countNesting); @@ -47,38 +57,70 @@ SimplePredicate getWakeLockHeldCondition(bool countNesting, bool defaultFalse, return simplePredicate; } -void makeWakeLockEvent(LogEvent* event, int uid, const string& wl, int acquire) { - event->write(uid); // uid +void writeAttributionNodesToEvent(LogEvent* event, const std::vector<int> &uids) { + std::vector<AttributionNode> nodes; + for (size_t i = 0; i < uids.size(); ++i) { + AttributionNode node; + node.set_uid(uids[i]); + nodes.push_back(node); + } + event->write(nodes); // attribution chain. +} + +void makeWakeLockEvent( + LogEvent* event, const std::vector<int> &uids, const string& wl, int acquire) { + writeAttributionNodesToEvent(event, uids); event->write(wl); event->write(acquire); event->init(); } -map<string, HashableDimensionKey> getWakeLockQueryKey(int key, int uid, - const string& conditionName) { - // test query - KeyValuePair kv1; - kv1.set_key(key); - kv1.set_value_int(uid); - vector<KeyValuePair> kv_list; - kv_list.push_back(kv1); - map<string, HashableDimensionKey> queryKey; - queryKey[conditionName] = HashableDimensionKey(kv_list); - return queryKey; +std::map<int64_t, std::vector<HashableDimensionKey>> getWakeLockQueryKey( + const Position position, + const std::vector<int> &uids, const string& conditionName) { + std::map<int64_t, std::vector<HashableDimensionKey>> outputKeyMap; + std::vector<int> uid_indexes; + switch(position) { + case Position::FIRST: + uid_indexes.push_back(0); + break; + case Position::LAST: + uid_indexes.push_back(uids.size() - 1); + break; + case Position::ANY: + uid_indexes.resize(uids.size()); + std::iota(uid_indexes.begin(), uid_indexes.end(), 0); + break; + default: + break; + } + + for (const int idx : uid_indexes) { + DimensionsValue dimensionsValue; + dimensionsValue.set_field(TAG_ID); + dimensionsValue.mutable_value_tuple()->add_dimensions_value()->set_field( + ATTRIBUTION_NODE_FIELD_ID); + dimensionsValue.mutable_value_tuple()->mutable_dimensions_value(0) + ->mutable_value_tuple()->add_dimensions_value()->set_field(ATTRIBUTION_NODE_FIELD_ID); + dimensionsValue.mutable_value_tuple()->mutable_dimensions_value(0) + ->mutable_value_tuple()->mutable_dimensions_value(0)->set_value_int(uids[idx]); + outputKeyMap[StringToId(conditionName)].push_back(HashableDimensionKey(dimensionsValue)); + } + return outputKeyMap; } TEST(SimpleConditionTrackerTest, TestNonSlicedCondition) { SimplePredicate simplePredicate; - simplePredicate.set_start("SCREEN_TURNED_ON"); - simplePredicate.set_stop("SCREEN_TURNED_OFF"); + simplePredicate.set_start(StringToId("SCREEN_TURNED_ON")); + simplePredicate.set_stop(StringToId("SCREEN_TURNED_OFF")); simplePredicate.set_count_nesting(false); simplePredicate.set_initial_value(SimplePredicate_InitialValue_UNKNOWN); - unordered_map<string, int> trackerNameIndexMap; - trackerNameIndexMap["SCREEN_TURNED_ON"] = 0; - trackerNameIndexMap["SCREEN_TURNED_OFF"] = 1; + unordered_map<int64_t, int> trackerNameIndexMap; + trackerNameIndexMap[StringToId("SCREEN_TURNED_ON")] = 0; + trackerNameIndexMap[StringToId("SCREEN_TURNED_OFF")] = 1; - SimpleConditionTracker conditionTracker(kConfigKey, "SCREEN_IS_ON", 0 /*tracker index*/, + SimpleConditionTracker conditionTracker(kConfigKey, StringToId("SCREEN_IS_ON"), 0 /*tracker index*/, simplePredicate, trackerNameIndexMap); LogEvent event(1 /*tagId*/, 0 /*timestamp*/); @@ -152,15 +194,15 @@ TEST(SimpleConditionTrackerTest, TestNonSlicedCondition) { TEST(SimpleConditionTrackerTest, TestNonSlicedConditionNestCounting) { SimplePredicate simplePredicate; - simplePredicate.set_start("SCREEN_TURNED_ON"); - simplePredicate.set_stop("SCREEN_TURNED_OFF"); + simplePredicate.set_start(StringToId("SCREEN_TURNED_ON")); + simplePredicate.set_stop(StringToId("SCREEN_TURNED_OFF")); simplePredicate.set_count_nesting(true); - unordered_map<string, int> trackerNameIndexMap; - trackerNameIndexMap["SCREEN_TURNED_ON"] = 0; - trackerNameIndexMap["SCREEN_TURNED_OFF"] = 1; + unordered_map<int64_t, int> trackerNameIndexMap; + trackerNameIndexMap[StringToId("SCREEN_TURNED_ON")] = 0; + trackerNameIndexMap[StringToId("SCREEN_TURNED_OFF")] = 1; - SimpleConditionTracker conditionTracker(kConfigKey, "SCREEN_IS_ON", + SimpleConditionTracker conditionTracker(kConfigKey, StringToId("SCREEN_IS_ON"), 0 /*condition tracker index*/, simplePredicate, trackerNameIndexMap); @@ -221,108 +263,133 @@ TEST(SimpleConditionTrackerTest, TestNonSlicedConditionNestCounting) { } TEST(SimpleConditionTrackerTest, TestSlicedCondition) { - SimplePredicate simplePredicate = getWakeLockHeldCondition( - true /*nesting*/, true /*default to false*/, true /*output slice by uid*/); - string conditionName = "WL_HELD_BY_UID2"; - - unordered_map<string, int> trackerNameIndexMap; - trackerNameIndexMap["WAKE_LOCK_ACQUIRE"] = 0; - trackerNameIndexMap["WAKE_LOCK_RELEASE"] = 1; - trackerNameIndexMap["RELEASE_ALL"] = 2; - - SimpleConditionTracker conditionTracker(kConfigKey, conditionName, - 0 /*condition tracker index*/, simplePredicate, - trackerNameIndexMap); - int uid = 111; - - LogEvent event(1 /*tagId*/, 0 /*timestamp*/); - makeWakeLockEvent(&event, uid, "wl1", 1); - - // one matched start - vector<MatchingState> matcherState; - matcherState.push_back(MatchingState::kMatched); - matcherState.push_back(MatchingState::kNotMatched); - matcherState.push_back(MatchingState::kNotMatched); - vector<sp<ConditionTracker>> allPredicates; - vector<ConditionState> conditionCache(1, ConditionState::kNotEvaluated); - vector<bool> changedCache(1, false); - - conditionTracker.evaluateCondition(event, matcherState, allPredicates, conditionCache, - changedCache); - - EXPECT_EQ(1UL, conditionTracker.mSlicedConditionState.size()); - EXPECT_TRUE(changedCache[0]); - - // Now test query - const auto queryKey = getWakeLockQueryKey(1, uid, conditionName); - conditionCache[0] = ConditionState::kNotEvaluated; - - conditionTracker.isConditionMet(queryKey, allPredicates, conditionCache); - EXPECT_EQ(ConditionState::kTrue, conditionCache[0]); - - // another wake lock acquired by this uid - LogEvent event2(1 /*tagId*/, 0 /*timestamp*/); - makeWakeLockEvent(&event2, uid, "wl2", 1); - matcherState.clear(); - matcherState.push_back(MatchingState::kMatched); - matcherState.push_back(MatchingState::kNotMatched); - conditionCache[0] = ConditionState::kNotEvaluated; - changedCache[0] = false; - conditionTracker.evaluateCondition(event2, matcherState, allPredicates, conditionCache, - changedCache); - EXPECT_FALSE(changedCache[0]); + for (Position position : + { Position::ANY, Position::FIRST, Position::LAST}) { + SimplePredicate simplePredicate = getWakeLockHeldCondition( + true /*nesting*/, true /*default to false*/, true /*output slice by uid*/, + position); + string conditionName = "WL_HELD_BY_UID2"; + + unordered_map<int64_t, int> trackerNameIndexMap; + trackerNameIndexMap[StringToId("WAKE_LOCK_ACQUIRE")] = 0; + trackerNameIndexMap[StringToId("WAKE_LOCK_RELEASE")] = 1; + trackerNameIndexMap[StringToId("RELEASE_ALL")] = 2; + + SimpleConditionTracker conditionTracker(kConfigKey, StringToId(conditionName), + 0 /*condition tracker index*/, simplePredicate, + trackerNameIndexMap); + std::vector<int> uids = {111, 222, 333}; + + LogEvent event(1 /*tagId*/, 0 /*timestamp*/); + makeWakeLockEvent(&event, uids, "wl1", 1); + + // one matched start + vector<MatchingState> matcherState; + matcherState.push_back(MatchingState::kMatched); + matcherState.push_back(MatchingState::kNotMatched); + matcherState.push_back(MatchingState::kNotMatched); + vector<sp<ConditionTracker>> allPredicates; + vector<ConditionState> conditionCache(1, ConditionState::kNotEvaluated); + vector<bool> changedCache(1, false); + + conditionTracker.evaluateCondition(event, matcherState, allPredicates, conditionCache, + changedCache); + + if (position == Position::FIRST || + position == Position::LAST) { + EXPECT_EQ(1UL, conditionTracker.mSlicedConditionState.size()); + } else { + EXPECT_EQ(uids.size(), conditionTracker.mSlicedConditionState.size()); + } + EXPECT_TRUE(changedCache[0]); + + // Now test query + const auto queryKey = getWakeLockQueryKey(position, uids, conditionName); + conditionCache[0] = ConditionState::kNotEvaluated; + + conditionTracker.isConditionMet(queryKey, allPredicates, conditionCache); + EXPECT_EQ(ConditionState::kTrue, conditionCache[0]); + + // another wake lock acquired by this uid + LogEvent event2(1 /*tagId*/, 0 /*timestamp*/); + makeWakeLockEvent(&event2, uids, "wl2", 1); + matcherState.clear(); + matcherState.push_back(MatchingState::kMatched); + matcherState.push_back(MatchingState::kNotMatched); + conditionCache[0] = ConditionState::kNotEvaluated; + changedCache[0] = false; + conditionTracker.evaluateCondition(event2, matcherState, allPredicates, conditionCache, + changedCache); + EXPECT_FALSE(changedCache[0]); + if (position == Position::FIRST || + position == Position::LAST) { + EXPECT_EQ(1UL, conditionTracker.mSlicedConditionState.size()); + } else { + EXPECT_EQ(uids.size(), conditionTracker.mSlicedConditionState.size()); + } + + // wake lock 1 release + LogEvent event3(1 /*tagId*/, 0 /*timestamp*/); + makeWakeLockEvent(&event3, uids, "wl1", 0); // now release it. + matcherState.clear(); + matcherState.push_back(MatchingState::kNotMatched); + matcherState.push_back(MatchingState::kMatched); + conditionCache[0] = ConditionState::kNotEvaluated; + changedCache[0] = false; + conditionTracker.evaluateCondition(event3, matcherState, allPredicates, conditionCache, + changedCache); + // nothing changes, because wake lock 2 is still held for this uid + EXPECT_FALSE(changedCache[0]); + if (position == Position::FIRST || + position == Position::LAST) { + EXPECT_EQ(1UL, conditionTracker.mSlicedConditionState.size()); + } else { + EXPECT_EQ(uids.size(), conditionTracker.mSlicedConditionState.size()); + } + + LogEvent event4(1 /*tagId*/, 0 /*timestamp*/); + makeWakeLockEvent(&event4, uids, "wl2", 0); // now release it. + matcherState.clear(); + matcherState.push_back(MatchingState::kNotMatched); + matcherState.push_back(MatchingState::kMatched); + conditionCache[0] = ConditionState::kNotEvaluated; + changedCache[0] = false; + conditionTracker.evaluateCondition(event4, matcherState, allPredicates, conditionCache, + changedCache); + EXPECT_EQ(0UL, conditionTracker.mSlicedConditionState.size()); + EXPECT_TRUE(changedCache[0]); + + // query again + conditionCache[0] = ConditionState::kNotEvaluated; + conditionTracker.isConditionMet(queryKey, allPredicates, conditionCache); + EXPECT_EQ(ConditionState::kFalse, conditionCache[0]); - // wake lock 1 release - LogEvent event3(1 /*tagId*/, 0 /*timestamp*/); - makeWakeLockEvent(&event3, uid, "wl1", 0); // now release it. - matcherState.clear(); - matcherState.push_back(MatchingState::kNotMatched); - matcherState.push_back(MatchingState::kMatched); - conditionCache[0] = ConditionState::kNotEvaluated; - changedCache[0] = false; - conditionTracker.evaluateCondition(event3, matcherState, allPredicates, conditionCache, - changedCache); - // nothing changes, because wake lock 2 is still held for this uid - EXPECT_FALSE(changedCache[0]); - - LogEvent event4(1 /*tagId*/, 0 /*timestamp*/); - makeWakeLockEvent(&event4, uid, "wl2", 0); // now release it. - matcherState.clear(); - matcherState.push_back(MatchingState::kNotMatched); - matcherState.push_back(MatchingState::kMatched); - conditionCache[0] = ConditionState::kNotEvaluated; - changedCache[0] = false; - conditionTracker.evaluateCondition(event4, matcherState, allPredicates, conditionCache, - changedCache); - EXPECT_EQ(0UL, conditionTracker.mSlicedConditionState.size()); - EXPECT_TRUE(changedCache[0]); + } - // query again - conditionCache[0] = ConditionState::kNotEvaluated; - conditionTracker.isConditionMet(queryKey, allPredicates, conditionCache); - EXPECT_EQ(ConditionState::kFalse, conditionCache[0]); } TEST(SimpleConditionTrackerTest, TestSlicedWithNoOutputDim) { SimplePredicate simplePredicate = getWakeLockHeldCondition( - true /*nesting*/, true /*default to false*/, false /*slice output by uid*/); + true /*nesting*/, true /*default to false*/, false /*slice output by uid*/, + Position::ANY /* position */); string conditionName = "WL_HELD"; - unordered_map<string, int> trackerNameIndexMap; - trackerNameIndexMap["WAKE_LOCK_ACQUIRE"] = 0; - trackerNameIndexMap["WAKE_LOCK_RELEASE"] = 1; - trackerNameIndexMap["RELEASE_ALL"] = 2; + unordered_map<int64_t, int> trackerNameIndexMap; + trackerNameIndexMap[StringToId("WAKE_LOCK_ACQUIRE")] = 0; + trackerNameIndexMap[StringToId("WAKE_LOCK_RELEASE")] = 1; + trackerNameIndexMap[StringToId("RELEASE_ALL")] = 2; - SimpleConditionTracker conditionTracker(kConfigKey, conditionName, + SimpleConditionTracker conditionTracker(kConfigKey, StringToId(conditionName), 0 /*condition tracker index*/, simplePredicate, trackerNameIndexMap); - int uid1 = 111; + + std::vector<int> uid_list1 = {111, 1111, 11111}; string uid1_wl1 = "wl1_1"; - int uid2 = 222; + std::vector<int> uid_list2 = {222, 2222, 22222}; string uid2_wl1 = "wl2_1"; LogEvent event(1 /*tagId*/, 0 /*timestamp*/); - makeWakeLockEvent(&event, uid1, uid1_wl1, 1); + makeWakeLockEvent(&event, uid_list1, uid1_wl1, 1); // one matched start for uid1 vector<MatchingState> matcherState; @@ -340,7 +407,7 @@ TEST(SimpleConditionTrackerTest, TestSlicedWithNoOutputDim) { EXPECT_TRUE(changedCache[0]); // Now test query - map<string, HashableDimensionKey> queryKey; + ConditionKey queryKey; conditionCache[0] = ConditionState::kNotEvaluated; conditionTracker.isConditionMet(queryKey, allPredicates, conditionCache); @@ -348,7 +415,7 @@ TEST(SimpleConditionTrackerTest, TestSlicedWithNoOutputDim) { // another wake lock acquired by this uid LogEvent event2(1 /*tagId*/, 0 /*timestamp*/); - makeWakeLockEvent(&event2, uid2, uid2_wl1, 1); + makeWakeLockEvent(&event2, uid_list2, uid2_wl1, 1); matcherState.clear(); matcherState.push_back(MatchingState::kMatched); matcherState.push_back(MatchingState::kNotMatched); @@ -360,7 +427,7 @@ TEST(SimpleConditionTrackerTest, TestSlicedWithNoOutputDim) { // uid1 wake lock 1 release LogEvent event3(1 /*tagId*/, 0 /*timestamp*/); - makeWakeLockEvent(&event3, uid1, uid1_wl1, 0); // now release it. + makeWakeLockEvent(&event3, uid_list1, uid1_wl1, 0); // now release it. matcherState.clear(); matcherState.push_back(MatchingState::kNotMatched); matcherState.push_back(MatchingState::kMatched); @@ -372,7 +439,7 @@ TEST(SimpleConditionTrackerTest, TestSlicedWithNoOutputDim) { EXPECT_FALSE(changedCache[0]); LogEvent event4(1 /*tagId*/, 0 /*timestamp*/); - makeWakeLockEvent(&event4, uid2, uid2_wl1, 0); // now release it. + makeWakeLockEvent(&event4, uid_list2, uid2_wl1, 0); // now release it. matcherState.clear(); matcherState.push_back(MatchingState::kNotMatched); matcherState.push_back(MatchingState::kMatched); @@ -390,95 +457,111 @@ TEST(SimpleConditionTrackerTest, TestSlicedWithNoOutputDim) { } TEST(SimpleConditionTrackerTest, TestStopAll) { - SimplePredicate simplePredicate = getWakeLockHeldCondition( - true /*nesting*/, true /*default to false*/, true /*output slice by uid*/); - string conditionName = "WL_HELD_BY_UID3"; - - unordered_map<string, int> trackerNameIndexMap; - trackerNameIndexMap["WAKE_LOCK_ACQUIRE"] = 0; - trackerNameIndexMap["WAKE_LOCK_RELEASE"] = 1; - trackerNameIndexMap["RELEASE_ALL"] = 2; - - SimpleConditionTracker conditionTracker(kConfigKey, conditionName, - 0 /*condition tracker index*/, simplePredicate, - trackerNameIndexMap); - int uid1 = 111; - int uid2 = 222; - - LogEvent event(1 /*tagId*/, 0 /*timestamp*/); - makeWakeLockEvent(&event, uid1, "wl1", 1); - - // one matched start - vector<MatchingState> matcherState; - matcherState.push_back(MatchingState::kMatched); - matcherState.push_back(MatchingState::kNotMatched); - matcherState.push_back(MatchingState::kNotMatched); - vector<sp<ConditionTracker>> allPredicates; - vector<ConditionState> conditionCache(1, ConditionState::kNotEvaluated); - vector<bool> changedCache(1, false); - - conditionTracker.evaluateCondition(event, matcherState, allPredicates, conditionCache, - changedCache); - - EXPECT_EQ(1UL, conditionTracker.mSlicedConditionState.size()); - EXPECT_TRUE(changedCache[0]); - - // Now test query - const auto queryKey = getWakeLockQueryKey(1, uid1, conditionName); - conditionCache[0] = ConditionState::kNotEvaluated; - - conditionTracker.isConditionMet(queryKey, allPredicates, conditionCache); - EXPECT_EQ(ConditionState::kTrue, conditionCache[0]); - - // another wake lock acquired by uid2 - LogEvent event2(1 /*tagId*/, 0 /*timestamp*/); - makeWakeLockEvent(&event2, uid2, "wl2", 1); - matcherState.clear(); - matcherState.push_back(MatchingState::kMatched); - matcherState.push_back(MatchingState::kNotMatched); - matcherState.push_back(MatchingState::kNotMatched); - conditionCache[0] = ConditionState::kNotEvaluated; - changedCache[0] = false; - conditionTracker.evaluateCondition(event2, matcherState, allPredicates, conditionCache, - changedCache); - EXPECT_EQ(2UL, conditionTracker.mSlicedConditionState.size()); - EXPECT_TRUE(changedCache[0]); - - // TEST QUERY - const auto queryKey2 = getWakeLockQueryKey(1, uid2, conditionName); - conditionCache[0] = ConditionState::kNotEvaluated; - - conditionTracker.isConditionMet(queryKey, allPredicates, conditionCache); - EXPECT_EQ(ConditionState::kTrue, conditionCache[0]); - - - // stop all event - LogEvent event3(2 /*tagId*/, 0 /*timestamp*/); - matcherState.clear(); - matcherState.push_back(MatchingState::kNotMatched); - matcherState.push_back(MatchingState::kNotMatched); - matcherState.push_back(MatchingState::kMatched); - - conditionCache[0] = ConditionState::kNotEvaluated; - changedCache[0] = false; - conditionTracker.evaluateCondition(event3, matcherState, allPredicates, conditionCache, - changedCache); - EXPECT_TRUE(changedCache[0]); - EXPECT_EQ(0UL, conditionTracker.mSlicedConditionState.size()); - - // TEST QUERY - const auto queryKey3 = getWakeLockQueryKey(1, uid1, conditionName); - conditionCache[0] = ConditionState::kNotEvaluated; - - conditionTracker.isConditionMet(queryKey, allPredicates, conditionCache); - EXPECT_EQ(ConditionState::kFalse, conditionCache[0]); - - // TEST QUERY - const auto queryKey4 = getWakeLockQueryKey(1, uid2, conditionName); - conditionCache[0] = ConditionState::kNotEvaluated; + for (Position position : + {Position::ANY, Position::FIRST, Position::LAST}) { + SimplePredicate simplePredicate = getWakeLockHeldCondition( + true /*nesting*/, true /*default to false*/, true /*output slice by uid*/, + position); + string conditionName = "WL_HELD_BY_UID3"; + + unordered_map<int64_t, int> trackerNameIndexMap; + trackerNameIndexMap[StringToId("WAKE_LOCK_ACQUIRE")] = 0; + trackerNameIndexMap[StringToId("WAKE_LOCK_RELEASE")] = 1; + trackerNameIndexMap[StringToId("RELEASE_ALL")] = 2; + + SimpleConditionTracker conditionTracker(kConfigKey, StringToId(conditionName), + 0 /*condition tracker index*/, simplePredicate, + trackerNameIndexMap); + + std::vector<int> uid_list1 = {111, 1111, 11111}; + std::vector<int> uid_list2 = {222, 2222, 22222}; + + LogEvent event(1 /*tagId*/, 0 /*timestamp*/); + makeWakeLockEvent(&event, uid_list1, "wl1", 1); + + // one matched start + vector<MatchingState> matcherState; + matcherState.push_back(MatchingState::kMatched); + matcherState.push_back(MatchingState::kNotMatched); + matcherState.push_back(MatchingState::kNotMatched); + vector<sp<ConditionTracker>> allPredicates; + vector<ConditionState> conditionCache(1, ConditionState::kNotEvaluated); + vector<bool> changedCache(1, false); + + conditionTracker.evaluateCondition(event, matcherState, allPredicates, conditionCache, + changedCache); + if (position == Position::FIRST || + position == Position::LAST) { + EXPECT_EQ(1UL, conditionTracker.mSlicedConditionState.size()); + } else { + EXPECT_EQ(uid_list1.size(), conditionTracker.mSlicedConditionState.size()); + } + EXPECT_TRUE(changedCache[0]); + + // Now test query + const auto queryKey = getWakeLockQueryKey(position, uid_list1, conditionName); + conditionCache[0] = ConditionState::kNotEvaluated; + + conditionTracker.isConditionMet(queryKey, allPredicates, conditionCache); + EXPECT_EQ(ConditionState::kTrue, conditionCache[0]); + + // another wake lock acquired by uid2 + LogEvent event2(1 /*tagId*/, 0 /*timestamp*/); + makeWakeLockEvent(&event2, uid_list2, "wl2", 1); + matcherState.clear(); + matcherState.push_back(MatchingState::kMatched); + matcherState.push_back(MatchingState::kNotMatched); + matcherState.push_back(MatchingState::kNotMatched); + conditionCache[0] = ConditionState::kNotEvaluated; + changedCache[0] = false; + conditionTracker.evaluateCondition(event2, matcherState, allPredicates, conditionCache, + changedCache); + if (position == Position::FIRST || + position == Position::LAST) { + EXPECT_EQ(2UL, conditionTracker.mSlicedConditionState.size()); + } else { + EXPECT_EQ(uid_list1.size() + uid_list2.size(), + conditionTracker.mSlicedConditionState.size()); + } + EXPECT_TRUE(changedCache[0]); + + // TEST QUERY + const auto queryKey2 = getWakeLockQueryKey(position, uid_list2, conditionName); + conditionCache[0] = ConditionState::kNotEvaluated; + + conditionTracker.isConditionMet(queryKey, allPredicates, conditionCache); + EXPECT_EQ(ConditionState::kTrue, conditionCache[0]); + + + // stop all event + LogEvent event3(2 /*tagId*/, 0 /*timestamp*/); + matcherState.clear(); + matcherState.push_back(MatchingState::kNotMatched); + matcherState.push_back(MatchingState::kNotMatched); + matcherState.push_back(MatchingState::kMatched); + + conditionCache[0] = ConditionState::kNotEvaluated; + changedCache[0] = false; + conditionTracker.evaluateCondition(event3, matcherState, allPredicates, conditionCache, + changedCache); + EXPECT_TRUE(changedCache[0]); + EXPECT_EQ(0UL, conditionTracker.mSlicedConditionState.size()); + + // TEST QUERY + const auto queryKey3 = getWakeLockQueryKey(position, uid_list1, conditionName); + conditionCache[0] = ConditionState::kNotEvaluated; + + conditionTracker.isConditionMet(queryKey, allPredicates, conditionCache); + EXPECT_EQ(ConditionState::kFalse, conditionCache[0]); + + // TEST QUERY + const auto queryKey4 = getWakeLockQueryKey(position, uid_list2, conditionName); + conditionCache[0] = ConditionState::kNotEvaluated; + + conditionTracker.isConditionMet(queryKey, allPredicates, conditionCache); + EXPECT_EQ(ConditionState::kFalse, conditionCache[0]); + } - conditionTracker.isConditionMet(queryKey, allPredicates, conditionCache); - EXPECT_EQ(ConditionState::kFalse, conditionCache[0]); } } // namespace statsd diff --git a/cmds/statsd/tests/e2e/MetricConditionLink_e2e_test.cpp b/cmds/statsd/tests/e2e/MetricConditionLink_e2e_test.cpp new file mode 100644 index 000000000000..b56b8176cceb --- /dev/null +++ b/cmds/statsd/tests/e2e/MetricConditionLink_e2e_test.cpp @@ -0,0 +1,228 @@ +// Copyright (C) 2017 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. + +#include <gtest/gtest.h> + +#include "src/StatsLogProcessor.h" +#include "tests/statsd_test_util.h" + +#include <vector> + +namespace android { +namespace os { +namespace statsd { + +#ifdef __ANDROID__ + +StatsdConfig CreateStatsdConfig() { + StatsdConfig config; + *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher(); + *config.add_atom_matcher() = CreateScreenTurnedOffAtomMatcher(); + + *config.add_atom_matcher() = CreateSyncStartAtomMatcher(); + *config.add_atom_matcher() = CreateSyncEndAtomMatcher(); + + *config.add_atom_matcher() = CreateMoveToBackgroundAtomMatcher(); + *config.add_atom_matcher() = CreateMoveToForegroundAtomMatcher(); + + auto appCrashMatcher = CreateProcessCrashAtomMatcher(); + *config.add_atom_matcher() = appCrashMatcher; + + auto screenIsOffPredicate = CreateScreenIsOffPredicate(); + + auto isSyncingPredicate = CreateIsSyncingPredicate(); + *isSyncingPredicate.mutable_simple_predicate()->mutable_dimensions() = + CreateDimensions( + android::util::SYNC_STATE_CHANGED, {1 /* uid field */}); + + auto isInBackgroundPredicate = CreateIsInBackgroundPredicate(); + *isInBackgroundPredicate.mutable_simple_predicate()->mutable_dimensions() = + CreateDimensions(android::util::ACTIVITY_FOREGROUND_STATE_CHANGED, {1 /* uid field */ }); + + *config.add_predicate() = screenIsOffPredicate; + *config.add_predicate() = isSyncingPredicate; + *config.add_predicate() = isInBackgroundPredicate; + + auto combinationPredicate = config.add_predicate(); + combinationPredicate->set_id(StringToId("combinationPredicate")); + combinationPredicate->mutable_combination()->set_operation(LogicalOperation::AND); + addPredicateToPredicateCombination(screenIsOffPredicate, combinationPredicate); + addPredicateToPredicateCombination(isSyncingPredicate, combinationPredicate); + addPredicateToPredicateCombination(isInBackgroundPredicate, combinationPredicate); + + auto countMetric = config.add_count_metric(); + countMetric->set_id(StringToId("AppCrashes")); + countMetric->set_what(appCrashMatcher.id()); + countMetric->set_condition(combinationPredicate->id()); + // The metric is dimensioning by uid only. + *countMetric->mutable_dimensions() = + CreateDimensions(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED, {1}); + countMetric->mutable_bucket()->set_bucket_size_millis(30 * 1000LL); + + // Links between crash atom and condition of app is in syncing. + auto links = countMetric->add_links(); + links->set_condition(isSyncingPredicate.id()); + auto dimensionWhat = links->mutable_dimensions_in_what(); + dimensionWhat->set_field(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED); + dimensionWhat->add_child()->set_field(1); // uid field. + auto dimensionCondition = links->mutable_dimensions_in_condition(); + dimensionCondition->set_field(android::util::SYNC_STATE_CHANGED); + dimensionCondition->add_child()->set_field(1); // uid field. + + // Links between crash atom and condition of app is in background. + links = countMetric->add_links(); + links->set_condition(isInBackgroundPredicate.id()); + dimensionWhat = links->mutable_dimensions_in_what(); + dimensionWhat->set_field(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED); + dimensionWhat->add_child()->set_field(1); // uid field. + dimensionCondition = links->mutable_dimensions_in_condition(); + dimensionCondition->set_field(android::util::ACTIVITY_FOREGROUND_STATE_CHANGED); + dimensionCondition->add_child()->set_field(1); // uid field. + return config; +} + +TEST(MetricConditionLinkE2eTest, TestMultiplePredicatesAndLinks) { + auto config = CreateStatsdConfig(); + uint64_t bucketStartTimeNs = 10000000000; + uint64_t bucketSizeNs = config.count_metric(0).bucket().bucket_size_millis() * 1000 * 1000; + + ConfigKey cfgKey; + auto processor = CreateStatsLogProcessor(bucketStartTimeNs / NS_PER_SEC, config, cfgKey); + EXPECT_EQ(processor->mMetricsManagers.size(), 1u); + EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); + + int appUid = 123; + auto crashEvent1 = CreateAppCrashEvent(appUid, bucketStartTimeNs + 1); + auto crashEvent2 = CreateAppCrashEvent(appUid, bucketStartTimeNs + 201); + auto crashEvent3= CreateAppCrashEvent(appUid, bucketStartTimeNs + 2 * bucketSizeNs - 101); + + auto crashEvent4 = CreateAppCrashEvent(appUid, bucketStartTimeNs + 51); + auto crashEvent5 = CreateAppCrashEvent(appUid, bucketStartTimeNs + bucketSizeNs + 299); + auto crashEvent6 = CreateAppCrashEvent(appUid, bucketStartTimeNs + bucketSizeNs + 2001); + + auto crashEvent7 = CreateAppCrashEvent(appUid, bucketStartTimeNs + 16); + auto crashEvent8 = CreateAppCrashEvent(appUid, bucketStartTimeNs + bucketSizeNs + 249); + + auto crashEvent9 = CreateAppCrashEvent(appUid, bucketStartTimeNs + bucketSizeNs + 351); + auto crashEvent10 = CreateAppCrashEvent(appUid, bucketStartTimeNs + 2 * bucketSizeNs - 2); + + auto screenTurnedOnEvent = + CreateScreenStateChangedEvent(ScreenStateChanged::STATE_ON, bucketStartTimeNs + 2); + auto screenTurnedOffEvent = + CreateScreenStateChangedEvent(ScreenStateChanged::STATE_OFF, bucketStartTimeNs + 200); + auto screenTurnedOnEvent2 = + CreateScreenStateChangedEvent(ScreenStateChanged::STATE_ON, + bucketStartTimeNs + 2 * bucketSizeNs - 100); + + auto syncOnEvent1 = + CreateSyncStartEvent(appUid, "ReadEmail", bucketStartTimeNs + 50); + auto syncOffEvent1 = + CreateSyncEndEvent(appUid, "ReadEmail", bucketStartTimeNs + bucketSizeNs + 300); + auto syncOnEvent2 = + CreateSyncStartEvent(appUid, "ReadDoc", bucketStartTimeNs + bucketSizeNs + 2000); + + auto moveToBackgroundEvent1 = + CreateMoveToBackgroundEvent(appUid, bucketStartTimeNs + 15); + auto moveToForegroundEvent1 = + CreateMoveToForegroundEvent(appUid, bucketStartTimeNs + bucketSizeNs + 250); + + auto moveToBackgroundEvent2 = + CreateMoveToBackgroundEvent(appUid, bucketStartTimeNs + bucketSizeNs + 350); + auto moveToForegroundEvent2 = + CreateMoveToForegroundEvent(appUid, bucketStartTimeNs + 2 * bucketSizeNs - 1); + + /* + bucket #1 bucket #2 + + + | | | | | | | | | | (crashEvents) + |-------------------------------------|-----------------------------------|--------- + + | | (MoveToBkground) + + | | (MoveToForeground) + + | | (SyncIsOn) + | (SyncIsOff) + | | (ScreenIsOn) + | (ScreenIsOff) + */ + std::vector<std::unique_ptr<LogEvent>> events; + events.push_back(std::move(crashEvent1)); + events.push_back(std::move(crashEvent2)); + events.push_back(std::move(crashEvent3)); + events.push_back(std::move(crashEvent4)); + events.push_back(std::move(crashEvent5)); + events.push_back(std::move(crashEvent6)); + events.push_back(std::move(crashEvent7)); + events.push_back(std::move(crashEvent8)); + events.push_back(std::move(crashEvent9)); + events.push_back(std::move(crashEvent10)); + events.push_back(std::move(screenTurnedOnEvent)); + events.push_back(std::move(screenTurnedOffEvent)); + events.push_back(std::move(screenTurnedOnEvent2)); + events.push_back(std::move(syncOnEvent1)); + events.push_back(std::move(syncOffEvent1)); + events.push_back(std::move(syncOnEvent2)); + events.push_back(std::move(moveToBackgroundEvent1)); + events.push_back(std::move(moveToForegroundEvent1)); + events.push_back(std::move(moveToBackgroundEvent2)); + events.push_back(std::move(moveToForegroundEvent2)); + + sortLogEventsByTimestamp(&events); + + for (const auto& event : events) { + processor->OnLogEvent(*event); + } + ConfigMetricsReportList reports; + processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs - 1, &reports); + EXPECT_EQ(reports.reports_size(), 1); + EXPECT_EQ(reports.reports(0).metrics_size(), 1); + EXPECT_EQ(reports.reports(0).metrics(0).count_metrics().data_size(), 1); + EXPECT_EQ(reports.reports(0).metrics(0).count_metrics().data(0).bucket_info_size(), 1); + EXPECT_EQ(reports.reports(0).metrics(0).count_metrics().data(0).bucket_info(0).count(), 1); + auto data = reports.reports(0).metrics(0).count_metrics().data(0); + // Validate dimension value. + EXPECT_EQ(data.dimension().field(), + android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED); + EXPECT_EQ(data.dimension().value_tuple().dimensions_value_size(), 1); + // Uid field. + EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0).field(), 1); + EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0).value_int(), appUid); + + reports.Clear(); + processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs + 1, &reports); + EXPECT_EQ(reports.reports_size(), 1); + EXPECT_EQ(reports.reports(0).metrics_size(), 1); + EXPECT_EQ(reports.reports(0).metrics(0).count_metrics().data_size(), 1); + EXPECT_EQ(reports.reports(0).metrics(0).count_metrics().data(0).bucket_info_size(), 2); + EXPECT_EQ(reports.reports(0).metrics(0).count_metrics().data(0).bucket_info(0).count(), 1); + EXPECT_EQ(reports.reports(0).metrics(0).count_metrics().data(0).bucket_info(1).count(), 3); + data = reports.reports(0).metrics(0).count_metrics().data(0); + // Validate dimension value. + EXPECT_EQ(data.dimension().field(), + android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED); + EXPECT_EQ(data.dimension().value_tuple().dimensions_value_size(), 1); + // Uid field. + EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0).field(), 1); + EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0).value_int(), appUid); +} + +#else +GTEST_LOG_(INFO) << "This test does nothing.\n"; +#endif + +} // namespace statsd +} // namespace os +} // namespace android
\ No newline at end of file diff --git a/cmds/statsd/tests/e2e/WakelockDuration_e2e_test.cpp b/cmds/statsd/tests/e2e/WakelockDuration_e2e_test.cpp new file mode 100644 index 000000000000..ecdb002c1863 --- /dev/null +++ b/cmds/statsd/tests/e2e/WakelockDuration_e2e_test.cpp @@ -0,0 +1,178 @@ +// Copyright (C) 2017 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. + +#include <gtest/gtest.h> + +#include "src/StatsLogProcessor.h" +#include "tests/statsd_test_util.h" + +#include <vector> + +namespace android { +namespace os { +namespace statsd { + +#ifdef __ANDROID__ + +StatsdConfig CreateStatsdConfig(DurationMetric::AggregationType aggregationType) { + StatsdConfig config; + *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher(); + *config.add_atom_matcher() = CreateScreenTurnedOffAtomMatcher(); + *config.add_atom_matcher() = CreateAcquireWakelockAtomMatcher(); + *config.add_atom_matcher() = CreateReleaseWakelockAtomMatcher(); + + auto screenIsOffPredicate = CreateScreenIsOffPredicate(); + *config.add_predicate() = screenIsOffPredicate; + + auto holdingWakelockPredicate = CreateHoldingWakelockPredicate(); + // The predicate is dimensioning by any attribution node and both by uid and tag. + *holdingWakelockPredicate.mutable_simple_predicate()->mutable_dimensions() = + CreateAttributionUidAndTagDimensions( + android::util::WAKELOCK_STATE_CHANGED, {Position::FIRST, Position::LAST}); + *config.add_predicate() = holdingWakelockPredicate; + + auto durationMetric = config.add_duration_metric(); + durationMetric->set_id(StringToId("WakelockDuration")); + durationMetric->set_what(holdingWakelockPredicate.id()); + durationMetric->set_condition(screenIsOffPredicate.id()); + durationMetric->set_aggregation_type(aggregationType); + // The metric is dimensioning by first attribution node and only by uid. + *durationMetric->mutable_dimensions() = + CreateAttributionUidDimensions( + android::util::WAKELOCK_STATE_CHANGED, {Position::FIRST}); + durationMetric->mutable_bucket()->set_bucket_size_millis(30 * 1000LL); + return config; +} + +TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensions) { + ConfigKey cfgKey; + for (auto aggregationType : { DurationMetric::SUM, DurationMetric::MAX_SPARSE }) { + auto config = CreateStatsdConfig(aggregationType); + uint64_t bucketStartTimeNs = 10000000000; + uint64_t bucketSizeNs = + config.duration_metric(0).bucket().bucket_size_millis() * 1000 * 1000; + + auto processor = CreateStatsLogProcessor(bucketStartTimeNs / NS_PER_SEC, config, cfgKey); + EXPECT_EQ(processor->mMetricsManagers.size(), 1u); + EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); + + auto screenTurnedOnEvent = + CreateScreenStateChangedEvent(ScreenStateChanged::STATE_ON, bucketStartTimeNs + 1); + auto screenTurnedOffEvent = + CreateScreenStateChangedEvent(ScreenStateChanged::STATE_OFF, bucketStartTimeNs + 200); + auto screenTurnedOnEvent2 = + CreateScreenStateChangedEvent(ScreenStateChanged::STATE_ON, + bucketStartTimeNs + bucketSizeNs + 500); + + std::vector<AttributionNode> attributions1 = + {CreateAttribution(111, "App1"), CreateAttribution(222, "GMSCoreModule1"), + CreateAttribution(222, "GMSCoreModule2")}; + + std::vector<AttributionNode> attributions2 = + {CreateAttribution(111, "App2"), CreateAttribution(222, "GMSCoreModule1"), + CreateAttribution(222, "GMSCoreModule2")}; + + auto acquireEvent1 = CreateAcquireWakelockEvent( + attributions1, "wl1", bucketStartTimeNs + 2); + auto acquireEvent2 = CreateAcquireWakelockEvent( + attributions2, "wl2", bucketStartTimeNs + bucketSizeNs - 10); + + auto releaseEvent1 = CreateReleaseWakelockEvent( + attributions1, "wl1", bucketStartTimeNs + bucketSizeNs + 2); + auto releaseEvent2 = CreateReleaseWakelockEvent( + attributions2, "wl2", bucketStartTimeNs + 2 * bucketSizeNs - 15); + + std::vector<std::unique_ptr<LogEvent>> events; + + events.push_back(std::move(screenTurnedOnEvent)); + events.push_back(std::move(screenTurnedOffEvent)); + events.push_back(std::move(screenTurnedOnEvent2)); + events.push_back(std::move(acquireEvent1)); + events.push_back(std::move(acquireEvent2)); + events.push_back(std::move(releaseEvent1)); + events.push_back(std::move(releaseEvent2)); + + sortLogEventsByTimestamp(&events); + + for (const auto& event : events) { + processor->OnLogEvent(*event); + } + + ConfigMetricsReportList reports; + processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs - 1, &reports); + EXPECT_EQ(reports.reports_size(), 1); + EXPECT_EQ(reports.reports(0).metrics_size(), 1); + // Only 1 dimension output. The tag dimension in the predicate has been aggregated. + EXPECT_EQ(reports.reports(0).metrics(0).duration_metrics().data_size(), 1); + + auto data = reports.reports(0).metrics(0).duration_metrics().data(0); + // Validate dimension value. + EXPECT_EQ(data.dimension().field(), + android::util::WAKELOCK_STATE_CHANGED); + EXPECT_EQ(data.dimension().value_tuple().dimensions_value_size(), 1); + // Attribution field. + EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0).field(), 1); + // Uid only. + EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0) + .value_tuple().dimensions_value_size(), 1); + EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0) + .value_tuple().dimensions_value(0).field(), 1); + EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0) + .value_tuple().dimensions_value(0).value_int(), 111); + // Validate bucket info. + EXPECT_EQ(reports.reports(0).metrics(0).duration_metrics().data(0).bucket_info_size(), 1); + data = reports.reports(0).metrics(0).duration_metrics().data(0); + // The wakelock holding interval starts from the screen off event and to the end of the 1st + // bucket. + EXPECT_EQ((unsigned long long)data.bucket_info(0).duration_nanos(), bucketSizeNs - 200); + + reports.Clear(); + processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs + 1, &reports); + EXPECT_EQ(reports.reports_size(), 1); + EXPECT_EQ(reports.reports(0).metrics_size(), 1); + EXPECT_EQ(reports.reports(0).metrics(0).duration_metrics().data_size(), 1); + // Dump the report after the end of 2nd bucket. + EXPECT_EQ(reports.reports(0).metrics(0).duration_metrics().data(0).bucket_info_size(), 2); + data = reports.reports(0).metrics(0).duration_metrics().data(0); + // Validate dimension value. + EXPECT_EQ(data.dimension().field(), + android::util::WAKELOCK_STATE_CHANGED); + EXPECT_EQ(data.dimension().value_tuple().dimensions_value_size(), 1); + // Attribution field. + EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0).field(), 1); + // Uid only. + EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0) + .value_tuple().dimensions_value_size(), 1); + EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0) + .value_tuple().dimensions_value(0).field(), 1); + EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0) + .value_tuple().dimensions_value(0).value_int(), 111); + // Two output buckets. + // The wakelock holding interval in the 1st bucket starts from the screen off event and to + // the end of the 1st bucket. + EXPECT_EQ((unsigned long long)data.bucket_info(0).duration_nanos(), + bucketStartTimeNs + bucketSizeNs - (bucketStartTimeNs + 200)); + // The wakelock holding interval in the 2nd bucket starts at the beginning of the bucket and + // ends at the second screen on event. + EXPECT_EQ((unsigned long long)data.bucket_info(1).duration_nanos(), 500UL); + } +} + +#else +GTEST_LOG_(INFO) << "This test does nothing.\n"; +#endif + +} // namespace statsd +} // namespace os +} // namespace android
\ No newline at end of file diff --git a/cmds/statsd/tests/guardrail/StatsdStats_test.cpp b/cmds/statsd/tests/guardrail/StatsdStats_test.cpp index 765804475eef..a1343002405b 100644 --- a/cmds/statsd/tests/guardrail/StatsdStats_test.cpp +++ b/cmds/statsd/tests/guardrail/StatsdStats_test.cpp @@ -14,6 +14,7 @@ #include "src/guardrail/StatsdStats.h" #include "statslog.h" +#include "tests/statsd_test_util.h" #include <gtest/gtest.h> #include <vector> @@ -28,8 +29,7 @@ using std::vector; TEST(StatsdStatsTest, TestValidConfigAdd) { StatsdStats stats; - string name = "StatsdTest"; - ConfigKey key(0, name); + ConfigKey key(0, 12345); const int metricsCount = 10; const int conditionsCount = 20; const int matchersCount = 30; @@ -45,7 +45,7 @@ TEST(StatsdStatsTest, TestValidConfigAdd) { EXPECT_EQ(1, report.config_stats_size()); const auto& configReport = report.config_stats(0); EXPECT_EQ(0, configReport.uid()); - EXPECT_EQ(name, configReport.name()); + EXPECT_EQ(12345, configReport.id()); EXPECT_EQ(metricsCount, configReport.metric_count()); EXPECT_EQ(conditionsCount, configReport.condition_count()); EXPECT_EQ(matchersCount, configReport.matcher_count()); @@ -56,8 +56,7 @@ TEST(StatsdStatsTest, TestValidConfigAdd) { TEST(StatsdStatsTest, TestInvalidConfigAdd) { StatsdStats stats; - string name = "StatsdTest"; - ConfigKey key(0, name); + ConfigKey key(0, 12345); const int metricsCount = 10; const int conditionsCount = 20; const int matchersCount = 30; @@ -78,8 +77,7 @@ TEST(StatsdStatsTest, TestInvalidConfigAdd) { TEST(StatsdStatsTest, TestConfigRemove) { StatsdStats stats; - string name = "StatsdTest"; - ConfigKey key(0, name); + ConfigKey key(0, 12345); const int metricsCount = 10; const int conditionsCount = 20; const int matchersCount = 30; @@ -105,22 +103,22 @@ TEST(StatsdStatsTest, TestConfigRemove) { TEST(StatsdStatsTest, TestSubStats) { StatsdStats stats; - ConfigKey key(0, "test"); + ConfigKey key(0, 12345); stats.noteConfigReceived(key, 2, 3, 4, 5, true); - stats.noteMatcherMatched(key, "matcher1"); - stats.noteMatcherMatched(key, "matcher1"); - stats.noteMatcherMatched(key, "matcher2"); + stats.noteMatcherMatched(key, StringToId("matcher1")); + stats.noteMatcherMatched(key, StringToId("matcher1")); + stats.noteMatcherMatched(key, StringToId("matcher2")); - stats.noteConditionDimensionSize(key, "condition1", 250); - stats.noteConditionDimensionSize(key, "condition1", 240); + stats.noteConditionDimensionSize(key, StringToId("condition1"), 250); + stats.noteConditionDimensionSize(key, StringToId("condition1"), 240); - stats.noteMetricDimensionSize(key, "metric1", 201); - stats.noteMetricDimensionSize(key, "metric1", 202); + stats.noteMetricDimensionSize(key, StringToId("metric1"), 201); + stats.noteMetricDimensionSize(key, StringToId("metric1"), 202); - stats.noteAnomalyDeclared(key, "alert1"); - stats.noteAnomalyDeclared(key, "alert1"); - stats.noteAnomalyDeclared(key, "alert2"); + stats.noteAnomalyDeclared(key, StringToId("alert1")); + stats.noteAnomalyDeclared(key, StringToId("alert1")); + stats.noteAnomalyDeclared(key, StringToId("alert2")); // broadcast-> 2 stats.noteBroadcastSent(key); @@ -147,39 +145,39 @@ TEST(StatsdStatsTest, TestSubStats) { EXPECT_EQ(2, configReport.matcher_stats_size()); // matcher1 is the first in the list - if (!configReport.matcher_stats(0).name().compare("matcher1")) { + if (configReport.matcher_stats(0).id() == StringToId("matcher1")) { EXPECT_EQ(2, configReport.matcher_stats(0).matched_times()); EXPECT_EQ(1, configReport.matcher_stats(1).matched_times()); - EXPECT_EQ("matcher2", configReport.matcher_stats(1).name()); + EXPECT_EQ(StringToId("matcher2"), configReport.matcher_stats(1).id()); } else { // matcher1 is the second in the list. EXPECT_EQ(1, configReport.matcher_stats(0).matched_times()); - EXPECT_EQ("matcher2", configReport.matcher_stats(0).name()); + EXPECT_EQ(StringToId("matcher2"), configReport.matcher_stats(0).id()); EXPECT_EQ(2, configReport.matcher_stats(1).matched_times()); - EXPECT_EQ("matcher1", configReport.matcher_stats(1).name()); + EXPECT_EQ(StringToId("matcher1"), configReport.matcher_stats(1).id()); } EXPECT_EQ(2, configReport.alert_stats_size()); - bool alert1first = !configReport.alert_stats(0).name().compare("alert1"); - EXPECT_EQ("alert1", configReport.alert_stats(alert1first ? 0 : 1).name()); + bool alert1first = configReport.alert_stats(0).id() == StringToId("alert1"); + EXPECT_EQ(StringToId("alert1"), configReport.alert_stats(alert1first ? 0 : 1).id()); EXPECT_EQ(2, configReport.alert_stats(alert1first ? 0 : 1).alerted_times()); - EXPECT_EQ("alert2", configReport.alert_stats(alert1first ? 1 : 0).name()); + EXPECT_EQ(StringToId("alert2"), configReport.alert_stats(alert1first ? 1 : 0).id()); EXPECT_EQ(1, configReport.alert_stats(alert1first ? 1 : 0).alerted_times()); EXPECT_EQ(1, configReport.condition_stats_size()); - EXPECT_EQ("condition1", configReport.condition_stats(0).name()); + EXPECT_EQ(StringToId("condition1"), configReport.condition_stats(0).id()); EXPECT_EQ(250, configReport.condition_stats(0).max_tuple_counts()); EXPECT_EQ(1, configReport.metric_stats_size()); - EXPECT_EQ("metric1", configReport.metric_stats(0).name()); + EXPECT_EQ(StringToId("metric1"), configReport.metric_stats(0).id()); EXPECT_EQ(202, configReport.metric_stats(0).max_tuple_counts()); // after resetting the stats, some new events come - stats.noteMatcherMatched(key, "matcher99"); - stats.noteConditionDimensionSize(key, "condition99", 300); - stats.noteMetricDimensionSize(key, "metric99", 270); - stats.noteAnomalyDeclared(key, "alert99"); + stats.noteMatcherMatched(key, StringToId("matcher99")); + stats.noteConditionDimensionSize(key, StringToId("condition99"), 300); + stats.noteMetricDimensionSize(key, StringToId("metric99tion99"), 270); + stats.noteAnomalyDeclared(key, StringToId("alert99")); // now the config stats should only contain the stats about the new event. stats.dumpStats(&output, false); @@ -188,19 +186,19 @@ TEST(StatsdStatsTest, TestSubStats) { EXPECT_EQ(1, report.config_stats_size()); const auto& configReport2 = report.config_stats(0); EXPECT_EQ(1, configReport2.matcher_stats_size()); - EXPECT_EQ("matcher99", configReport2.matcher_stats(0).name()); + EXPECT_EQ(StringToId("matcher99"), configReport2.matcher_stats(0).id()); EXPECT_EQ(1, configReport2.matcher_stats(0).matched_times()); EXPECT_EQ(1, configReport2.condition_stats_size()); - EXPECT_EQ("condition99", configReport2.condition_stats(0).name()); + EXPECT_EQ(StringToId("condition99"), configReport2.condition_stats(0).id()); EXPECT_EQ(300, configReport2.condition_stats(0).max_tuple_counts()); EXPECT_EQ(1, configReport2.metric_stats_size()); - EXPECT_EQ("metric99", configReport2.metric_stats(0).name()); + EXPECT_EQ(StringToId("metric99tion99"), configReport2.metric_stats(0).id()); EXPECT_EQ(270, configReport2.metric_stats(0).max_tuple_counts()); EXPECT_EQ(1, configReport2.alert_stats_size()); - EXPECT_EQ("alert99", configReport2.alert_stats(0).name()); + EXPECT_EQ(StringToId("alert99"), configReport2.alert_stats(0).id()); EXPECT_EQ(1, configReport2.alert_stats(0).alerted_times()); } @@ -260,7 +258,7 @@ TEST(StatsdStatsTest, TestTimestampThreshold) { for (int i = 0; i < StatsdStats::kMaxTimestampCount; i++) { timestamps.push_back(i); } - ConfigKey key(0, "test"); + ConfigKey key(0, 12345); stats.noteConfigReceived(key, 2, 3, 4, 5, true); for (int i = 0; i < StatsdStats::kMaxTimestampCount; i++) { diff --git a/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp b/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp index eec9453940dc..4c2d4729d957 100644 --- a/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp +++ b/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp @@ -13,7 +13,9 @@ // limitations under the License. #include "src/metrics/CountMetricProducer.h" +#include "src/dimension.h" #include "metrics_test_helper.h" +#include "tests/statsd_test_util.h" #include <gmock/gmock.h> #include <gtest/gtest.h> @@ -32,7 +34,7 @@ namespace android { namespace os { namespace statsd { -const ConfigKey kConfigKey(0, "test"); +const ConfigKey kConfigKey(0, 12345); TEST(CountMetricProducerTest, TestNonDimensionalEvents) { int64_t bucketStartTimeNs = 10000000000; @@ -42,7 +44,7 @@ TEST(CountMetricProducerTest, TestNonDimensionalEvents) { int tagId = 1; CountMetric metric; - metric.set_name("1"); + metric.set_id(1); metric.mutable_bucket()->set_bucket_size_millis(bucketSizeNs / 1000000); LogEvent event1(tagId, bucketStartTimeNs + 1); @@ -99,9 +101,9 @@ TEST(CountMetricProducerTest, TestEventsWithNonSlicedCondition) { int64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; CountMetric metric; - metric.set_name("1"); + metric.set_id(1); metric.mutable_bucket()->set_bucket_size_millis(bucketSizeNs / 1000000); - metric.set_condition("SCREEN_ON"); + metric.set_condition(StringToId("SCREEN_ON")); LogEvent event1(1, bucketStartTimeNs + 1); LogEvent event2(1, bucketStartTimeNs + 10); @@ -137,26 +139,31 @@ TEST(CountMetricProducerTest, TestEventsWithSlicedCondition) { int64_t bucketStartTimeNs = 10000000000; int64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; + int tagId = 1; + int conditionTagId = 2; + CountMetric metric; - metric.set_name("1"); + metric.set_id(1); metric.mutable_bucket()->set_bucket_size_millis(bucketSizeNs / 1000000); - metric.set_condition("APP_IN_BACKGROUND_PER_UID_AND_SCREEN_ON"); + metric.set_condition(StringToId("APP_IN_BACKGROUND_PER_UID_AND_SCREEN_ON")); MetricConditionLink* link = metric.add_links(); - link->set_condition("APP_IN_BACKGROUND_PER_UID"); - link->add_key_in_what()->set_key(1); - link->add_key_in_condition()->set_key(2); + link->set_condition(StringToId("APP_IN_BACKGROUND_PER_UID")); + *link->mutable_dimensions_in_what() = buildSimpleAtomFieldMatcher(tagId, 1); + *link->mutable_dimensions_in_condition() = buildSimpleAtomFieldMatcher(conditionTagId, 2); - LogEvent event1(1, bucketStartTimeNs + 1); + LogEvent event1(tagId, bucketStartTimeNs + 1); event1.write("111"); // uid event1.init(); ConditionKey key1; - key1["APP_IN_BACKGROUND_PER_UID"] = getMockedDimensionKey(2, "111"); + key1[StringToId("APP_IN_BACKGROUND_PER_UID")] = + {getMockedDimensionKey(conditionTagId, 2, "111")}; - LogEvent event2(1, bucketStartTimeNs + 10); + LogEvent event2(tagId, bucketStartTimeNs + 10); event2.write("222"); // uid event2.init(); ConditionKey key2; - key2["APP_IN_BACKGROUND_PER_UID"] = getMockedDimensionKey(2, "222"); + key2[StringToId("APP_IN_BACKGROUND_PER_UID")] = + {getMockedDimensionKey(conditionTagId, 2, "222")}; sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); EXPECT_CALL(*wizard, query(_, key1)).WillOnce(Return(ConditionState::kFalse)); @@ -185,8 +192,8 @@ TEST(CountMetricProducerTest, TestEventsWithSlicedCondition) { TEST(CountMetricProducerTest, TestAnomalyDetection) { Alert alert; - alert.set_name("alert"); - alert.set_metric_name("1"); + alert.set_id(11); + alert.set_metric_id(1); alert.set_trigger_if_sum_gt(2); alert.set_number_of_buckets(2); alert.set_refractory_period_secs(1); @@ -196,16 +203,14 @@ TEST(CountMetricProducerTest, TestAnomalyDetection) { int64_t bucket2StartTimeNs = bucketStartTimeNs + bucketSizeNs; int64_t bucket3StartTimeNs = bucketStartTimeNs + 2 * bucketSizeNs; - sp<AnomalyTracker> anomalyTracker = new AnomalyTracker(alert, kConfigKey); - CountMetric metric; - metric.set_name("1"); + metric.set_id(1); metric.mutable_bucket()->set_bucket_size_millis(bucketSizeNs / 1000000); sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); CountMetricProducer countProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard, bucketStartTimeNs); - countProducer.addAnomalyTracker(anomalyTracker); + sp<AnomalyTracker> anomalyTracker = countProducer.addAnomalyTracker(alert); int tagId = 1; LogEvent event1(tagId, bucketStartTimeNs + 1); @@ -222,13 +227,13 @@ TEST(CountMetricProducerTest, TestAnomalyDetection) { EXPECT_EQ(1UL, countProducer.mCurrentSlicedCounter->size()); EXPECT_EQ(2L, countProducer.mCurrentSlicedCounter->begin()->second); - EXPECT_EQ(anomalyTracker->getLastAlarmTimestampNs(), -1LL); + EXPECT_EQ(anomalyTracker->getLastAnomalyTimestampNs(), -1LL); // One event in bucket #2. No alarm as bucket #0 is trashed out. countProducer.onMatchedLogEvent(1 /*log matcher index*/, event3); EXPECT_EQ(1UL, countProducer.mCurrentSlicedCounter->size()); EXPECT_EQ(1L, countProducer.mCurrentSlicedCounter->begin()->second); - EXPECT_EQ(anomalyTracker->getLastAlarmTimestampNs(), -1LL); + EXPECT_EQ(anomalyTracker->getLastAnomalyTimestampNs(), -1LL); // Two events in bucket #3. countProducer.onMatchedLogEvent(1 /*log matcher index*/, event4); @@ -237,12 +242,12 @@ TEST(CountMetricProducerTest, TestAnomalyDetection) { EXPECT_EQ(1UL, countProducer.mCurrentSlicedCounter->size()); EXPECT_EQ(3L, countProducer.mCurrentSlicedCounter->begin()->second); // Anomaly at event 6 is within refractory period. The alarm is at event 5 timestamp not event 6 - EXPECT_EQ(anomalyTracker->getLastAlarmTimestampNs(), (long long)event5.GetTimestampNs()); + EXPECT_EQ(anomalyTracker->getLastAnomalyTimestampNs(), (long long)event5.GetTimestampNs()); countProducer.onMatchedLogEvent(1 /*log matcher index*/, event7); EXPECT_EQ(1UL, countProducer.mCurrentSlicedCounter->size()); EXPECT_EQ(4L, countProducer.mCurrentSlicedCounter->begin()->second); - EXPECT_EQ(anomalyTracker->getLastAlarmTimestampNs(), (long long)event7.GetTimestampNs()); + EXPECT_EQ(anomalyTracker->getLastAnomalyTimestampNs(), (long long)event7.GetTimestampNs()); } } // namespace statsd diff --git a/cmds/statsd/tests/metrics/DurationMetricProducer_test.cpp b/cmds/statsd/tests/metrics/DurationMetricProducer_test.cpp index 58a4ac6afd51..a4213dec7c44 100644 --- a/cmds/statsd/tests/metrics/DurationMetricProducer_test.cpp +++ b/cmds/statsd/tests/metrics/DurationMetricProducer_test.cpp @@ -36,7 +36,7 @@ namespace android { namespace os { namespace statsd { -const ConfigKey kConfigKey(0, "test"); +const ConfigKey kConfigKey(0, 12345); TEST(DurationMetricTrackerTest, TestNoCondition) { sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); @@ -44,7 +44,7 @@ TEST(DurationMetricTrackerTest, TestNoCondition) { uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; DurationMetric metric; - metric.set_name("1"); + metric.set_id(1); metric.mutable_bucket()->set_bucket_size_millis(bucketSizeNs / 1000000); metric.set_aggregation_type(DurationMetric_AggregationType_SUM); @@ -52,9 +52,10 @@ TEST(DurationMetricTrackerTest, TestNoCondition) { LogEvent event1(tagId, bucketStartTimeNs + 1); LogEvent event2(tagId, bucketStartTimeNs + bucketSizeNs + 2); + FieldMatcher dimensions; DurationMetricProducer durationProducer( kConfigKey, metric, -1 /*no condition*/, 1 /* start index */, 2 /* stop index */, - 3 /* stop_all index */, false /*nesting*/, wizard, {}, bucketStartTimeNs); + 3 /* stop_all index */, false /*nesting*/, wizard, dimensions, bucketStartTimeNs); durationProducer.onMatchedLogEvent(1 /* start index*/, event1); durationProducer.onMatchedLogEvent(2 /* stop index*/, event2); @@ -78,7 +79,7 @@ TEST(DurationMetricTrackerTest, TestNonSlicedCondition) { uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; DurationMetric metric; - metric.set_name("1"); + metric.set_id(1); metric.mutable_bucket()->set_bucket_size_millis(bucketSizeNs / 1000000); metric.set_aggregation_type(DurationMetric_AggregationType_SUM); @@ -88,9 +89,10 @@ TEST(DurationMetricTrackerTest, TestNonSlicedCondition) { LogEvent event3(tagId, bucketStartTimeNs + bucketSizeNs + 1); LogEvent event4(tagId, bucketStartTimeNs + bucketSizeNs + 3); + FieldMatcher dimensions; DurationMetricProducer durationProducer( kConfigKey, metric, 0 /* condition index */, 1 /* start index */, 2 /* stop index */, - 3 /* stop_all index */, false /*nesting*/, wizard, {}, bucketStartTimeNs); + 3 /* stop_all index */, false /*nesting*/, wizard, dimensions, bucketStartTimeNs); EXPECT_FALSE(durationProducer.mCondition); EXPECT_FALSE(durationProducer.isConditionSliced()); diff --git a/cmds/statsd/tests/metrics/EventMetricProducer_test.cpp b/cmds/statsd/tests/metrics/EventMetricProducer_test.cpp index baaac67f395b..7171de939c62 100644 --- a/cmds/statsd/tests/metrics/EventMetricProducer_test.cpp +++ b/cmds/statsd/tests/metrics/EventMetricProducer_test.cpp @@ -13,7 +13,9 @@ // limitations under the License. #include "src/metrics/EventMetricProducer.h" +#include "src/dimension.h" #include "metrics_test_helper.h" +#include "tests/statsd_test_util.h" #include <gmock/gmock.h> #include <gtest/gtest.h> @@ -32,7 +34,7 @@ namespace android { namespace os { namespace statsd { -const ConfigKey kConfigKey(0, "test"); +const ConfigKey kConfigKey(0, 12345); TEST(EventMetricProducerTest, TestNoCondition) { uint64_t bucketStartTimeNs = 10000000000; @@ -40,7 +42,7 @@ TEST(EventMetricProducerTest, TestNoCondition) { uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; EventMetric metric; - metric.set_name("1"); + metric.set_id(1); LogEvent event1(1 /*tag id*/, bucketStartTimeNs + 1); LogEvent event2(1 /*tag id*/, bucketStartTimeNs + 2); @@ -63,8 +65,8 @@ TEST(EventMetricProducerTest, TestEventsWithNonSlicedCondition) { uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; EventMetric metric; - metric.set_name("1"); - metric.set_condition("SCREEN_ON"); + metric.set_id(1); + metric.set_condition(StringToId("SCREEN_ON")); LogEvent event1(1, bucketStartTimeNs + 1); LogEvent event2(1, bucketStartTimeNs + 10); @@ -88,25 +90,28 @@ TEST(EventMetricProducerTest, TestEventsWithSlicedCondition) { uint64_t bucketStartTimeNs = 10000000000; uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; + int tagId = 1; + int conditionTagId = 2; + EventMetric metric; - metric.set_name("1"); - metric.set_condition("APP_IN_BACKGROUND_PER_UID_AND_SCREEN_ON"); + metric.set_id(1); + metric.set_condition(StringToId("APP_IN_BACKGROUND_PER_UID_AND_SCREEN_ON")); MetricConditionLink* link = metric.add_links(); - link->set_condition("APP_IN_BACKGROUND_PER_UID"); - link->add_key_in_what()->set_key(1); - link->add_key_in_condition()->set_key(2); + link->set_condition(StringToId("APP_IN_BACKGROUND_PER_UID")); + *link->mutable_dimensions_in_what() = buildSimpleAtomFieldMatcher(tagId, 1); + *link->mutable_dimensions_in_condition() = buildSimpleAtomFieldMatcher(conditionTagId, 2); - LogEvent event1(1, bucketStartTimeNs + 1); - event1.write("111"); // uid + LogEvent event1(tagId, bucketStartTimeNs + 1); + EXPECT_TRUE(event1.write("111")); event1.init(); ConditionKey key1; - key1["APP_IN_BACKGROUND_PER_UID"] = getMockedDimensionKey(2, "111"); + key1[StringToId("APP_IN_BACKGROUND_PER_UID")] = {getMockedDimensionKey(conditionTagId, 2, "111")}; - LogEvent event2(1, bucketStartTimeNs + 10); - event2.write("222"); // uid + LogEvent event2(tagId, bucketStartTimeNs + 10); + EXPECT_TRUE(event2.write("222")); event2.init(); ConditionKey key2; - key2["APP_IN_BACKGROUND_PER_UID"] = getMockedDimensionKey(2, "222"); + key2[StringToId("APP_IN_BACKGROUND_PER_UID")] = {getMockedDimensionKey(conditionTagId, 2, "222")}; sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); EXPECT_CALL(*wizard, query(_, key1)).WillOnce(Return(ConditionState::kFalse)); diff --git a/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp b/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp index 5204834634ce..cde50c100ac9 100644 --- a/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp +++ b/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp @@ -15,6 +15,7 @@ #include "src/metrics/GaugeMetricProducer.h" #include "logd/LogEvent.h" #include "metrics_test_helper.h" +#include "tests/statsd_test_util.h" #include <gmock/gmock.h> #include <gtest/gtest.h> @@ -34,9 +35,9 @@ namespace android { namespace os { namespace statsd { -const ConfigKey kConfigKey(0, "test"); +const ConfigKey kConfigKey(0, 12345); const int tagId = 1; -const string metricName = "test_metric"; +const int64_t metricId = 123; const int64_t bucketStartTimeNs = 10000000000; const int64_t bucketSizeNs = 60 * 1000 * 1000 * 1000LL; const int64_t bucket2StartTimeNs = bucketStartTimeNs + bucketSizeNs; @@ -45,9 +46,13 @@ const int64_t bucket4StartTimeNs = bucketStartTimeNs + 3 * bucketSizeNs; TEST(GaugeMetricProducerTest, TestNoCondition) { GaugeMetric metric; - metric.set_name(metricName); + metric.set_id(metricId); metric.mutable_bucket()->set_bucket_size_millis(bucketSizeNs / 1000000); - metric.mutable_gauge_fields()->add_field_num(2); + metric.mutable_gauge_fields_filter()->set_include_all(false); + auto gaugeFieldMatcher = metric.mutable_gauge_fields_filter()->mutable_fields(); + gaugeFieldMatcher->set_field(tagId); + gaugeFieldMatcher->add_child()->set_field(1); + gaugeFieldMatcher->add_child()->set_field(3); sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); @@ -64,30 +69,41 @@ TEST(GaugeMetricProducerTest, TestNoCondition) { vector<shared_ptr<LogEvent>> allData; allData.clear(); shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 1); - event->write(tagId); + event->write(10); + event->write("some value"); event->write(11); event->init(); allData.push_back(event); gaugeProducer.onDataPulled(allData); EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size()); - EXPECT_EQ(11, gaugeProducer.mCurrentSlicedBucket->begin()->second->kv[0].value_int()); + auto it = gaugeProducer.mCurrentSlicedBucket->begin()->second->begin(); + EXPECT_EQ(10, it->second.value_int()); + it++; + EXPECT_EQ(11, it->second.value_int()); EXPECT_EQ(0UL, gaugeProducer.mPastBuckets.size()); allData.clear(); std::shared_ptr<LogEvent> event2 = std::make_shared<LogEvent>(tagId, bucket3StartTimeNs + 10); - event2->write(tagId); + event2->write(24); + event2->write("some value"); event2->write(25); event2->init(); allData.push_back(event2); gaugeProducer.onDataPulled(allData); EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size()); - EXPECT_EQ(25, gaugeProducer.mCurrentSlicedBucket->begin()->second->kv[0].value_int()); + it = gaugeProducer.mCurrentSlicedBucket->begin()->second->begin(); + EXPECT_EQ(24, it->second.value_int()); + it++; + EXPECT_EQ(25, it->second.value_int()); // One dimension. EXPECT_EQ(1UL, gaugeProducer.mPastBuckets.size()); EXPECT_EQ(1UL, gaugeProducer.mPastBuckets.begin()->second.size()); - EXPECT_EQ(11L, gaugeProducer.mPastBuckets.begin()->second.back().mEvent->kv[0].value_int()); + it = gaugeProducer.mPastBuckets.begin()->second.back().mGaugeFields->begin(); + EXPECT_EQ(10L, it->second.value_int()); + it++; + EXPECT_EQ(11L, it->second.value_int()); EXPECT_EQ(1UL, gaugeProducer.mPastBuckets.begin()->second.back().mBucketNum); gaugeProducer.flushIfNeededLocked(bucket4StartTimeNs); @@ -95,16 +111,21 @@ TEST(GaugeMetricProducerTest, TestNoCondition) { // One dimension. EXPECT_EQ(1UL, gaugeProducer.mPastBuckets.size()); EXPECT_EQ(2UL, gaugeProducer.mPastBuckets.begin()->second.size()); - EXPECT_EQ(25L, gaugeProducer.mPastBuckets.begin()->second.back().mEvent->kv[0].value_int()); + it = gaugeProducer.mPastBuckets.begin()->second.back().mGaugeFields->begin(); + EXPECT_EQ(24L, it->second.value_int()); + it++; + EXPECT_EQ(25L, it->second.value_int()); EXPECT_EQ(2UL, gaugeProducer.mPastBuckets.begin()->second.back().mBucketNum); } TEST(GaugeMetricProducerTest, TestWithCondition) { GaugeMetric metric; - metric.set_name(metricName); + metric.set_id(metricId); metric.mutable_bucket()->set_bucket_size_millis(bucketSizeNs / 1000000); - metric.mutable_gauge_fields()->add_field_num(2); - metric.set_condition("SCREEN_ON"); + auto gaugeFieldMatcher = metric.mutable_gauge_fields_filter()->mutable_fields(); + gaugeFieldMatcher->set_field(tagId); + gaugeFieldMatcher->add_child()->set_field(2); + metric.set_condition(StringToId("SCREEN_ON")); sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); @@ -116,7 +137,7 @@ TEST(GaugeMetricProducerTest, TestWithCondition) { .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { data->clear(); shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 10); - event->write(tagId); + event->write("some value"); event->write(100); event->init(); data->push_back(event); @@ -128,28 +149,32 @@ TEST(GaugeMetricProducerTest, TestWithCondition) { gaugeProducer.onConditionChanged(true, bucketStartTimeNs + 8); EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size()); - EXPECT_EQ(100, gaugeProducer.mCurrentSlicedBucket->begin()->second->kv[0].value_int()); + EXPECT_EQ(100, + gaugeProducer.mCurrentSlicedBucket->begin()->second->begin()->second.value_int()); EXPECT_EQ(0UL, gaugeProducer.mPastBuckets.size()); vector<shared_ptr<LogEvent>> allData; allData.clear(); shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 1); - event->write(1); + event->write("some value"); event->write(110); event->init(); allData.push_back(event); gaugeProducer.onDataPulled(allData); EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size()); - EXPECT_EQ(110, gaugeProducer.mCurrentSlicedBucket->begin()->second->kv[0].value_int()); + EXPECT_EQ(110, + gaugeProducer.mCurrentSlicedBucket->begin()->second->begin()->second.value_int()); EXPECT_EQ(1UL, gaugeProducer.mPastBuckets.size()); - EXPECT_EQ(100, gaugeProducer.mPastBuckets.begin()->second.back().mEvent->kv[0].value_int()); + EXPECT_EQ(100, gaugeProducer.mPastBuckets.begin()->second.back() + .mGaugeFields->begin()->second.value_int()); gaugeProducer.onConditionChanged(false, bucket2StartTimeNs + 10); gaugeProducer.flushIfNeededLocked(bucket3StartTimeNs + 10); EXPECT_EQ(1UL, gaugeProducer.mPastBuckets.size()); EXPECT_EQ(2UL, gaugeProducer.mPastBuckets.begin()->second.size()); - EXPECT_EQ(110L, gaugeProducer.mPastBuckets.begin()->second.back().mEvent->kv[0].value_int()); + EXPECT_EQ(110L, gaugeProducer.mPastBuckets.begin()->second.back() + .mGaugeFields->begin()->second.value_int()); EXPECT_EQ(1UL, gaugeProducer.mPastBuckets.begin()->second.back().mBucketNum); } @@ -162,61 +187,66 @@ TEST(GaugeMetricProducerTest, TestAnomalyDetection) { EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, _)).WillOnce(Return()); GaugeMetric metric; - metric.set_name(metricName); + metric.set_id(metricId); metric.mutable_bucket()->set_bucket_size_millis(bucketSizeNs / 1000000); - metric.mutable_gauge_fields()->add_field_num(2); + auto gaugeFieldMatcher = metric.mutable_gauge_fields_filter()->mutable_fields(); + gaugeFieldMatcher->set_field(tagId); + gaugeFieldMatcher->add_child()->set_field(2); GaugeMetricProducer gaugeProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard, tagId, tagId, bucketStartTimeNs, pullerManager); Alert alert; - alert.set_name("alert"); - alert.set_metric_name(metricName); + alert.set_id(101); + alert.set_metric_id(metricId); alert.set_trigger_if_sum_gt(25); alert.set_number_of_buckets(2); - sp<AnomalyTracker> anomalyTracker = new AnomalyTracker(alert, kConfigKey); - gaugeProducer.addAnomalyTracker(anomalyTracker); + sp<AnomalyTracker> anomalyTracker = gaugeProducer.addAnomalyTracker(alert); - std::shared_ptr<LogEvent> event1 = std::make_shared<LogEvent>(1, bucketStartTimeNs + 1); - event1->write(1); + int tagId = 1; + std::shared_ptr<LogEvent> event1 = std::make_shared<LogEvent>(tagId, bucketStartTimeNs + 1); + event1->write("some value"); event1->write(13); event1->init(); gaugeProducer.onDataPulled({event1}); EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size()); - EXPECT_EQ(13L, gaugeProducer.mCurrentSlicedBucket->begin()->second->kv[0].value_int()); - EXPECT_EQ(anomalyTracker->getLastAlarmTimestampNs(), -1LL); + EXPECT_EQ(13L, + gaugeProducer.mCurrentSlicedBucket->begin()->second->begin()->second.value_int()); + EXPECT_EQ(anomalyTracker->getLastAnomalyTimestampNs(), -1LL); std::shared_ptr<LogEvent> event2 = - std::make_shared<LogEvent>(1, bucketStartTimeNs + bucketSizeNs + 10); - event2->write(1); + std::make_shared<LogEvent>(tagId, bucketStartTimeNs + bucketSizeNs + 10); + event2->write("some value"); event2->write(15); event2->init(); gaugeProducer.onDataPulled({event2}); EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size()); - EXPECT_EQ(15L, gaugeProducer.mCurrentSlicedBucket->begin()->second->kv[0].value_int()); - EXPECT_EQ(anomalyTracker->getLastAlarmTimestampNs(), (long long)event2->GetTimestampNs()); + EXPECT_EQ(15L, + gaugeProducer.mCurrentSlicedBucket->begin()->second->begin()->second.value_int()); + EXPECT_EQ(anomalyTracker->getLastAnomalyTimestampNs(), (long long)event2->GetTimestampNs()); std::shared_ptr<LogEvent> event3 = - std::make_shared<LogEvent>(1, bucketStartTimeNs + 2 * bucketSizeNs + 10); - event3->write(1); + std::make_shared<LogEvent>(tagId, bucketStartTimeNs + 2 * bucketSizeNs + 10); + event3->write("some value"); event3->write(24); event3->init(); gaugeProducer.onDataPulled({event3}); EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size()); - EXPECT_EQ(24L, gaugeProducer.mCurrentSlicedBucket->begin()->second->kv[0].value_int()); - EXPECT_EQ(anomalyTracker->getLastAlarmTimestampNs(), (long long)event3->GetTimestampNs()); + EXPECT_EQ(24L, + gaugeProducer.mCurrentSlicedBucket->begin()->second->begin()->second.value_int()); + EXPECT_EQ(anomalyTracker->getLastAnomalyTimestampNs(), (long long)event3->GetTimestampNs()); // The event4 does not have the gauge field. Thus the current bucket value is 0. std::shared_ptr<LogEvent> event4 = - std::make_shared<LogEvent>(1, bucketStartTimeNs + 3 * bucketSizeNs + 10); - event4->write(1); + std::make_shared<LogEvent>(tagId, bucketStartTimeNs + 3 * bucketSizeNs + 10); + event4->write("some value"); event4->init(); gaugeProducer.onDataPulled({event4}); EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size()); - EXPECT_EQ(0, gaugeProducer.mCurrentSlicedBucket->begin()->second->kv[0].value_int()); - EXPECT_EQ(anomalyTracker->getLastAlarmTimestampNs(), (long long)event3->GetTimestampNs()); + EXPECT_TRUE(gaugeProducer.mCurrentSlicedBucket->begin()->second->empty()); + EXPECT_EQ(anomalyTracker->getLastAnomalyTimestampNs(), (long long)event3->GetTimestampNs()); } } // namespace statsd diff --git a/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp b/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp index 7dac0fbb38a0..6d323291e8ad 100644 --- a/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp +++ b/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp @@ -13,8 +13,9 @@ // limitations under the License. #include "src/metrics/duration_helper/MaxDurationTracker.h" -#include "metrics_test_helper.h" #include "src/condition/ConditionWizard.h" +#include "metrics_test_helper.h" +#include "tests/statsd_test_util.h" #include <gmock/gmock.h> #include <gtest/gtest.h> @@ -36,24 +37,27 @@ namespace android { namespace os { namespace statsd { -const ConfigKey kConfigKey(0, "test"); +const ConfigKey kConfigKey(0, 12345); + +const int TagId = 1; -const HashableDimensionKey eventKey = getMockedDimensionKey(0, "1"); -const HashableDimensionKey conditionKey = getMockedDimensionKey(4, "1"); -const HashableDimensionKey key1 = getMockedDimensionKey(1, "1"); -const HashableDimensionKey key2 = getMockedDimensionKey(1, "2"); +const HashableDimensionKey eventKey = getMockedDimensionKey(TagId, 0, "1"); +const std::vector<HashableDimensionKey> conditionKey = {getMockedDimensionKey(TagId, 4, "1")}; +const HashableDimensionKey key1 = getMockedDimensionKey(TagId, 1, "1"); +const HashableDimensionKey key2 = getMockedDimensionKey(TagId, 1, "2"); TEST(MaxDurationTrackerTest, TestSimpleMaxDuration) { sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets; ConditionKey conditionKey1; - conditionKey1["condition"] = conditionKey; + conditionKey1[StringToId("condition")] = conditionKey; uint64_t bucketStartTimeNs = 10000000000; uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; - MaxDurationTracker tracker(kConfigKey, "metric", eventKey, wizard, -1, false, bucketStartTimeNs, + int64_t metricId = 1; + MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, false, bucketStartTimeNs, bucketSizeNs, {}); tracker.noteStart(key1, true, bucketStartTimeNs, conditionKey1); @@ -77,12 +81,13 @@ TEST(MaxDurationTrackerTest, TestStopAll) { unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets; ConditionKey conditionKey1; - conditionKey1["condition"] = conditionKey; + conditionKey1[StringToId("condition")] = conditionKey; uint64_t bucketStartTimeNs = 10000000000; uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; - MaxDurationTracker tracker(kConfigKey, "metric", eventKey, wizard, -1, false, bucketStartTimeNs, + int64_t metricId = 1; + MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, false, bucketStartTimeNs, bucketSizeNs, {}); tracker.noteStart(key1, true, bucketStartTimeNs + 1, conditionKey1); @@ -108,12 +113,13 @@ TEST(MaxDurationTrackerTest, TestCrossBucketBoundary) { unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets; ConditionKey conditionKey1; - conditionKey1["condition"] = conditionKey; + conditionKey1[StringToId("condition")] = conditionKey; uint64_t bucketStartTimeNs = 10000000000; uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; - MaxDurationTracker tracker(kConfigKey, "metric", eventKey, wizard, -1, false, bucketStartTimeNs, + int64_t metricId = 1; + MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, false, bucketStartTimeNs, bucketSizeNs, {}); // The event starts. @@ -139,12 +145,13 @@ TEST(MaxDurationTrackerTest, TestCrossBucketBoundary_nested) { unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets; ConditionKey conditionKey1; - conditionKey1["condition"] = conditionKey; + conditionKey1[StringToId("condition")] = conditionKey; uint64_t bucketStartTimeNs = 10000000000; uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; - MaxDurationTracker tracker(kConfigKey, "metric", eventKey, wizard, -1, true, bucketStartTimeNs, + int64_t metricId = 1; + MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, true, bucketStartTimeNs, bucketSizeNs, {}); // 2 starts @@ -174,8 +181,8 @@ TEST(MaxDurationTrackerTest, TestMaxDurationWithCondition) { sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); ConditionKey conditionKey1; - HashableDimensionKey eventKey = getMockedDimensionKey(2, "maps"); - conditionKey1["APP_BACKGROUND"] = conditionKey; + HashableDimensionKey eventKey = getMockedDimensionKey(TagId, 2, "maps"); + conditionKey1[StringToId("APP_BACKGROUND")] = conditionKey; EXPECT_CALL(*wizard, query(_, conditionKey1)) // #4 .WillOnce(Return(ConditionState::kFalse)); @@ -187,7 +194,8 @@ TEST(MaxDurationTrackerTest, TestMaxDurationWithCondition) { uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; int64_t durationTimeNs = 2 * 1000; - MaxDurationTracker tracker(kConfigKey, "metric", eventKey, wizard, 1, false, bucketStartTimeNs, + int64_t metricId = 1; + MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false, bucketStartTimeNs, bucketSizeNs, {}); EXPECT_TRUE(tracker.mAnomalyTrackers.empty()); @@ -204,9 +212,10 @@ TEST(MaxDurationTrackerTest, TestMaxDurationWithCondition) { } TEST(MaxDurationTrackerTest, TestAnomalyDetection) { + int64_t metricId = 1; Alert alert; - alert.set_name("alert"); - alert.set_metric_name("metric"); + alert.set_id(101); + alert.set_metric_id(metricId); alert.set_trigger_if_sum_gt(32 * NS_PER_SEC); alert.set_number_of_buckets(2); alert.set_refractory_period_secs(1); @@ -214,25 +223,25 @@ TEST(MaxDurationTrackerTest, TestAnomalyDetection) { unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets; sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); ConditionKey conditionKey1; - conditionKey1["APP_BACKGROUND"] = conditionKey; + conditionKey1[StringToId("APP_BACKGROUND")] = conditionKey; uint64_t bucketStartTimeNs = 10 * NS_PER_SEC; uint64_t eventStartTimeNs = bucketStartTimeNs + NS_PER_SEC + 1; uint64_t bucketSizeNs = 30 * NS_PER_SEC; - sp<AnomalyTracker> anomalyTracker = new AnomalyTracker(alert, kConfigKey); - MaxDurationTracker tracker(kConfigKey, "metric", eventKey, wizard, -1, true, bucketStartTimeNs, + sp<DurationAnomalyTracker> anomalyTracker = new DurationAnomalyTracker(alert, kConfigKey); + MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, true, bucketStartTimeNs, bucketSizeNs, {anomalyTracker}); tracker.noteStart(key1, true, eventStartTimeNs, conditionKey1); tracker.noteStop(key1, eventStartTimeNs + 10, false); - EXPECT_EQ(anomalyTracker->mLastAlarmTimestampNs, -1); + EXPECT_EQ(anomalyTracker->mLastAnomalyTimestampNs, -1); EXPECT_EQ(10LL, tracker.mDuration); tracker.noteStart(key2, true, eventStartTimeNs + 20, conditionKey1); tracker.flushIfNeeded(eventStartTimeNs + 2 * bucketSizeNs + 3 * NS_PER_SEC, &buckets); tracker.noteStop(key2, eventStartTimeNs + 2 * bucketSizeNs + 3 * NS_PER_SEC, false); EXPECT_EQ((long long)(4 * NS_PER_SEC + 1LL), tracker.mDuration); - EXPECT_EQ(anomalyTracker->mLastAlarmTimestampNs, + EXPECT_EQ(anomalyTracker->mLastAnomalyTimestampNs, (long long)(eventStartTimeNs + 2 * bucketSizeNs + 3 * NS_PER_SEC)); } diff --git a/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp b/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp index 9ec302fbfd7d..d34c85b92d86 100644 --- a/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp +++ b/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp @@ -13,8 +13,9 @@ // limitations under the License. #include "src/metrics/duration_helper/OringDurationTracker.h" -#include "metrics_test_helper.h" #include "src/condition/ConditionWizard.h" +#include "metrics_test_helper.h" +#include "tests/statsd_test_util.h" #include <gmock/gmock.h> #include <gtest/gtest.h> @@ -34,18 +35,20 @@ namespace android { namespace os { namespace statsd { -const ConfigKey kConfigKey(0, "test"); -const HashableDimensionKey eventKey = getMockedDimensionKey(0, "event"); +const ConfigKey kConfigKey(0, 12345); +const int TagId = 1; +const int64_t metricId = 123; +const HashableDimensionKey eventKey = getMockedDimensionKey(TagId, 0, "event"); -const HashableDimensionKey kConditionKey1 = getMockedDimensionKey(1, "maps"); -const HashableDimensionKey kEventKey1 = getMockedDimensionKey(2, "maps"); -const HashableDimensionKey kEventKey2 = getMockedDimensionKey(3, "maps"); +const std::vector<HashableDimensionKey> kConditionKey1 = {getMockedDimensionKey(TagId, 1, "maps")}; +const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps"); +const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps"); TEST(OringDurationTrackerTest, TestDurationOverlap) { sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); ConditionKey key1; - key1["APP_BACKGROUND"] = kConditionKey1; + key1[StringToId("APP_BACKGROUND")] = kConditionKey1; unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets; @@ -54,7 +57,7 @@ TEST(OringDurationTrackerTest, TestDurationOverlap) { uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; uint64_t durationTimeNs = 2 * 1000; - OringDurationTracker tracker(kConfigKey, "metric", eventKey, wizard, 1, false, + OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false, bucketStartTimeNs, bucketSizeNs, {}); tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1); @@ -74,7 +77,7 @@ TEST(OringDurationTrackerTest, TestDurationNested) { sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); ConditionKey key1; - key1["APP_BACKGROUND"] = kConditionKey1; + key1[StringToId("APP_BACKGROUND")] = kConditionKey1; unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets; @@ -82,7 +85,7 @@ TEST(OringDurationTrackerTest, TestDurationNested) { uint64_t eventStartTimeNs = bucketStartTimeNs + 1; uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; - OringDurationTracker tracker(kConfigKey, "metric", eventKey, wizard, 1, true, bucketStartTimeNs, + OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true, bucketStartTimeNs, bucketSizeNs, {}); tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1); @@ -101,7 +104,7 @@ TEST(OringDurationTrackerTest, TestStopAll) { sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); ConditionKey key1; - key1["APP_BACKGROUND"] = kConditionKey1; + key1[StringToId("APP_BACKGROUND")] = kConditionKey1; unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets; @@ -109,7 +112,7 @@ TEST(OringDurationTrackerTest, TestStopAll) { uint64_t eventStartTimeNs = bucketStartTimeNs + 1; uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; - OringDurationTracker tracker(kConfigKey, "metric", eventKey, wizard, 1, true, bucketStartTimeNs, + OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true, bucketStartTimeNs, bucketSizeNs, {}); tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1); @@ -127,7 +130,7 @@ TEST(OringDurationTrackerTest, TestCrossBucketBoundary) { sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); ConditionKey key1; - key1["APP_BACKGROUND"] = kConditionKey1; + key1[StringToId("APP_BACKGROUND")] = kConditionKey1; unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets; @@ -136,7 +139,7 @@ TEST(OringDurationTrackerTest, TestCrossBucketBoundary) { uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; uint64_t durationTimeNs = 2 * 1000; - OringDurationTracker tracker(kConfigKey, "metric", eventKey, wizard, 1, true, bucketStartTimeNs, + OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true, bucketStartTimeNs, bucketSizeNs, {}); tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1); @@ -162,7 +165,7 @@ TEST(OringDurationTrackerTest, TestDurationConditionChange) { sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); ConditionKey key1; - key1["APP_BACKGROUND"] = kConditionKey1; + key1[StringToId("APP_BACKGROUND")] = kConditionKey1; EXPECT_CALL(*wizard, query(_, key1)) // #4 .WillOnce(Return(ConditionState::kFalse)); @@ -174,7 +177,7 @@ TEST(OringDurationTrackerTest, TestDurationConditionChange) { uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; uint64_t durationTimeNs = 2 * 1000; - OringDurationTracker tracker(kConfigKey, "metric", eventKey, wizard, 1, false, + OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false, bucketStartTimeNs, bucketSizeNs, {}); tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1); @@ -193,7 +196,7 @@ TEST(OringDurationTrackerTest, TestDurationConditionChange2) { sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); ConditionKey key1; - key1["APP_BACKGROUND"] = kConditionKey1; + key1[StringToId("APP_BACKGROUND")] = kConditionKey1; EXPECT_CALL(*wizard, query(_, key1)) .Times(2) @@ -207,7 +210,7 @@ TEST(OringDurationTrackerTest, TestDurationConditionChange2) { uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; uint64_t durationTimeNs = 2 * 1000; - OringDurationTracker tracker(kConfigKey, "metric", eventKey, wizard, 1, false, + OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false, bucketStartTimeNs, bucketSizeNs, {}); tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1); @@ -228,7 +231,7 @@ TEST(OringDurationTrackerTest, TestDurationConditionChangeNested) { sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); ConditionKey key1; - key1["APP_BACKGROUND"] = kConditionKey1; + key1[StringToId("APP_BACKGROUND")] = kConditionKey1; EXPECT_CALL(*wizard, query(_, key1)) // #4 .WillOnce(Return(ConditionState::kFalse)); @@ -239,7 +242,7 @@ TEST(OringDurationTrackerTest, TestDurationConditionChangeNested) { uint64_t eventStartTimeNs = bucketStartTimeNs + 1; uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; - OringDurationTracker tracker(kConfigKey, "metric", eventKey, wizard, 1, true, bucketStartTimeNs, + OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true, bucketStartTimeNs, bucketSizeNs, {}); tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1); @@ -259,8 +262,8 @@ TEST(OringDurationTrackerTest, TestDurationConditionChangeNested) { TEST(OringDurationTrackerTest, TestPredictAnomalyTimestamp) { Alert alert; - alert.set_name("alert"); - alert.set_metric_name("1"); + alert.set_id(101); + alert.set_metric_id(1); alert.set_trigger_if_sum_gt(40 * NS_PER_SEC); alert.set_number_of_buckets(2); alert.set_refractory_period_secs(1); @@ -268,13 +271,13 @@ TEST(OringDurationTrackerTest, TestPredictAnomalyTimestamp) { unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets; sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); ConditionKey key1; - key1["APP_BACKGROUND"] = kConditionKey1; + key1[StringToId("APP_BACKGROUND")] = kConditionKey1; uint64_t bucketStartTimeNs = 10 * NS_PER_SEC; uint64_t eventStartTimeNs = bucketStartTimeNs + NS_PER_SEC + 1; uint64_t bucketSizeNs = 30 * NS_PER_SEC; - sp<AnomalyTracker> anomalyTracker = new AnomalyTracker(alert, kConfigKey); - OringDurationTracker tracker(kConfigKey, "metric", eventKey, wizard, 1, true, bucketStartTimeNs, + sp<DurationAnomalyTracker> anomalyTracker = new DurationAnomalyTracker(alert, kConfigKey); + OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true, bucketStartTimeNs, bucketSizeNs, {anomalyTracker}); // Nothing in the past bucket. @@ -321,8 +324,8 @@ TEST(OringDurationTrackerTest, TestPredictAnomalyTimestamp) { TEST(OringDurationTrackerTest, TestAnomalyDetection) { Alert alert; - alert.set_name("alert"); - alert.set_metric_name("1"); + alert.set_id(101); + alert.set_metric_id(1); alert.set_trigger_if_sum_gt(40 * NS_PER_SEC); alert.set_number_of_buckets(2); alert.set_refractory_period_secs(1); @@ -330,18 +333,18 @@ TEST(OringDurationTrackerTest, TestAnomalyDetection) { unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets; sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); ConditionKey key1; - key1["APP_BACKGROUND"] = kConditionKey1; + key1[StringToId("APP_BACKGROUND")] = kConditionKey1; uint64_t bucketStartTimeNs = 10 * NS_PER_SEC; uint64_t eventStartTimeNs = bucketStartTimeNs + NS_PER_SEC + 1; uint64_t bucketSizeNs = 30 * NS_PER_SEC; - sp<AnomalyTracker> anomalyTracker = new AnomalyTracker(alert, kConfigKey); - OringDurationTracker tracker(kConfigKey, "metric", eventKey, wizard, 1, true /*nesting*/, + sp<DurationAnomalyTracker> anomalyTracker = new DurationAnomalyTracker(alert, kConfigKey); + OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true /*nesting*/, bucketStartTimeNs, bucketSizeNs, {anomalyTracker}); tracker.noteStart(DEFAULT_DIMENSION_KEY, true, eventStartTimeNs, key1); tracker.noteStop(DEFAULT_DIMENSION_KEY, eventStartTimeNs + 10, false); - EXPECT_EQ(anomalyTracker->mLastAlarmTimestampNs, -1); + EXPECT_EQ(anomalyTracker->mLastAnomalyTimestampNs, -1); EXPECT_TRUE(tracker.mStarted.empty()); EXPECT_EQ(10LL, tracker.mDuration); @@ -355,7 +358,7 @@ TEST(OringDurationTrackerTest, TestAnomalyDetection) { tracker.noteStop(DEFAULT_DIMENSION_KEY, eventStartTimeNs + 2 * bucketSizeNs + 25, false); EXPECT_EQ(anomalyTracker->getSumOverPastBuckets(eventKey), (long long)(bucketSizeNs)); EXPECT_EQ((long long)(eventStartTimeNs + 2 * bucketSizeNs + 25), - anomalyTracker->mLastAlarmTimestampNs); + anomalyTracker->mLastAnomalyTimestampNs); } } // namespace statsd diff --git a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp index 6f117d3d76d3..acbfbbac25e1 100644 --- a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp +++ b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp @@ -14,6 +14,7 @@ #include "src/metrics/ValueMetricProducer.h" #include "metrics_test_helper.h" +#include "tests/statsd_test_util.h" #include <gmock/gmock.h> #include <gtest/gtest.h> @@ -34,9 +35,9 @@ namespace android { namespace os { namespace statsd { -const ConfigKey kConfigKey(0, "test"); +const ConfigKey kConfigKey(0, 12345); const int tagId = 1; -const string metricName = "test_metric"; +const int64_t metricId = 123; const int64_t bucketStartTimeNs = 10000000000; const int64_t bucketSizeNs = 60 * 1000 * 1000 * 1000LL; const int64_t bucket2StartTimeNs = bucketStartTimeNs + bucketSizeNs; @@ -48,7 +49,7 @@ const int64_t bucket4StartTimeNs = bucketStartTimeNs + 3 * bucketSizeNs; */ TEST(ValueMetricProducerTest, TestNonDimensionalEvents) { ValueMetric metric; - metric.set_name(metricName); + metric.set_id(metricId); metric.mutable_bucket()->set_bucket_size_millis(bucketSizeNs / 1000000); metric.set_value_field(2); @@ -124,10 +125,10 @@ TEST(ValueMetricProducerTest, TestNonDimensionalEvents) { */ TEST(ValueMetricProducerTest, TestEventsWithNonSlicedCondition) { ValueMetric metric; - metric.set_name(metricName); + metric.set_id(metricId); metric.mutable_bucket()->set_bucket_size_millis(bucketSizeNs / 1000000); metric.set_value_field(2); - metric.set_condition("SCREEN_ON"); + metric.set_condition(StringToId("SCREEN_ON")); sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); shared_ptr<MockStatsPullerManager> pullerManager = @@ -200,7 +201,7 @@ TEST(ValueMetricProducerTest, TestEventsWithNonSlicedCondition) { TEST(ValueMetricProducerTest, TestPushedEventsWithoutCondition) { ValueMetric metric; - metric.set_name(metricName); + metric.set_id(metricId); metric.mutable_bucket()->set_bucket_size_millis(bucketSizeNs / 1000000); metric.set_value_field(2); @@ -240,22 +241,21 @@ TEST(ValueMetricProducerTest, TestPushedEventsWithoutCondition) { TEST(ValueMetricProducerTest, TestAnomalyDetection) { Alert alert; - alert.set_name("alert"); - alert.set_metric_name(metricName); + alert.set_id(101); + alert.set_metric_id(metricId); alert.set_trigger_if_sum_gt(130); alert.set_number_of_buckets(2); alert.set_refractory_period_secs(3); - sp<AnomalyTracker> anomalyTracker = new AnomalyTracker(alert, kConfigKey); ValueMetric metric; - metric.set_name(metricName); + metric.set_id(metricId); metric.mutable_bucket()->set_bucket_size_millis(bucketSizeNs / 1000000); metric.set_value_field(2); sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); ValueMetricProducer valueProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard, -1 /*not pulled*/, bucketStartTimeNs); - valueProducer.addAnomalyTracker(anomalyTracker); + sp<AnomalyTracker> anomalyTracker = valueProducer.addAnomalyTracker(alert); shared_ptr<LogEvent> event1 @@ -292,23 +292,23 @@ TEST(ValueMetricProducerTest, TestAnomalyDetection) { // Two events in bucket #0. valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event1); valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event2); - EXPECT_EQ(anomalyTracker->getLastAlarmTimestampNs(), -1LL); // Value sum == 30 <= 130. + EXPECT_EQ(anomalyTracker->getLastAnomalyTimestampNs(), -1LL); // Value sum == 30 <= 130. // One event in bucket #2. No alarm as bucket #0 is trashed out. valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event3); - EXPECT_EQ(anomalyTracker->getLastAlarmTimestampNs(), -1LL); // Value sum == 130 <= 130. + EXPECT_EQ(anomalyTracker->getLastAnomalyTimestampNs(), -1LL); // Value sum == 130 <= 130. // Three events in bucket #3. valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event4); // Anomaly at event 4 since Value sum == 131 > 130! - EXPECT_EQ(anomalyTracker->getLastAlarmTimestampNs(), (long long)event4->GetTimestampNs()); + EXPECT_EQ(anomalyTracker->getLastAnomalyTimestampNs(), (long long)event4->GetTimestampNs()); valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event5); // Event 5 is within 3 sec refractory period. Thus last alarm timestamp is still event4. - EXPECT_EQ(anomalyTracker->getLastAlarmTimestampNs(), (long long)event4->GetTimestampNs()); + EXPECT_EQ(anomalyTracker->getLastAnomalyTimestampNs(), (long long)event4->GetTimestampNs()); valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event6); // Anomaly at event 6 since Value sum == 160 > 130 and after refractory period. - EXPECT_EQ(anomalyTracker->getLastAlarmTimestampNs(), (long long)event6->GetTimestampNs()); + EXPECT_EQ(anomalyTracker->getLastAnomalyTimestampNs(), (long long)event6->GetTimestampNs()); } } // namespace statsd diff --git a/cmds/statsd/tests/metrics/metrics_test_helper.cpp b/cmds/statsd/tests/metrics/metrics_test_helper.cpp index a0a854a28d36..fc7245ca54e3 100644 --- a/cmds/statsd/tests/metrics/metrics_test_helper.cpp +++ b/cmds/statsd/tests/metrics/metrics_test_helper.cpp @@ -18,15 +18,12 @@ namespace android { namespace os { namespace statsd { -HashableDimensionKey getMockedDimensionKey(int key, string value) { - KeyValuePair pair; - pair.set_key(key); - pair.set_value_str(value); - - vector<KeyValuePair> pairs; - pairs.push_back(pair); - - return HashableDimensionKey(pairs); +HashableDimensionKey getMockedDimensionKey(int tagId, int key, string value) { + DimensionsValue dimensionsValue; + dimensionsValue.set_field(tagId); + dimensionsValue.mutable_value_tuple()->add_dimensions_value()->set_field(key); + dimensionsValue.mutable_value_tuple()->mutable_dimensions_value(0)->set_value_str(value); + return HashableDimensionKey(dimensionsValue); } } // namespace statsd diff --git a/cmds/statsd/tests/metrics/metrics_test_helper.h b/cmds/statsd/tests/metrics/metrics_test_helper.h index 7cb332953016..23e86f92f844 100644 --- a/cmds/statsd/tests/metrics/metrics_test_helper.h +++ b/cmds/statsd/tests/metrics/metrics_test_helper.h @@ -28,7 +28,7 @@ public: MOCK_METHOD2( query, ConditionState(const int conditionIndex, - const std::map<std::string, HashableDimensionKey>& conditionParameters)); + const ConditionKey& conditionParameters)); }; class MockStatsPullerManager : public StatsPullerManager { @@ -38,7 +38,7 @@ public: MOCK_METHOD2(Pull, bool(const int pullCode, vector<std::shared_ptr<LogEvent>>* data)); }; -HashableDimensionKey getMockedDimensionKey(int key, std::string value); +HashableDimensionKey getMockedDimensionKey(int tagId, int key, std::string value); } // namespace statsd } // namespace os diff --git a/cmds/statsd/tests/statsd_test_util.cpp b/cmds/statsd/tests/statsd_test_util.cpp new file mode 100644 index 000000000000..939dc1f7c66c --- /dev/null +++ b/cmds/statsd/tests/statsd_test_util.cpp @@ -0,0 +1,324 @@ +// Copyright (C) 2017 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. + +#include <gtest/gtest.h> +#include "statsd_test_util.h" + +namespace android { +namespace os { +namespace statsd { + +AtomMatcher CreateWakelockStateChangedAtomMatcher(const string& name, + WakelockStateChanged::State state) { + AtomMatcher atom_matcher; + atom_matcher.set_id(StringToId(name)); + auto simple_atom_matcher = atom_matcher.mutable_simple_atom_matcher(); + simple_atom_matcher->set_atom_id(android::util::WAKELOCK_STATE_CHANGED); + auto field_value_matcher = simple_atom_matcher->add_field_value_matcher(); + field_value_matcher->set_field(4); // State field. + field_value_matcher->set_eq_int(state); + return atom_matcher; +} + +AtomMatcher CreateAcquireWakelockAtomMatcher() { + return CreateWakelockStateChangedAtomMatcher("AcquireWakelock", WakelockStateChanged::ACQUIRE); +} + +AtomMatcher CreateReleaseWakelockAtomMatcher() { + return CreateWakelockStateChangedAtomMatcher("ReleaseWakelock", WakelockStateChanged::RELEASE); +} + +AtomMatcher CreateScreenStateChangedAtomMatcher( + const string& name, ScreenStateChanged::State state) { + AtomMatcher atom_matcher; + atom_matcher.set_id(StringToId(name)); + auto simple_atom_matcher = atom_matcher.mutable_simple_atom_matcher(); + simple_atom_matcher->set_atom_id(android::util::SCREEN_STATE_CHANGED); + auto field_value_matcher = simple_atom_matcher->add_field_value_matcher(); + field_value_matcher->set_field(1); // State field. + field_value_matcher->set_eq_int(state); + return atom_matcher; +} + +AtomMatcher CreateScreenTurnedOnAtomMatcher() { + return CreateScreenStateChangedAtomMatcher("ScreenTurnedOn", ScreenStateChanged::STATE_ON); +} + +AtomMatcher CreateScreenTurnedOffAtomMatcher() { + return CreateScreenStateChangedAtomMatcher("ScreenTurnedOff", ScreenStateChanged::STATE_OFF); +} + +AtomMatcher CreateSyncStateChangedAtomMatcher( + const string& name, SyncStateChanged::State state) { + AtomMatcher atom_matcher; + atom_matcher.set_id(StringToId(name)); + auto simple_atom_matcher = atom_matcher.mutable_simple_atom_matcher(); + simple_atom_matcher->set_atom_id(android::util::SYNC_STATE_CHANGED); + auto field_value_matcher = simple_atom_matcher->add_field_value_matcher(); + field_value_matcher->set_field(3); // State field. + field_value_matcher->set_eq_int(state); + return atom_matcher; +} + +AtomMatcher CreateSyncStartAtomMatcher() { + return CreateSyncStateChangedAtomMatcher("SyncStart", SyncStateChanged::ON); +} + +AtomMatcher CreateSyncEndAtomMatcher() { + return CreateSyncStateChangedAtomMatcher("SyncEnd", SyncStateChanged::OFF); +} + +AtomMatcher CreateActivityForegroundStateChangedAtomMatcher( + const string& name, ActivityForegroundStateChanged::Activity activity) { + AtomMatcher atom_matcher; + atom_matcher.set_id(StringToId(name)); + auto simple_atom_matcher = atom_matcher.mutable_simple_atom_matcher(); + simple_atom_matcher->set_atom_id(android::util::ACTIVITY_FOREGROUND_STATE_CHANGED); + auto field_value_matcher = simple_atom_matcher->add_field_value_matcher(); + field_value_matcher->set_field(4); // Activity field. + field_value_matcher->set_eq_int(activity); + return atom_matcher; +} + +AtomMatcher CreateMoveToBackgroundAtomMatcher() { + return CreateActivityForegroundStateChangedAtomMatcher( + "MoveToBackground", ActivityForegroundStateChanged::MOVE_TO_BACKGROUND); +} + +AtomMatcher CreateMoveToForegroundAtomMatcher() { + return CreateActivityForegroundStateChangedAtomMatcher( + "MoveToForeground", ActivityForegroundStateChanged::MOVE_TO_FOREGROUND); +} + +AtomMatcher CreateProcessLifeCycleStateChangedAtomMatcher( + const string& name, ProcessLifeCycleStateChanged::Event event) { + AtomMatcher atom_matcher; + atom_matcher.set_id(StringToId(name)); + auto simple_atom_matcher = atom_matcher.mutable_simple_atom_matcher(); + simple_atom_matcher->set_atom_id(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED); + auto field_value_matcher = simple_atom_matcher->add_field_value_matcher(); + field_value_matcher->set_field(3); // Process state field. + field_value_matcher->set_eq_int(event); + return atom_matcher; +} + +AtomMatcher CreateProcessCrashAtomMatcher() { + return CreateProcessLifeCycleStateChangedAtomMatcher( + "ProcessCrashed", ProcessLifeCycleStateChanged::PROCESS_CRASHED); +} + + +Predicate CreateScreenIsOnPredicate() { + Predicate predicate; + predicate.set_id(StringToId("ScreenIsOn")); + predicate.mutable_simple_predicate()->set_start(StringToId("ScreenTurnedOn")); + predicate.mutable_simple_predicate()->set_stop(StringToId("ScreenTurnedOff")); + return predicate; +} + +Predicate CreateScreenIsOffPredicate() { + Predicate predicate; + predicate.set_id(StringToId("ScreenIsOff")); + predicate.mutable_simple_predicate()->set_start(StringToId("ScreenTurnedOff")); + predicate.mutable_simple_predicate()->set_stop(StringToId("ScreenTurnedOn")); + return predicate; +} + +Predicate CreateHoldingWakelockPredicate() { + Predicate predicate; + predicate.set_id(StringToId("HoldingWakelock")); + predicate.mutable_simple_predicate()->set_start(StringToId("AcquireWakelock")); + predicate.mutable_simple_predicate()->set_stop(StringToId("ReleaseWakelock")); + return predicate; +} + +Predicate CreateIsSyncingPredicate() { + Predicate predicate; + predicate.set_id(StringToId("IsSyncing")); + predicate.mutable_simple_predicate()->set_start(StringToId("SyncStart")); + predicate.mutable_simple_predicate()->set_stop(StringToId("SyncEnd")); + return predicate; +} + +Predicate CreateIsInBackgroundPredicate() { + Predicate predicate; + predicate.set_id(StringToId("IsInBackground")); + predicate.mutable_simple_predicate()->set_start(StringToId("MoveToBackground")); + predicate.mutable_simple_predicate()->set_stop(StringToId("MoveToForeground")); + return predicate; +} + +void addPredicateToPredicateCombination(const Predicate& predicate, + Predicate* combinationPredicate) { + combinationPredicate->mutable_combination()->add_predicate(predicate.id()); +} + +FieldMatcher CreateAttributionUidDimensions(const int atomId, + const std::vector<Position>& positions) { + FieldMatcher dimensions; + dimensions.set_field(atomId); + for (const auto position : positions) { + auto child = dimensions.add_child(); + child->set_field(1); + child->set_position(position); + child->add_child()->set_field(1); + } + return dimensions; +} + +FieldMatcher CreateAttributionUidAndTagDimensions(const int atomId, + const std::vector<Position>& positions) { + FieldMatcher dimensions; + dimensions.set_field(atomId); + for (const auto position : positions) { + auto child = dimensions.add_child(); + child->set_field(1); + child->set_position(position); + child->add_child()->set_field(1); + child->add_child()->set_field(2); + } + return dimensions; +} + +FieldMatcher CreateDimensions(const int atomId, const std::vector<int>& fields) { + FieldMatcher dimensions; + dimensions.set_field(atomId); + for (const int field : fields) { + dimensions.add_child()->set_field(field); + } + return dimensions; +} + +std::unique_ptr<LogEvent> CreateScreenStateChangedEvent( + const ScreenStateChanged::State state, uint64_t timestampNs) { + auto event = std::make_unique<LogEvent>(android::util::SCREEN_STATE_CHANGED, timestampNs); + EXPECT_TRUE(event->write(state)); + event->init(); + return event; +} + +std::unique_ptr<LogEvent> CreateWakelockStateChangedEvent( + const std::vector<AttributionNode>& attributions, const string& wakelockName, + const WakelockStateChanged::State state, uint64_t timestampNs) { + auto event = std::make_unique<LogEvent>(android::util::WAKELOCK_STATE_CHANGED, timestampNs); + event->write(attributions); + event->write(WakelockStateChanged::PARTIAL); + event->write(wakelockName); + event->write(state); + event->init(); + return event; +} + +std::unique_ptr<LogEvent> CreateAcquireWakelockEvent( + const std::vector<AttributionNode>& attributions, + const string& wakelockName, uint64_t timestampNs) { + return CreateWakelockStateChangedEvent( + attributions, wakelockName, WakelockStateChanged::ACQUIRE, timestampNs); +} + +std::unique_ptr<LogEvent> CreateReleaseWakelockEvent( + const std::vector<AttributionNode>& attributions, + const string& wakelockName, uint64_t timestampNs) { + return CreateWakelockStateChangedEvent( + attributions, wakelockName, WakelockStateChanged::RELEASE, timestampNs); +} + +std::unique_ptr<LogEvent> CreateActivityForegroundStateChangedEvent( + const int uid, const ActivityForegroundStateChanged::Activity activity, uint64_t timestampNs) { + auto event = std::make_unique<LogEvent>( + android::util::ACTIVITY_FOREGROUND_STATE_CHANGED, timestampNs); + event->write(uid); + event->write("pkg_name"); + event->write("class_name"); + event->write(activity); + event->init(); + return event; +} + +std::unique_ptr<LogEvent> CreateMoveToBackgroundEvent(const int uid, uint64_t timestampNs) { + return CreateActivityForegroundStateChangedEvent( + uid, ActivityForegroundStateChanged::MOVE_TO_BACKGROUND, timestampNs); +} + +std::unique_ptr<LogEvent> CreateMoveToForegroundEvent(const int uid, uint64_t timestampNs) { + return CreateActivityForegroundStateChangedEvent( + uid, ActivityForegroundStateChanged::MOVE_TO_FOREGROUND, timestampNs); +} + +std::unique_ptr<LogEvent> CreateSyncStateChangedEvent( + const int uid, const string& name, const SyncStateChanged::State state, uint64_t timestampNs) { + auto event = std::make_unique<LogEvent>(android::util::SYNC_STATE_CHANGED, timestampNs); + event->write(uid); + event->write(name); + event->write(state); + event->init(); + return event; +} + +std::unique_ptr<LogEvent> CreateSyncStartEvent( + const int uid, const string& name, uint64_t timestampNs){ + return CreateSyncStateChangedEvent(uid, name, SyncStateChanged::ON, timestampNs); +} + +std::unique_ptr<LogEvent> CreateSyncEndEvent( + const int uid, const string& name, uint64_t timestampNs) { + return CreateSyncStateChangedEvent(uid, name, SyncStateChanged::OFF, timestampNs); +} + +std::unique_ptr<LogEvent> CreateProcessLifeCycleStateChangedEvent( + const int uid, const ProcessLifeCycleStateChanged::Event event, uint64_t timestampNs) { + auto logEvent = std::make_unique<LogEvent>( + android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED, timestampNs); + logEvent->write(uid); + logEvent->write(""); + logEvent->write(event); + logEvent->init(); + return logEvent; +} + +std::unique_ptr<LogEvent> CreateAppCrashEvent(const int uid, uint64_t timestampNs) { + return CreateProcessLifeCycleStateChangedEvent( + uid, ProcessLifeCycleStateChanged::PROCESS_CRASHED, timestampNs); +} + +sp<StatsLogProcessor> CreateStatsLogProcessor(const long timeBaseSec, const StatsdConfig& config, + const ConfigKey& key) { + sp<UidMap> uidMap = new UidMap(); + sp<AnomalyMonitor> anomalyMonitor = new AnomalyMonitor(10); // 10 seconds + sp<StatsLogProcessor> processor = new StatsLogProcessor( + uidMap, anomalyMonitor, timeBaseSec, [](const ConfigKey&){}); + processor->OnConfigUpdated(key, config); + return processor; +} + +AttributionNode CreateAttribution(const int& uid, const string& tag) { + AttributionNode attribution; + attribution.set_uid(uid); + attribution.set_tag(tag); + return attribution; +} + +void sortLogEventsByTimestamp(std::vector<std::unique_ptr<LogEvent>> *events) { + std::sort(events->begin(), events->end(), + [](const std::unique_ptr<LogEvent>& a, const std::unique_ptr<LogEvent>& b) { + return a->GetTimestampNs() < b->GetTimestampNs(); + }); +} + +int64_t StringToId(const string& str) { + return static_cast<int64_t>(std::hash<std::string>()(str)); +} +} // namespace statsd +} // namespace os +} // namespace android
\ No newline at end of file diff --git a/cmds/statsd/tests/statsd_test_util.h b/cmds/statsd/tests/statsd_test_util.h new file mode 100644 index 000000000000..5e19da032e07 --- /dev/null +++ b/cmds/statsd/tests/statsd_test_util.h @@ -0,0 +1,128 @@ +// Copyright (C) 2017 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. + +#pragma once + +#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h" +#include "statslog.h" +#include "src/logd/LogEvent.h" +#include "src/StatsLogProcessor.h" + +namespace android { +namespace os { +namespace statsd { + +// Create AtomMatcher proto for acquiring wakelock. +AtomMatcher CreateAcquireWakelockAtomMatcher(); + +// Create AtomMatcher proto for releasing wakelock. +AtomMatcher CreateReleaseWakelockAtomMatcher() ; + +// Create AtomMatcher proto for screen turned on. +AtomMatcher CreateScreenTurnedOnAtomMatcher(); + +// Create AtomMatcher proto for screen turned off. +AtomMatcher CreateScreenTurnedOffAtomMatcher(); + +// Create AtomMatcher proto for app sync turned on. +AtomMatcher CreateSyncStartAtomMatcher(); + +// Create AtomMatcher proto for app sync turned off. +AtomMatcher CreateSyncEndAtomMatcher(); + +// Create AtomMatcher proto for app sync moves to background. +AtomMatcher CreateMoveToBackgroundAtomMatcher(); + +// Create AtomMatcher proto for app sync moves to foreground. +AtomMatcher CreateMoveToForegroundAtomMatcher(); + +// Create AtomMatcher proto for process crashes +AtomMatcher CreateProcessCrashAtomMatcher() ; + +// Create Predicate proto for screen is on. +Predicate CreateScreenIsOnPredicate(); + +// Create Predicate proto for screen is off. +Predicate CreateScreenIsOffPredicate(); + +// Create Predicate proto for holding wakelock. +Predicate CreateHoldingWakelockPredicate(); + +// Create a Predicate proto for app syncing. +Predicate CreateIsSyncingPredicate(); + +// Create a Predicate proto for app is in background. +Predicate CreateIsInBackgroundPredicate(); + +// Add a predicate to the predicate combination. +void addPredicateToPredicateCombination(const Predicate& predicate, Predicate* combination); + +// Create dimensions from primitive fields. +FieldMatcher CreateDimensions(const int atomId, const std::vector<int>& fields); + +// Create dimensions by attribution uid and tag. +FieldMatcher CreateAttributionUidAndTagDimensions(const int atomId, + const std::vector<Position>& positions); + +// Create dimensions by attribution uid only. +FieldMatcher CreateAttributionUidDimensions(const int atomId, + const std::vector<Position>& positions); + +// Create log event for screen state changed. +std::unique_ptr<LogEvent> CreateScreenStateChangedEvent( + const ScreenStateChanged::State state, uint64_t timestampNs); + +// Create log event for app moving to background. +std::unique_ptr<LogEvent> CreateMoveToBackgroundEvent(const int uid, uint64_t timestampNs); + +// Create log event for app moving to foreground. +std::unique_ptr<LogEvent> CreateMoveToForegroundEvent(const int uid, uint64_t timestampNs); + +// Create log event when the app sync starts. +std::unique_ptr<LogEvent> CreateSyncStartEvent( + const int uid, const string& name, uint64_t timestampNs); + +// Create log event when the app sync ends. +std::unique_ptr<LogEvent> CreateSyncEndEvent( + const int uid, const string& name, uint64_t timestampNs); + +// Create log event when the app sync ends. +std::unique_ptr<LogEvent> CreateAppCrashEvent( + const int uid, uint64_t timestampNs); + +// Create log event for acquiring wakelock. +std::unique_ptr<LogEvent> CreateAcquireWakelockEvent( + const std::vector<AttributionNode>& attributions, + const string& wakelockName, uint64_t timestampNs); + +// Create log event for releasing wakelock. +std::unique_ptr<LogEvent> CreateReleaseWakelockEvent( + const std::vector<AttributionNode>& attributions, + const string& wakelockName, uint64_t timestampNs); + +// Helper function to create an AttributionNode proto. +AttributionNode CreateAttribution(const int& uid, const string& tag); + +// Create a statsd log event processor upon the start time in seconds, config and key. +sp<StatsLogProcessor> CreateStatsLogProcessor(const long timeBaseSec, const StatsdConfig& config, + const ConfigKey& key); + +// Util function to sort the log events by timestamp. +void sortLogEventsByTimestamp(std::vector<std::unique_ptr<LogEvent>> *events); + +int64_t StringToId(const string& str); + +} // namespace statsd +} // namespace os +} // namespace android
\ No newline at end of file diff --git a/cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/DisplayProtoUtils.java b/cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/DisplayProtoUtils.java index fe3d86d4cd6a..93dba71716a5 100644 --- a/cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/DisplayProtoUtils.java +++ b/cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/DisplayProtoUtils.java @@ -26,7 +26,7 @@ public class DisplayProtoUtils { sb.append("ConfigKey: "); if (reports.hasConfigKey()) { com.android.os.StatsLog.ConfigMetricsReportList.ConfigKey key = reports.getConfigKey(); - sb.append("\tuid: ").append(key.getUid()).append(" name: ").append(key.getName()) + sb.append("\tuid: ").append(key.getUid()).append(" name: ").append(key.getId()) .append("\n"); } @@ -34,7 +34,7 @@ public class DisplayProtoUtils { sb.append("StatsLogReport size: ").append(report.getMetricsCount()).append("\n"); for (StatsLog.StatsLogReport log : report.getMetricsList()) { sb.append("\n\n"); - sb.append("metric id: ").append(log.getMetricName()).append("\n"); + sb.append("metric id: ").append(log.getMetricId()).append("\n"); sb.append("start time:").append(getDateStr(log.getStartReportNanos())).append("\n"); sb.append("end time:").append(getDateStr(log.getEndReportNanos())).append("\n"); @@ -71,20 +71,25 @@ public class DisplayProtoUtils { return DateFormat.format("dd/MM hh:mm:ss", nanoSec/1000000).toString(); } - private static void displayDimension(StringBuilder sb, List<StatsLog.KeyValuePair> pairs) { - for (com.android.os.StatsLog.KeyValuePair kv : pairs) { - sb.append(kv.getKey()).append(":"); - if (kv.hasValueBool()) { - sb.append(kv.getValueBool()); - } else if (kv.hasValueFloat()) { - sb.append(kv.getValueFloat()); - } else if (kv.hasValueInt()) { - sb.append(kv.getValueInt()); - } else if (kv.hasValueStr()) { - sb.append(kv.getValueStr()); + private static void displayDimension(StringBuilder sb, StatsLog.DimensionsValue dimensionValue) { + sb.append(dimensionValue.getField()).append(":"); + if (dimensionValue.hasValueBool()) { + sb.append(dimensionValue.getValueBool()); + } else if (dimensionValue.hasValueFloat()) { + sb.append(dimensionValue.getValueFloat()); + } else if (dimensionValue.hasValueInt()) { + sb.append(dimensionValue.getValueInt()); + } else if (dimensionValue.hasValueStr()) { + sb.append(dimensionValue.getValueStr()); + } else if (dimensionValue.hasValueTuple()) { + sb.append("{"); + for (StatsLog.DimensionsValue child : + dimensionValue.getValueTuple().getDimensionsValueList()) { + displayDimension(sb, child); } - sb.append(" "); + sb.append("}"); } + sb.append(" "); } public static void displayDurationMetricData(StringBuilder sb, StatsLog.StatsLogReport log) { @@ -93,7 +98,7 @@ public class DisplayProtoUtils { sb.append("Dimension size: ").append(durationMetricDataWrapper.getDataCount()).append("\n"); for (StatsLog.DurationMetricData duration : durationMetricDataWrapper.getDataList()) { sb.append("dimension: "); - displayDimension(sb, duration.getDimensionList()); + displayDimension(sb, duration.getDimension()); sb.append("\n"); for (StatsLog.DurationBucketInfo info : duration.getBucketInfoList()) { @@ -120,7 +125,7 @@ public class DisplayProtoUtils { sb.append("Dimension size: ").append(countMetricDataWrapper.getDataCount()).append("\n"); for (StatsLog.CountMetricData count : countMetricDataWrapper.getDataList()) { sb.append("dimension: "); - displayDimension(sb, count.getDimensionList()); + displayDimension(sb, count.getDimension()); sb.append("\n"); for (StatsLog.CountBucketInfo info : count.getBucketInfoList()) { diff --git a/cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/MainActivity.java b/cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/MainActivity.java index 70dd6347e1a4..119d6f52266e 100644 --- a/cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/MainActivity.java +++ b/cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/MainActivity.java @@ -34,6 +34,8 @@ import static com.android.statsd.dogfood.DisplayProtoUtils.displayLogReport; public class MainActivity extends Activity { private final static String TAG = "StatsdDogfood"; + private final static long CONFIG_ID = 987654321; + final int[] mUids = {11111111, 2222222}; StatsManager mStatsManager; @@ -163,7 +165,7 @@ public class MainActivity extends Activity { return; } if (mStatsManager != null) { - byte[] data = mStatsManager.getData("fake"); + byte[] data = mStatsManager.getData(CONFIG_ID); if (data != null) { displayData(data); } else { @@ -186,7 +188,7 @@ public class MainActivity extends Activity { byte[] config = new byte[inputStream.available()]; inputStream.read(config); if (mStatsManager != null) { - if (mStatsManager.addConfiguration("fake", + if (mStatsManager.addConfiguration(CONFIG_ID, config, getPackageName(), MainActivity.this.getClass().getName())) { Toast.makeText( MainActivity.this, "Config pushed", Toast.LENGTH_LONG).show(); @@ -252,7 +254,9 @@ public class MainActivity extends Activity { Log.d(TAG, "invalid pkg id"); return; } - StatsLog.write(StatsLog.WAKELOCK_STATE_CHANGED, mUids[id], 0, name, 1); + int[] uids = new int[] {mUids[id]}; + String[] tags = new String[] {"acquire"}; + StatsLog.write(StatsLog.WAKELOCK_STATE_CHANGED, uids, tags, 0, name, 1); StringBuilder sb = new StringBuilder(); sb.append("StagsLog.write(10, ").append(mUids[id]).append(", ").append(0) .append(", ").append(name).append(", 1);"); @@ -264,7 +268,9 @@ public class MainActivity extends Activity { Log.d(TAG, "invalid pkg id"); return; } - StatsLog.write(10, mUids[id], 0, name, 0); + int[] uids = new int[] {mUids[id]}; + String[] tags = new String[] {"release"}; + StatsLog.write(10, uids, tags, 0, name, 0); StringBuilder sb = new StringBuilder(); sb.append("StagsLog.write(10, ").append(mUids[id]).append(", ").append(0) .append(", ").append(name).append(", 0);"); diff --git a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/ConfigFactory.java b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/ConfigFactory.java index 5fc4cf52f850..4bd284448446 100644 --- a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/ConfigFactory.java +++ b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/ConfigFactory.java @@ -27,8 +27,7 @@ import com.android.internal.os.StatsdConfigProto.MetricConditionLink; import com.android.internal.os.StatsdConfigProto.EventMetric; import com.android.internal.os.StatsdConfigProto.GaugeMetric; import com.android.internal.os.StatsdConfigProto.ValueMetric; -import com.android.internal.os.StatsdConfigProto.KeyMatcher; -import com.android.internal.os.StatsdConfigProto.KeyValueMatcher; +import com.android.internal.os.StatsdConfigProto.FieldValueMatcher; import com.android.internal.os.StatsdConfigProto.AtomMatcher; import com.android.internal.os.StatsdConfigProto.SimplePredicate; import com.android.internal.os.StatsdConfigProto.StatsdConfig; @@ -42,7 +41,7 @@ import java.util.List; * Creates StatsdConfig protos for loadtesting. */ public class ConfigFactory { - public static final String CONFIG_NAME = "LOADTEST"; + public static final long CONFIG_ID = 123456789; private static final String TAG = "loadtest.ConfigFactory"; @@ -87,7 +86,7 @@ public class ConfigFactory { boolean includeDuration, boolean includeEvent, boolean includeValue, boolean includeGauge) { StatsdConfig.Builder config = StatsdConfig.newBuilder() - .setName(CONFIG_NAME); + .setId(CONFIG_ID); if (placebo) { replication = 0; // Config will be empty, aside from a name. } @@ -161,7 +160,7 @@ public class ConfigFactory { */ private void addEventMetric(EventMetric template, int suffix, StatsdConfig.Builder config) { EventMetric.Builder metric = template.toBuilder() - .setName(template.getName() + suffix) + .setId(template.getId() + suffix) .setWhat(template.getWhat() + suffix); if (template.hasCondition()) { metric.setCondition(template.getCondition() + suffix); @@ -187,7 +186,7 @@ public class ConfigFactory { private void addCountMetric(CountMetric template, int suffix, long bucketMillis, StatsdConfig.Builder config) { CountMetric.Builder metric = template.toBuilder() - .setName(template.getName() + suffix) + .setId(template.getId() + suffix) .setWhat(template.getWhat() + suffix); if (template.hasCondition()) { metric.setCondition(template.getCondition() + suffix); @@ -208,7 +207,7 @@ public class ConfigFactory { private void addDurationMetric(DurationMetric template, int suffix, long bucketMillis, StatsdConfig.Builder config) { DurationMetric.Builder metric = template.toBuilder() - .setName(template.getName() + suffix) + .setId(template.getId() + suffix) .setWhat(template.getWhat() + suffix); if (template.hasCondition()) { metric.setCondition(template.getCondition() + suffix); @@ -229,7 +228,7 @@ public class ConfigFactory { private void addGaugeMetric(GaugeMetric template, int suffix, long bucketMillis, StatsdConfig.Builder config) { GaugeMetric.Builder metric = template.toBuilder() - .setName(template.getName() + suffix) + .setId(template.getId() + suffix) .setWhat(template.getWhat() + suffix); if (template.hasCondition()) { metric.setCondition(template.getCondition() + suffix); @@ -250,7 +249,7 @@ public class ConfigFactory { private void addValueMetric(ValueMetric template, int suffix, long bucketMillis, StatsdConfig.Builder config) { ValueMetric.Builder metric = template.toBuilder() - .setName(template.getName() + suffix) + .setId(template.getId() + suffix) .setWhat(template.getWhat() + suffix); if (template.hasCondition()) { metric.setCondition(template.getCondition() + suffix); @@ -270,11 +269,11 @@ public class ConfigFactory { */ private void addPredicate(Predicate template, int suffix, StatsdConfig.Builder config) { Predicate.Builder predicate = template.toBuilder() - .setName(template.getName() + suffix); + .setId(template.getId() + suffix); if (template.hasCombination()) { Predicate.Combination.Builder cb = template.getCombination().toBuilder() .clearPredicate(); - for (String child : template.getCombination().getPredicateList()) { + for (long child : template.getCombination().getPredicateList()) { cb.addPredicate(child + suffix); } predicate.setCombination(cb.build()); @@ -297,11 +296,11 @@ public class ConfigFactory { */ private void addMatcher(AtomMatcher template, int suffix, StatsdConfig.Builder config) { AtomMatcher.Builder matcher = template.toBuilder() - .setName(template.getName() + suffix); + .setId(template.getId() + suffix); if (template.hasCombination()) { AtomMatcher.Combination.Builder cb = template.getCombination().toBuilder() .clearMatcher(); - for (String child : template.getCombination().getMatcherList()) { + for (long child : template.getCombination().getMatcherList()) { cb.addMatcher(child + suffix); } matcher.setCombination(cb); diff --git a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/DisplayProtoUtils.java b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/DisplayProtoUtils.java index 735a32729a0f..19087d86c4a6 100644 --- a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/DisplayProtoUtils.java +++ b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/DisplayProtoUtils.java @@ -26,7 +26,7 @@ public class DisplayProtoUtils { sb.append("ConfigKey: "); if (reports.hasConfigKey()) { com.android.os.StatsLog.ConfigMetricsReportList.ConfigKey key = reports.getConfigKey(); - sb.append("\tuid: ").append(key.getUid()).append(" name: ").append(key.getName()) + sb.append("\tuid: ").append(key.getUid()).append(" id: ").append(key.getId()) .append("\n"); } @@ -34,7 +34,7 @@ public class DisplayProtoUtils { sb.append("StatsLogReport size: ").append(report.getMetricsCount()).append("\n"); for (StatsLog.StatsLogReport log : report.getMetricsList()) { sb.append("\n\n"); - sb.append("metric id: ").append(log.getMetricName()).append("\n"); + sb.append("metric id: ").append(log.getMetricId()).append("\n"); sb.append("start time:").append(getDateStr(log.getStartReportNanos())).append("\n"); sb.append("end time:").append(getDateStr(log.getEndReportNanos())).append("\n"); @@ -71,20 +71,25 @@ public class DisplayProtoUtils { return DateFormat.format("dd/MM hh:mm:ss", nanoSec/1000000).toString(); } - private static void displayDimension(StringBuilder sb, List<StatsLog.KeyValuePair> pairs) { - for (com.android.os.StatsLog.KeyValuePair kv : pairs) { - sb.append(kv.getKey()).append(":"); - if (kv.hasValueBool()) { - sb.append(kv.getValueBool()); - } else if (kv.hasValueFloat()) { - sb.append(kv.getValueFloat()); - } else if (kv.hasValueInt()) { - sb.append(kv.getValueInt()); - } else if (kv.hasValueStr()) { - sb.append(kv.getValueStr()); + private static void displayDimension(StringBuilder sb, StatsLog.DimensionsValue dimensionValue) { + sb.append(dimensionValue.getField()).append(":"); + if (dimensionValue.hasValueBool()) { + sb.append(dimensionValue.getValueBool()); + } else if (dimensionValue.hasValueFloat()) { + sb.append(dimensionValue.getValueFloat()); + } else if (dimensionValue.hasValueInt()) { + sb.append(dimensionValue.getValueInt()); + } else if (dimensionValue.hasValueStr()) { + sb.append(dimensionValue.getValueStr()); + } else if (dimensionValue.hasValueTuple()) { + sb.append("{"); + for (StatsLog.DimensionsValue child : + dimensionValue.getValueTuple().getDimensionsValueList()) { + displayDimension(sb, child); } - sb.append(" "); + sb.append("}"); } + sb.append(" "); } public static void displayDurationMetricData(StringBuilder sb, StatsLog.StatsLogReport log) { @@ -93,7 +98,7 @@ public class DisplayProtoUtils { sb.append("Dimension size: ").append(durationMetricDataWrapper.getDataCount()).append("\n"); for (StatsLog.DurationMetricData duration : durationMetricDataWrapper.getDataList()) { sb.append("dimension: "); - displayDimension(sb, duration.getDimensionList()); + displayDimension(sb, duration.getDimension()); sb.append("\n"); for (StatsLog.DurationBucketInfo info : duration.getBucketInfoList()) { @@ -120,7 +125,7 @@ public class DisplayProtoUtils { sb.append("Dimension size: ").append(countMetricDataWrapper.getDataCount()).append("\n"); for (StatsLog.CountMetricData count : countMetricDataWrapper.getDataList()) { sb.append("dimension: "); - displayDimension(sb, count.getDimensionList()); + displayDimension(sb, count.getDimension()); sb.append("\n"); for (StatsLog.CountBucketInfo info : count.getBucketInfoList()) { diff --git a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/LoadtestActivity.java b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/LoadtestActivity.java index 83f4b7bed558..86da16c82e9b 100644 --- a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/LoadtestActivity.java +++ b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/LoadtestActivity.java @@ -294,7 +294,7 @@ public class LoadtestActivity extends Activity { return null; } if (mStatsManager != null) { - byte[] data = mStatsManager.getData(ConfigFactory.CONFIG_NAME); + byte[] data = mStatsManager.getData(ConfigFactory.CONFIG_ID); if (data != null) { ConfigMetricsReportList reports = null; try { @@ -453,7 +453,7 @@ public class LoadtestActivity extends Activity { // TODO: Clear all configs instead of specific ones. if (mStatsManager != null) { if (mStarted) { - if (!mStatsManager.removeConfiguration(ConfigFactory.CONFIG_NAME)) { + if (!mStatsManager.removeConfiguration(ConfigFactory.CONFIG_ID)) { Log.d(TAG, "Removed loadtest statsd configs."); } else { Log.d(TAG, "Failed to remove loadtest configs."); @@ -464,7 +464,7 @@ public class LoadtestActivity extends Activity { private boolean setConfig(byte[] config) { if (mStatsManager != null) { - if (mStatsManager.addConfiguration(ConfigFactory.CONFIG_NAME, + if (mStatsManager.addConfiguration(ConfigFactory.CONFIG_ID, config, getPackageName(), LoadtestActivity.this.getClass().getName())) { Log.d(TAG, "Config pushed to statsd"); return true; diff --git a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/ValidationRecorder.java b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/ValidationRecorder.java index 4b614aa19492..d122654ec8a1 100644 --- a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/ValidationRecorder.java +++ b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/ValidationRecorder.java @@ -59,18 +59,8 @@ public class ValidationRecorder extends PerfDataRecorder { Log.d(TAG, "GOT DATA"); for (ConfigMetricsReport report : reports) { for (StatsLogReport logReport : report.getMetricsList()) { - if (!logReport.hasMetricName()) { + if (!logReport.hasMetricId()) { Log.e(TAG, "Metric missing name."); - continue; - } - String metricName = logReport.getMetricName(); - if (metricName.startsWith("EVENT_BATTERY_LEVEL_CHANGES_WHILE_SCREEN_IS_ON_")) { - validateEventBatteryLevelChangesWhileScreenIsOn(logReport); - continue; - } - if (metricName.startsWith("EVENT_BATTERY_LEVEL_CHANGES_")) { - validateEventBatteryLevelChanges(logReport); - continue; } } } @@ -78,7 +68,7 @@ public class ValidationRecorder extends PerfDataRecorder { } private void validateEventBatteryLevelChanges(StatsLogReport logReport) { - Log.d(TAG, "Validating " + logReport.getMetricName()); + Log.d(TAG, "Validating " + logReport.getMetricId()); if (logReport.hasEventMetrics()) { Log.d(TAG, "Num events captured: " + logReport.getEventMetrics().getDataCount()); for (EventMetricData data : logReport.getEventMetrics().getDataList()) { @@ -90,6 +80,6 @@ public class ValidationRecorder extends PerfDataRecorder { } private void validateEventBatteryLevelChangesWhileScreenIsOn(StatsLogReport logReport) { - Log.d(TAG, "Validating " + logReport.getMetricName()); + Log.d(TAG, "Validating " + logReport.getMetricId()); } } diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index 1adae7a84fcc..847f91bdc59a 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -60,6 +60,7 @@ import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemProperties; import android.os.UserHandle; +import android.os.WorkSource; import android.text.TextUtils; import android.util.ArrayMap; import android.util.DisplayMetrics; @@ -3911,10 +3912,10 @@ public class ActivityManager { /** * @hide */ - public static void noteWakeupAlarm(PendingIntent ps, int sourceUid, String sourcePkg, - String tag) { + public static void noteWakeupAlarm(PendingIntent ps, WorkSource workSource, int sourceUid, + String sourcePkg, String tag) { try { - getService().noteWakeupAlarm((ps != null) ? ps.getTarget() : null, + getService().noteWakeupAlarm((ps != null) ? ps.getTarget() : null, workSource, sourceUid, sourcePkg, tag); } catch (RemoteException ex) { } @@ -3923,19 +3924,24 @@ public class ActivityManager { /** * @hide */ - public static void noteAlarmStart(PendingIntent ps, int sourceUid, String tag) { + public static void noteAlarmStart(PendingIntent ps, WorkSource workSource, int sourceUid, + String tag) { try { - getService().noteAlarmStart((ps != null) ? ps.getTarget() : null, sourceUid, tag); + getService().noteAlarmStart((ps != null) ? ps.getTarget() : null, workSource, + sourceUid, tag); } catch (RemoteException ex) { } } + /** * @hide */ - public static void noteAlarmFinish(PendingIntent ps, int sourceUid, String tag) { + public static void noteAlarmFinish(PendingIntent ps, WorkSource workSource, int sourceUid, + String tag) { try { - getService().noteAlarmFinish((ps != null) ? ps.getTarget() : null, sourceUid, tag); + getService().noteAlarmFinish((ps != null) ? ps.getTarget() : null, workSource, + sourceUid, tag); } catch (RemoteException ex) { } } diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java index c09403c29943..4c558f374f91 100644 --- a/core/java/android/app/ActivityManagerNative.java +++ b/core/java/android/app/ActivityManagerNative.java @@ -75,20 +75,20 @@ public abstract class ActivityManagerNative { */ static public void noteWakeupAlarm(PendingIntent ps, int sourceUid, String sourcePkg, String tag) { - ActivityManager.noteWakeupAlarm(ps, sourceUid, sourcePkg, tag); + ActivityManager.noteWakeupAlarm(ps, null, sourceUid, sourcePkg, tag); } /** * @deprecated use ActivityManager.noteAlarmStart instead. */ static public void noteAlarmStart(PendingIntent ps, int sourceUid, String tag) { - ActivityManager.noteAlarmStart(ps, sourceUid, tag); + ActivityManager.noteAlarmStart(ps, null, sourceUid, tag); } /** * @deprecated use ActivityManager.noteAlarmFinish instead. */ static public void noteAlarmFinish(PendingIntent ps, int sourceUid, String tag) { - ActivityManager.noteAlarmFinish(ps, sourceUid, tag); + ActivityManager.noteAlarmFinish(ps, null, sourceUid, tag); } } diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index aaa6bf0333a2..de346f315016 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -1768,9 +1768,7 @@ public final class ActivityThread extends ClientTransactionHandler { (String[]) ((SomeArgs) msg.obj).arg2); break; case EXECUTE_TRANSACTION: - final ClientTransaction transaction = (ClientTransaction) msg.obj; - mTransactionExecutor.execute(transaction); - transaction.recycle(); + mTransactionExecutor.execute(((ClientTransaction) msg.obj)); break; } Object obj = msg.obj; diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl index 1278f75abce0..a9e633ff392d 100644 --- a/core/java/android/app/IActivityManager.aidl +++ b/core/java/android/app/IActivityManager.aidl @@ -63,6 +63,7 @@ import android.os.IProgressListener; import android.os.ParcelFileDescriptor; import android.os.PersistableBundle; import android.os.StrictMode; +import android.os.WorkSource; import android.service.voice.IVoiceInteractionSession; import com.android.internal.app.IVoiceInteractor; import com.android.internal.os.IResultReceiver; @@ -198,7 +199,7 @@ interface IActivityManager { void enterSafeMode(); boolean startNextMatchingActivity(in IBinder callingActivity, in Intent intent, in Bundle options); - void noteWakeupAlarm(in IIntentSender sender, int sourceUid, + void noteWakeupAlarm(in IIntentSender sender, in WorkSource workSource, int sourceUid, in String sourcePkg, in String tag); void removeContentProvider(in IBinder connection, boolean stable); void setRequestedOrientation(in IBinder token, int requestedOrientation); @@ -468,8 +469,8 @@ interface IActivityManager { void dumpHeapFinished(in String path); void setVoiceKeepAwake(in IVoiceInteractionSession session, boolean keepAwake); void updateLockTaskPackages(int userId, in String[] packages); - void noteAlarmStart(in IIntentSender sender, int sourceUid, in String tag); - void noteAlarmFinish(in IIntentSender sender, int sourceUid, in String tag); + void noteAlarmStart(in IIntentSender sender, in WorkSource workSource, int sourceUid, in String tag); + void noteAlarmFinish(in IIntentSender sender, in WorkSource workSource, int sourceUid, in String tag); int getPackageProcessState(in String packageName, in String callingPackage); oneway void showLockTaskEscapeMessage(in IBinder token); void updateDeviceOwner(in String packageName); diff --git a/core/java/android/app/servertransaction/ClientTransaction.java b/core/java/android/app/servertransaction/ClientTransaction.java index 3c96f06945c5..764ceede5d20 100644 --- a/core/java/android/app/servertransaction/ClientTransaction.java +++ b/core/java/android/app/servertransaction/ClientTransaction.java @@ -54,11 +54,6 @@ public class ClientTransaction implements Parcelable, ObjectPoolItem { /** Target client activity. Might be null if the entire transaction is targeting an app. */ private IBinder mActivityToken; - /** Get the target client of the transaction. */ - public IApplicationThread getClient() { - return mClient; - } - /** * Add a message to the end of the sequence of callbacks. * @param activityCallback A single message that can contain a lifecycle request/callback. diff --git a/core/java/android/app/servertransaction/ObjectPool.java b/core/java/android/app/servertransaction/ObjectPool.java index 2fec30a0dde7..98121253f486 100644 --- a/core/java/android/app/servertransaction/ObjectPool.java +++ b/core/java/android/app/servertransaction/ObjectPool.java @@ -16,8 +16,8 @@ package android.app.servertransaction; -import java.util.ArrayList; import java.util.HashMap; +import java.util.LinkedList; import java.util.Map; /** @@ -27,7 +27,7 @@ import java.util.Map; class ObjectPool { private static final Object sPoolSync = new Object(); - private static final Map<Class, ArrayList<? extends ObjectPoolItem>> sPoolMap = + private static final Map<Class, LinkedList<? extends ObjectPoolItem>> sPoolMap = new HashMap<>(); private static final int MAX_POOL_SIZE = 50; @@ -40,9 +40,9 @@ class ObjectPool { public static <T extends ObjectPoolItem> T obtain(Class<T> itemClass) { synchronized (sPoolSync) { @SuppressWarnings("unchecked") - final ArrayList<T> itemPool = (ArrayList<T>) sPoolMap.get(itemClass); + LinkedList<T> itemPool = (LinkedList<T>) sPoolMap.get(itemClass); if (itemPool != null && !itemPool.isEmpty()) { - return itemPool.remove(itemPool.size() - 1); + return itemPool.poll(); } return null; } @@ -56,20 +56,16 @@ class ObjectPool { public static <T extends ObjectPoolItem> void recycle(T item) { synchronized (sPoolSync) { @SuppressWarnings("unchecked") - ArrayList<T> itemPool = (ArrayList<T>) sPoolMap.get(item.getClass()); + LinkedList<T> itemPool = (LinkedList<T>) sPoolMap.get(item.getClass()); if (itemPool == null) { - itemPool = new ArrayList<>(); + itemPool = new LinkedList<>(); sPoolMap.put(item.getClass(), itemPool); } - // Check if the item is already in the pool - final int size = itemPool.size(); - for (int i = 0; i < size; i++) { - if (itemPool.get(i) == item) { - throw new IllegalStateException("Trying to recycle already recycled item"); - } + if (itemPool.contains(item)) { + throw new IllegalStateException("Trying to recycle already recycled item"); } - if (size < MAX_POOL_SIZE) { + if (itemPool.size() < MAX_POOL_SIZE) { itemPool.add(item); } } diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 74ba60d32fb6..4cedeaa0ada2 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -3021,7 +3021,8 @@ public abstract class Context { SYSTEM_HEALTH_SERVICE, //@hide: INCIDENT_SERVICE, //@hide: STATS_COMPANION_SERVICE, - COMPANION_DEVICE_SERVICE + COMPANION_DEVICE_SERVICE, + CROSS_PROFILE_APPS_SERVICE }) @Retention(RetentionPolicy.SOURCE) public @interface ServiceName {} diff --git a/core/java/android/content/pm/PackageList.java b/core/java/android/content/pm/PackageList.java new file mode 100644 index 000000000000..cfd99abc6283 --- /dev/null +++ b/core/java/android/content/pm/PackageList.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2017 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 android.content.pm; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.pm.PackageManagerInternal.PackageListObserver; + +import com.android.server.LocalServices; + +import java.util.List; + +/** + * All of the package name installed on the system. + * <p>A self observable list that automatically removes the listener when it goes out of scope. + * + * @hide Only for use within the system server. + */ +public class PackageList implements PackageListObserver, AutoCloseable { + private final PackageListObserver mWrappedObserver; + private final List<String> mPackageNames; + + /** + * Create a new object. + * <p>Ownership of the given {@link List} transfers to this object and should not + * be modified by the caller. + */ + public PackageList(@NonNull List<String> packageNames, @Nullable PackageListObserver observer) { + mPackageNames = packageNames; + mWrappedObserver = observer; + } + + @Override + public void onPackageAdded(String packageName) { + if (mWrappedObserver != null) { + mWrappedObserver.onPackageAdded(packageName); + } + } + + @Override + public void onPackageRemoved(String packageName) { + if (mWrappedObserver != null) { + mWrappedObserver.onPackageRemoved(packageName); + } + } + + @Override + public void close() throws Exception { + LocalServices.getService(PackageManagerInternal.class).removePackageListObserver(this); + } + + /** + * Returns the names of packages installed on the system. + * <p>The list is a copy-in-time and the actual set of installed packages may differ. Real + * time updates to the package list are sent via the {@link PackageListObserver} callback. + */ + public @NonNull List<String> getPackageNames() { + return mPackageNames; + } +} diff --git a/core/java/android/content/pm/PackageManagerInternal.java b/core/java/android/content/pm/PackageManagerInternal.java index 713cd109ef87..8ee8e102d9b5 100644 --- a/core/java/android/content/pm/PackageManagerInternal.java +++ b/core/java/android/content/pm/PackageManagerInternal.java @@ -53,6 +53,14 @@ public abstract class PackageManagerInternal { @Retention(RetentionPolicy.SOURCE) public @interface KnownPackage {} + /** Observer called whenever the list of packages changes */ + public interface PackageListObserver { + /** A package was added to the system. */ + void onPackageAdded(@NonNull String packageName); + /** A package was removed from the system. */ + void onPackageRemoved(@NonNull String packageName); + } + /** * Provider for package names. */ @@ -435,6 +443,35 @@ public abstract class PackageManagerInternal { public abstract @Nullable PackageParser.Package getPackage(@NonNull String packageName); /** + * Returns a list without a change observer. + * + * {@see #getPackageList(PackageListObserver)} + */ + public @NonNull PackageList getPackageList() { + return getPackageList(null); + } + + /** + * Returns the list of packages installed at the time of the method call. + * <p>The given observer is notified when the list of installed packages + * changes [eg. a package was installed or uninstalled]. It will not be + * notified if a package is updated. + * <p>The package list will not be updated automatically as packages are + * installed / uninstalled. Any changes must be handled within the observer. + */ + public abstract @NonNull PackageList getPackageList(@Nullable PackageListObserver observer); + + /** + * Removes the observer. + * <p>Generally not needed. {@link #getPackageList(PackageListObserver)} will automatically + * remove the observer. + * <p>Does nothing if the observer isn't currently registered. + * <p>Observers are notified asynchronously and it's possible for an observer to be + * invoked after its been removed. + */ + public abstract void removePackageListObserver(@NonNull PackageListObserver observer); + + /** * Returns a package object for the disabled system package name. */ public abstract @Nullable PackageParser.Package getDisabledPackage(@NonNull String packageName); diff --git a/core/java/android/hardware/location/ContextHubClient.java b/core/java/android/hardware/location/ContextHubClient.java index 52527ed67ae4..0a21083a0262 100644 --- a/core/java/android/hardware/location/ContextHubClient.java +++ b/core/java/android/hardware/location/ContextHubClient.java @@ -15,9 +15,13 @@ */ package android.hardware.location; +import android.annotation.NonNull; import android.annotation.RequiresPermission; +import android.annotation.SystemApi; import android.os.RemoteException; +import com.android.internal.util.Preconditions; + import dalvik.system.CloseGuard; import java.io.Closeable; @@ -31,16 +35,12 @@ import java.util.concurrent.atomic.AtomicBoolean; * * @hide */ +@SystemApi public class ContextHubClient implements Closeable { /* * The proxy to the client interface at the service. */ - private final IContextHubClient mClientProxy; - - /* - * The callback interface associated with this client. - */ - private final IContextHubClientCallback mCallbackInterface; + private IContextHubClient mClientProxy = null; /* * The Context Hub that this client is attached to. @@ -51,20 +51,33 @@ public class ContextHubClient implements Closeable { private final AtomicBoolean mIsClosed = new AtomicBoolean(false); - /* package */ ContextHubClient( - IContextHubClient clientProxy, IContextHubClientCallback callback, - ContextHubInfo hubInfo) { - mClientProxy = clientProxy; - mCallbackInterface = callback; + /* package */ ContextHubClient(ContextHubInfo hubInfo) { mAttachedHub = hubInfo; mCloseGuard.open("close"); } /** + * Sets the proxy interface of the client at the service. This method should always be called + * by the ContextHubManager after the client is registered at the service, and should only be + * called once. + * + * @param clientProxy the proxy of the client at the service + */ + /* package */ void setClientProxy(IContextHubClient clientProxy) { + Preconditions.checkNotNull(clientProxy, "IContextHubClient cannot be null"); + if (mClientProxy != null) { + throw new IllegalStateException("Cannot change client proxy multiple times"); + } + + mClientProxy = clientProxy; + } + + /** * Returns the hub that this client is attached to. * * @return the ContextHubInfo of the attached hub */ + @NonNull public ContextHubInfo getAttachedHub() { return mAttachedHub; } @@ -96,12 +109,16 @@ public class ContextHubClient implements Closeable { * * @return the result of sending the message defined as in ContextHubTransaction.Result * + * @throws NullPointerException if NanoAppMessage is null + * * @see NanoAppMessage * @see ContextHubTransaction.Result */ @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) @ContextHubTransaction.Result - public int sendMessageToNanoApp(NanoAppMessage message) { + public int sendMessageToNanoApp(@NonNull NanoAppMessage message) { + Preconditions.checkNotNull(message, "NanoAppMessage cannot be null"); + try { return mClientProxy.sendMessageToNanoApp(message); } catch (RemoteException e) { diff --git a/core/java/android/hardware/location/ContextHubClientCallback.java b/core/java/android/hardware/location/ContextHubClientCallback.java index ab19d547025d..cc2fe65dcb7e 100644 --- a/core/java/android/hardware/location/ContextHubClientCallback.java +++ b/core/java/android/hardware/location/ContextHubClientCallback.java @@ -15,15 +15,20 @@ */ package android.hardware.location; +import android.annotation.SystemApi; + +import java.util.concurrent.Executor; + /** * A class for {@link android.hardware.location.ContextHubClient ContextHubClient} to * receive messages and life-cycle events from nanoapps in the Context Hub at which the client is * attached to. * - * This callback is registered through the - * {@link android.hardware.location.ContextHubManager#createClient() creation} of - * {@link android.hardware.location.ContextHubClient ContextHubClient}. Callbacks are - * invoked in the following ways: + * This callback is registered through the {@link + * android.hardware.location.ContextHubManager#createClient( + * ContextHubInfo, ContextHubClientCallback, Executor) creation} of + * {@link android.hardware.location.ContextHubClient ContextHubClient}. Callbacks are invoked in + * the following ways: * 1) Messages from nanoapps delivered through onMessageFromNanoApp may either be broadcasted * or targeted to a specific client. * 2) Nanoapp or Context Hub events (the remaining callbacks) are broadcasted to all clients, and @@ -31,6 +36,7 @@ package android.hardware.location; * * @hide */ +@SystemApi public class ContextHubClientCallback { /** * Callback invoked when receiving a message from a nanoapp. @@ -38,48 +44,56 @@ public class ContextHubClientCallback { * The message contents of this callback may either be broadcasted or targeted to the * client receiving the invocation. * + * @param client the client that is associated with this callback * @param message the message sent by the nanoapp */ - public void onMessageFromNanoApp(NanoAppMessage message) {} + public void onMessageFromNanoApp(ContextHubClient client, NanoAppMessage message) {} /** * Callback invoked when the attached Context Hub has reset. + * + * @param client the client that is associated with this callback */ - public void onHubReset() {} + public void onHubReset(ContextHubClient client) {} /** * Callback invoked when a nanoapp aborts at the attached Context Hub. * + * @param client the client that is associated with this callback * @param nanoAppId the ID of the nanoapp that had aborted * @param abortCode the reason for nanoapp's abort, specific to each nanoapp */ - public void onNanoAppAborted(long nanoAppId, int abortCode) {} + public void onNanoAppAborted(ContextHubClient client, long nanoAppId, int abortCode) {} /** * Callback invoked when a nanoapp is loaded at the attached Context Hub. * + * @param client the client that is associated with this callback * @param nanoAppId the ID of the nanoapp that had been loaded */ - public void onNanoAppLoaded(long nanoAppId) {} + public void onNanoAppLoaded(ContextHubClient client, long nanoAppId) {} /** * Callback invoked when a nanoapp is unloaded from the attached Context Hub. * + * @param client the client that is associated with this callback * @param nanoAppId the ID of the nanoapp that had been unloaded */ - public void onNanoAppUnloaded(long nanoAppId) {} + public void onNanoAppUnloaded(ContextHubClient client, long nanoAppId) {} /** * Callback invoked when a nanoapp is enabled at the attached Context Hub. * + * @param client the client that is associated with this callback * @param nanoAppId the ID of the nanoapp that had been enabled */ - public void onNanoAppEnabled(long nanoAppId) {} + public void onNanoAppEnabled(ContextHubClient client, long nanoAppId) {} /** * Callback invoked when a nanoapp is disabled at the attached Context Hub. * + * @param client the client that is associated with this callback * @param nanoAppId the ID of the nanoapp that had been disabled */ - public void onNanoAppDisabled(long nanoAppId) {} + public void onNanoAppDisabled(ContextHubClient client, long nanoAppId) {} } diff --git a/core/java/android/hardware/location/ContextHubInfo.java b/core/java/android/hardware/location/ContextHubInfo.java index c2b280016f68..36123e3d4229 100644 --- a/core/java/android/hardware/location/ContextHubInfo.java +++ b/core/java/android/hardware/location/ContextHubInfo.java @@ -221,9 +221,6 @@ public class ContextHubInfo implements Parcelable { /** * @return the CHRE platform ID as defined in chre/version.h - * - * TODO(b/67734082): Expose as public API - * @hide */ public long getChrePlatformId() { return mChrePlatformId; @@ -231,9 +228,6 @@ public class ContextHubInfo implements Parcelable { /** * @return the CHRE API's major version as defined in chre/version.h - * - * TODO(b/67734082): Expose as public API - * @hide */ public byte getChreApiMajorVersion() { return mChreApiMajorVersion; @@ -241,9 +235,6 @@ public class ContextHubInfo implements Parcelable { /** * @return the CHRE API's minor version as defined in chre/version.h - * - * TODO(b/67734082): Expose as public API - * @hide */ public byte getChreApiMinorVersion() { return mChreApiMinorVersion; @@ -251,9 +242,6 @@ public class ContextHubInfo implements Parcelable { /** * @return the CHRE patch version as defined in chre/version.h - * - * TODO(b/67734082): Expose as public API - * @hide */ public short getChrePatchVersion() { return mChrePatchVersion; diff --git a/core/java/android/hardware/location/ContextHubManager.java b/core/java/android/hardware/location/ContextHubManager.java index 4cea0acd3809..be1efdea0260 100644 --- a/core/java/android/hardware/location/ContextHubManager.java +++ b/core/java/android/hardware/location/ContextHubManager.java @@ -17,6 +17,7 @@ package android.hardware.location; import android.annotation.CallbackExecutor; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SuppressLint; import android.annotation.SystemApi; @@ -30,6 +31,8 @@ import android.os.ServiceManager; import android.os.ServiceManager.ServiceNotFoundException; import android.util.Log; +import com.android.internal.util.Preconditions; + import java.util.List; import java.util.concurrent.Executor; @@ -59,7 +62,11 @@ public final class ContextHubManager { /** * An interface to receive asynchronous communication from the context hub. + * + * @deprecated Use the more refined {@link android.hardware.location.ContextHubClientCallback} + * instead for notification callbacks. */ + @Deprecated public abstract static class Callback { protected Callback() {} @@ -75,7 +82,7 @@ public final class ContextHubManager { public abstract void onMessageReceipt( int hubHandle, int nanoAppHandle, - ContextHubMessage message); + @NonNull ContextHubMessage message); } /** @@ -98,8 +105,13 @@ public final class ContextHubManager { /** * Get a handle to all the context hubs in the system + * * @return array of context hub handles + * + * @deprecated Use {@link #getContextHubs()} instead. The use of handles are deprecated in the + * new APIs. */ + @Deprecated @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public int[] getContextHubHandles() { try { @@ -116,7 +128,11 @@ public final class ContextHubManager { * @return ContextHubInfo Information about the requested context hub. * * @see ContextHubInfo + * + * @deprecated Use {@link #getContextHubs()} instead. The use of handles are deprecated in the + * new APIs. */ + @Deprecated @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public ContextHubInfo getContextHubInfo(int hubHandle) { try { @@ -144,9 +160,12 @@ public final class ContextHubManager { * -1 otherwise * * @see NanoApp + * + * @deprecated Use {@link #loadNanoApp(ContextHubInfo, NanoAppBinary)} instead. */ + @Deprecated @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) - public int loadNanoApp(int hubHandle, NanoApp app) { + public int loadNanoApp(int hubHandle, @NonNull NanoApp app) { try { return mService.loadNanoApp(hubHandle, app); } catch (RemoteException e) { @@ -168,7 +187,10 @@ public final class ContextHubManager { * * @return 0 if the command for unloading was sent to the context hub; * -1 otherwise + * + * @deprecated Use {@link #unloadNanoApp(ContextHubInfo, long)} instead. */ + @Deprecated @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public int unloadNanoApp(int nanoAppHandle) { try { @@ -199,13 +221,18 @@ public final class ContextHubManager { * TODO(b/30943489): Have the returned NanoAppInstanceInfo contain the * correct information. * - * @param nanoAppHandle handle of the nanoAppInstance - * @return NanoAppInstanceInfo Information about the nano app instance. + * @param nanoAppHandle handle of the nanoapp instance + * @return NanoAppInstanceInfo the NanoAppInstanceInfo of the nanoapp, or null if the nanoapp + * does not exist * * @see NanoAppInstanceInfo + * + * @deprecated Use {@link #queryNanoApps(ContextHubInfo)} instead to explicitly query the hub + * for loaded nanoapps. */ + @Deprecated @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) - public NanoAppInstanceInfo getNanoAppInstanceInfo(int nanoAppHandle) { + @Nullable public NanoAppInstanceInfo getNanoAppInstanceInfo(int nanoAppHandle) { try { return mService.getNanoAppInstanceInfo(nanoAppHandle); } catch (RemoteException e) { @@ -222,9 +249,13 @@ public final class ContextHubManager { * @see NanoAppFilter * * @return int[] Array of handles to any found nano apps + * + * @deprecated Use {@link #queryNanoApps(ContextHubInfo)} instead to explicitly query the hub + * for loaded nanoapps. */ + @Deprecated @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) - public int[] findNanoAppOnHub(int hubHandle, NanoAppFilter filter) { + @NonNull public int[] findNanoAppOnHub(int hubHandle, @NonNull NanoAppFilter filter) { try { return mService.findNanoAppOnHub(hubHandle, filter); } catch (RemoteException e) { @@ -250,9 +281,16 @@ public final class ContextHubManager { * @see ContextHubMessage * * @return int 0 on success, -1 otherwise + * + * @deprecated Use {@link android.hardware.location.ContextHubClient#sendMessageToNanoApp( + * NanoAppMessage)} instead, after creating a + * {@link android.hardware.location.ContextHubClient} with + * {@link #createClient(ContextHubInfo, ContextHubClientCallback, Executor)} + * or {@link #createClient(ContextHubInfo, ContextHubClientCallback)}. */ + @Deprecated @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) - public int sendMessage(int hubHandle, int nanoAppHandle, ContextHubMessage message) { + public int sendMessage(int hubHandle, int nanoAppHandle, @NonNull ContextHubMessage message) { try { return mService.sendMessage(hubHandle, nanoAppHandle, message); } catch (RemoteException e) { @@ -266,11 +304,9 @@ public final class ContextHubManager { * @return the list of ContextHubInfo objects * * @see ContextHubInfo - * - * @hide */ @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) - public List<ContextHubInfo> getContextHubs() { + @NonNull public List<ContextHubInfo> getContextHubs() { try { return mService.getContextHubs(); } catch (RemoteException e) { @@ -342,13 +378,16 @@ public final class ContextHubManager { * * @return the ContextHubTransaction of the request * - * @see NanoAppBinary + * @throws NullPointerException if hubInfo or NanoAppBinary is null * - * @hide + * @see NanoAppBinary */ @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) - public ContextHubTransaction<Void> loadNanoApp( - ContextHubInfo hubInfo, NanoAppBinary appBinary) { + @NonNull public ContextHubTransaction<Void> loadNanoApp( + @NonNull ContextHubInfo hubInfo, @NonNull NanoAppBinary appBinary) { + Preconditions.checkNotNull(hubInfo, "ContextHubInfo cannot be null"); + Preconditions.checkNotNull(appBinary, "NanoAppBinary cannot be null"); + ContextHubTransaction<Void> transaction = new ContextHubTransaction<>(ContextHubTransaction.TYPE_LOAD_NANOAPP); IContextHubTransactionCallback callback = createTransactionCallback(transaction); @@ -370,10 +409,13 @@ public final class ContextHubManager { * * @return the ContextHubTransaction of the request * - * @hide + * @throws NullPointerException if hubInfo is null */ @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) - public ContextHubTransaction<Void> unloadNanoApp(ContextHubInfo hubInfo, long nanoAppId) { + @NonNull public ContextHubTransaction<Void> unloadNanoApp( + @NonNull ContextHubInfo hubInfo, long nanoAppId) { + Preconditions.checkNotNull(hubInfo, "ContextHubInfo cannot be null"); + ContextHubTransaction<Void> transaction = new ContextHubTransaction<>(ContextHubTransaction.TYPE_UNLOAD_NANOAPP); IContextHubTransactionCallback callback = createTransactionCallback(transaction); @@ -395,10 +437,13 @@ public final class ContextHubManager { * * @return the ContextHubTransaction of the request * - * @hide + * @throws NullPointerException if hubInfo is null */ @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) - public ContextHubTransaction<Void> enableNanoApp(ContextHubInfo hubInfo, long nanoAppId) { + @NonNull public ContextHubTransaction<Void> enableNanoApp( + @NonNull ContextHubInfo hubInfo, long nanoAppId) { + Preconditions.checkNotNull(hubInfo, "ContextHubInfo cannot be null"); + ContextHubTransaction<Void> transaction = new ContextHubTransaction<>(ContextHubTransaction.TYPE_ENABLE_NANOAPP); IContextHubTransactionCallback callback = createTransactionCallback(transaction); @@ -420,10 +465,13 @@ public final class ContextHubManager { * * @return the ContextHubTransaction of the request * - * @hide + * @throws NullPointerException if hubInfo is null */ @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) - public ContextHubTransaction<Void> disableNanoApp(ContextHubInfo hubInfo, long nanoAppId) { + @NonNull public ContextHubTransaction<Void> disableNanoApp( + @NonNull ContextHubInfo hubInfo, long nanoAppId) { + Preconditions.checkNotNull(hubInfo, "ContextHubInfo cannot be null"); + ContextHubTransaction<Void> transaction = new ContextHubTransaction<>(ContextHubTransaction.TYPE_DISABLE_NANOAPP); IContextHubTransactionCallback callback = createTransactionCallback(transaction); @@ -444,10 +492,13 @@ public final class ContextHubManager { * * @return the ContextHubTransaction of the request * - * @hide + * @throws NullPointerException if hubInfo is null */ @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) - public ContextHubTransaction<List<NanoAppState>> queryNanoApps(ContextHubInfo hubInfo) { + @NonNull public ContextHubTransaction<List<NanoAppState>> queryNanoApps( + @NonNull ContextHubInfo hubInfo) { + Preconditions.checkNotNull(hubInfo, "ContextHubInfo cannot be null"); + ContextHubTransaction<List<NanoAppState>> transaction = new ContextHubTransaction<>(ContextHubTransaction.TYPE_QUERY_NANOAPPS); IContextHubTransactionCallback callback = createQueryCallback(transaction); @@ -469,9 +520,14 @@ public final class ContextHubManager { * @see Callback * * @return int 0 on success, -1 otherwise + * + * @deprecated Use {@link #createClient(ContextHubInfo, ContextHubClientCallback, Executor)} + * or {@link #createClient(ContextHubInfo, ContextHubClientCallback)} instead to + * register a {@link android.hardware.location.ContextHubClientCallback}. */ + @Deprecated @SuppressLint("Doclava125") - public int registerCallback(Callback callback) { + public int registerCallback(@NonNull Callback callback) { return registerCallback(callback, null); } @@ -498,7 +554,12 @@ public final class ContextHubManager { * @see Callback * * @return int 0 on success, -1 otherwise + * + * @deprecated Use {@link #createClient(ContextHubInfo, ContextHubClientCallback, Executor)} + * or {@link #createClient(ContextHubInfo, ContextHubClientCallback)} instead to + * register a {@link android.hardware.location.ContextHubClientCallback}. */ + @Deprecated @SuppressLint("Doclava125") public int registerCallback(Callback callback, Handler handler) { synchronized(this) { @@ -515,47 +576,48 @@ public final class ContextHubManager { /** * Creates an interface to the ContextHubClient to send down to the service. * + * @param client the ContextHubClient object associated with this callback * @param callback the callback to invoke at the client process * @param executor the executor to invoke callbacks for this client * * @return the callback interface */ private IContextHubClientCallback createClientCallback( - ContextHubClientCallback callback, Executor executor) { + ContextHubClient client, ContextHubClientCallback callback, Executor executor) { return new IContextHubClientCallback.Stub() { @Override public void onMessageFromNanoApp(NanoAppMessage message) { - executor.execute(() -> callback.onMessageFromNanoApp(message)); + executor.execute(() -> callback.onMessageFromNanoApp(client, message)); } @Override public void onHubReset() { - executor.execute(() -> callback.onHubReset()); + executor.execute(() -> callback.onHubReset(client)); } @Override public void onNanoAppAborted(long nanoAppId, int abortCode) { - executor.execute(() -> callback.onNanoAppAborted(nanoAppId, abortCode)); + executor.execute(() -> callback.onNanoAppAborted(client, nanoAppId, abortCode)); } @Override public void onNanoAppLoaded(long nanoAppId) { - executor.execute(() -> callback.onNanoAppLoaded(nanoAppId)); + executor.execute(() -> callback.onNanoAppLoaded(client, nanoAppId)); } @Override public void onNanoAppUnloaded(long nanoAppId) { - executor.execute(() -> callback.onNanoAppUnloaded(nanoAppId)); + executor.execute(() -> callback.onNanoAppUnloaded(client, nanoAppId)); } @Override public void onNanoAppEnabled(long nanoAppId) { - executor.execute(() -> callback.onNanoAppEnabled(nanoAppId)); + executor.execute(() -> callback.onNanoAppEnabled(client, nanoAppId)); } @Override public void onNanoAppDisabled(long nanoAppId) { - executor.execute(() -> callback.onNanoAppDisabled(nanoAppId)); + executor.execute(() -> callback.onNanoAppDisabled(client, nanoAppId)); } }; } @@ -574,31 +636,30 @@ public final class ContextHubManager { * * @throws IllegalArgumentException if hubInfo does not represent a valid hub * @throws IllegalStateException if there were too many registered clients at the service - * @throws NullPointerException if callback or hubInfo is null + * @throws NullPointerException if callback, hubInfo, or executor is null * - * @hide * @see ContextHubClientCallback */ @NonNull public ContextHubClient createClient( @NonNull ContextHubInfo hubInfo, @NonNull ContextHubClientCallback callback, @NonNull @CallbackExecutor Executor executor) { - if (callback == null) { - throw new NullPointerException("Callback cannot be null"); - } - if (hubInfo == null) { - throw new NullPointerException("Hub info cannot be null"); - } + Preconditions.checkNotNull(callback, "Callback cannot be null"); + Preconditions.checkNotNull(hubInfo, "ContextHubInfo cannot be null"); + Preconditions.checkNotNull(executor, "Executor cannot be null"); - IContextHubClientCallback clientInterface = createClientCallback(callback, executor); + ContextHubClient client = new ContextHubClient(hubInfo); + IContextHubClientCallback clientInterface = createClientCallback( + client, callback, executor); - IContextHubClient client; + IContextHubClient clientProxy; try { - client = mService.createClient(clientInterface, hubInfo.getId()); + clientProxy = mService.createClient(clientInterface, hubInfo.getId()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } - return new ContextHubClient(client, clientInterface, hubInfo); + client.setClientProxy(clientProxy); + return client; } /** @@ -612,7 +673,7 @@ public final class ContextHubManager { * @throws IllegalArgumentException if hubInfo does not represent a valid hub * @throws IllegalStateException if there were too many registered clients at the service * @throws NullPointerException if callback or hubInfo is null - * @hide + * * @see ContextHubClientCallback */ @NonNull public ContextHubClient createClient( @@ -628,9 +689,13 @@ public final class ContextHubManager { * @param callback method to deregister * * @return int 0 on success, -1 otherwise + * + * @deprecated Use {@link android.hardware.location.ContextHubClient#close()} to unregister + * a {@link android.hardware.location.ContextHubClientCallback}. */ @SuppressLint("Doclava125") - public int unregisterCallback(Callback callback) { + @Deprecated + public int unregisterCallback(@NonNull Callback callback) { synchronized(this) { if (callback != mCallback) { Log.w(TAG, "Cannot recognize callback!"); diff --git a/core/java/android/hardware/location/ContextHubTransaction.java b/core/java/android/hardware/location/ContextHubTransaction.java index a1b743da785b..bc7efef55bcf 100644 --- a/core/java/android/hardware/location/ContextHubTransaction.java +++ b/core/java/android/hardware/location/ContextHubTransaction.java @@ -18,9 +18,12 @@ package android.hardware.location; import android.annotation.CallbackExecutor; import android.annotation.IntDef; import android.annotation.NonNull; +import android.annotation.SystemApi; import android.os.Handler; import android.os.HandlerExecutor; +import com.android.internal.util.Preconditions; + import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.concurrent.CountDownLatch; @@ -35,17 +38,19 @@ import java.util.concurrent.TimeoutException; * through the ContextHubManager APIs. The caller can either retrieve the result * synchronously through a blocking call ({@link #waitForResponse(long, TimeUnit)}) or * asynchronously through a user-defined listener - * ({@link #setOnCompleteListener(Listener, Executor)} )}). + * ({@link #setOnCompleteListener(OnCompleteListener, Executor)} )}). * * @param <T> the type of the contents in the transaction response * * @hide */ +@SystemApi public class ContextHubTransaction<T> { private static final String TAG = "ContextHubTransaction"; /** * Constants describing the type of a transaction through the Context Hub Service. + * {@hide} */ @Retention(RetentionPolicy.SOURCE) @IntDef(prefix = { "TYPE_" }, value = { @@ -65,6 +70,7 @@ public class ContextHubTransaction<T> { /** * Constants describing the result of a transaction or request through the Context Hub Service. + * {@hide} */ @Retention(RetentionPolicy.SOURCE) @IntDef(prefix = { "RESULT_" }, value = { @@ -72,7 +78,7 @@ public class ContextHubTransaction<T> { RESULT_FAILED_UNKNOWN, RESULT_FAILED_BAD_PARAMS, RESULT_FAILED_UNINITIALIZED, - RESULT_FAILED_PENDING, + RESULT_FAILED_BUSY, RESULT_FAILED_AT_HUB, RESULT_FAILED_TIMEOUT, RESULT_FAILED_SERVICE_INTERNAL_FAILURE, @@ -95,7 +101,7 @@ public class ContextHubTransaction<T> { /** * Failure mode when there are too many transactions pending. */ - public static final int RESULT_FAILED_PENDING = 4; + public static final int RESULT_FAILED_BUSY = 4; /** * Failure mode when the request went through, but failed asynchronously at the hub. */ @@ -151,7 +157,7 @@ public class ContextHubTransaction<T> { * @param <L> the type of the contents in the transaction response */ @FunctionalInterface - public interface Listener<L> { + public interface OnCompleteListener<L> { /** * The listener function to invoke when the transaction completes. * @@ -181,7 +187,7 @@ public class ContextHubTransaction<T> { /* * The listener to be invoked when the transaction completes. */ - private ContextHubTransaction.Listener<T> mListener = null; + private ContextHubTransaction.OnCompleteListener<T> mListener = null; /* * Synchronization latch used to block on response. @@ -272,8 +278,8 @@ public class ContextHubTransaction<T> { * A transaction can be invalidated if the process owning the transaction is no longer active * and the reference to this object is lost. * - * This method or {@link #setOnCompleteListener(ContextHubTransaction.Listener)} can only be - * invoked once, or an IllegalStateException will be thrown. + * This method or {@link #setOnCompleteListener(ContextHubTransaction.OnCompleteListener)} can + * only be invoked once, or an IllegalStateException will be thrown. * * @param listener the listener to be invoked upon completion * @param executor the executor to invoke the callback @@ -282,15 +288,11 @@ public class ContextHubTransaction<T> { * @throws NullPointerException if the callback or handler is null */ public void setOnCompleteListener( - @NonNull ContextHubTransaction.Listener<T> listener, + @NonNull ContextHubTransaction.OnCompleteListener<T> listener, @NonNull @CallbackExecutor Executor executor) { synchronized (this) { - if (listener == null) { - throw new NullPointerException("Listener cannot be null"); - } - if (executor == null) { - throw new NullPointerException("Executor cannot be null"); - } + Preconditions.checkNotNull(listener, "OnCompleteListener cannot be null"); + Preconditions.checkNotNull(executor, "Executor cannot be null"); if (mListener != null) { throw new IllegalStateException( "Cannot set ContextHubTransaction listener multiple times"); @@ -308,18 +310,19 @@ public class ContextHubTransaction<T> { /** * Sets the listener to be invoked invoked when the transaction completes. * - * Equivalent to {@link #setOnCompleteListener(ContextHubTransaction.Listener, Executor)} - * with the executor using the main thread's Looper. + * Equivalent to {@link #setOnCompleteListener(ContextHubTransaction.OnCompleteListener, + * Executor)} with the executor using the main thread's Looper. * - * This method or {@link #setOnCompleteListener(ContextHubTransaction.Listener, Executor)} - * can only be invoked once, or an IllegalStateException will be thrown. + * This method or {@link #setOnCompleteListener(ContextHubTransaction.OnCompleteListener, + * Executor)} can only be invoked once, or an IllegalStateException will be thrown. * * @param listener the listener to be invoked upon completion * * @throws IllegalStateException if this method is called multiple times * @throws NullPointerException if the callback is null */ - public void setOnCompleteListener(@NonNull ContextHubTransaction.Listener<T> listener) { + public void setOnCompleteListener( + @NonNull ContextHubTransaction.OnCompleteListener<T> listener) { setOnCompleteListener(listener, new HandlerExecutor(Handler.getMain())); } @@ -337,9 +340,7 @@ public class ContextHubTransaction<T> { */ /* package */ void setResponse(ContextHubTransaction.Response<T> response) { synchronized (this) { - if (response == null) { - throw new NullPointerException("Response cannot be null"); - } + Preconditions.checkNotNull(response, "Response cannot be null"); if (mIsResponseSet) { throw new IllegalStateException( "Cannot set response of ContextHubTransaction multiple times"); diff --git a/core/java/android/hardware/location/NanoAppBinary.java b/core/java/android/hardware/location/NanoAppBinary.java index 934e9e48c01a..ba01ca251321 100644 --- a/core/java/android/hardware/location/NanoAppBinary.java +++ b/core/java/android/hardware/location/NanoAppBinary.java @@ -15,6 +15,7 @@ */ package android.hardware.location; +import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; import android.util.Log; @@ -27,6 +28,7 @@ import java.util.Arrays; /** * @hide */ +@SystemApi public final class NanoAppBinary implements Parcelable { private static final String TAG = "NanoAppBinary"; diff --git a/core/java/android/hardware/location/NanoAppMessage.java b/core/java/android/hardware/location/NanoAppMessage.java index 202867490fb9..716a1946c540 100644 --- a/core/java/android/hardware/location/NanoAppMessage.java +++ b/core/java/android/hardware/location/NanoAppMessage.java @@ -15,6 +15,7 @@ */ package android.hardware.location; +import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; @@ -25,6 +26,7 @@ import android.os.Parcelable; * * @hide */ +@SystemApi public final class NanoAppMessage implements Parcelable { private long mNanoAppId; private int mMessageType; diff --git a/core/java/android/hardware/location/NanoAppState.java b/core/java/android/hardware/location/NanoAppState.java index 644031b034d5..d05277d484a6 100644 --- a/core/java/android/hardware/location/NanoAppState.java +++ b/core/java/android/hardware/location/NanoAppState.java @@ -15,6 +15,7 @@ */ package android.hardware.location; +import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; @@ -23,6 +24,7 @@ import android.os.Parcelable; * * @hide */ +@SystemApi public final class NanoAppState implements Parcelable { private long mNanoAppId; private int mNanoAppVersion; diff --git a/core/java/android/net/NetworkWatchlistManager.java b/core/java/android/net/NetworkWatchlistManager.java index 42e43c8aea1e..5425bf534ebd 100644 --- a/core/java/android/net/NetworkWatchlistManager.java +++ b/core/java/android/net/NetworkWatchlistManager.java @@ -59,8 +59,8 @@ public class NetworkWatchlistManager { /** * Report network watchlist records if necessary. * - * Watchlist report process will run summarize records into a single report, then the - * report will be processed by differential privacy framework and store it on disk. + * Watchlist report process will summarize records into a single report, then the + * report will be processed by differential privacy framework and stored on disk. * * @hide */ @@ -72,4 +72,18 @@ public class NetworkWatchlistManager { e.rethrowFromSystemServer(); } } + + /** + * Reload network watchlist. + * + * @hide + */ + public void reloadWatchlist() { + try { + mNetworkWatchlistManager.reloadWatchlist(); + } catch (RemoteException e) { + Log.e(TAG, "Unable to reload watchlist"); + e.rethrowFromSystemServer(); + } + } } diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java index 1e847c595df0..d4d74f438e5d 100644 --- a/core/java/android/os/BatteryStats.java +++ b/core/java/android/os/BatteryStats.java @@ -180,6 +180,11 @@ public abstract class BatteryStats implements Parcelable { public static final int FOREGROUND_SERVICE = 22; /** + * A constant indicating an aggregate wifi multicast timer + */ + public static final int WIFI_AGGREGATE_MULTICAST_ENABLED = 23; + + /** * Include all of the data in the stats, including previously saved data. */ public static final int STATS_SINCE_CHARGED = 0; @@ -2334,6 +2339,22 @@ public abstract class BatteryStats implements Parcelable { }; /** + * Returns total time for WiFi Multicast Wakelock timer. + * Note that this may be different from the sum of per uid timer values. + * + * {@hide} + */ + public abstract long getWifiMulticastWakelockTime(long elapsedRealtimeUs, int which); + + /** + * Returns total time for WiFi Multicast Wakelock timer + * Note that this may be different from the sum of per uid timer values. + * + * {@hide} + */ + public abstract int getWifiMulticastWakelockCount(int which); + + /** * Returns the time in microseconds that wifi has been on while the device was * running on battery. * @@ -3442,16 +3463,13 @@ public abstract class BatteryStats implements Parcelable { screenDozeTime / 1000); - // Calculate both wakelock and wifi multicast wakelock times across all uids. + // Calculate wakelock times across all uids. long fullWakeLockTimeTotal = 0; long partialWakeLockTimeTotal = 0; - long multicastWakeLockTimeTotalMicros = 0; - int multicastWakeLockCountTotal = 0; for (int iu = 0; iu < NU; iu++) { final Uid u = uidStats.valueAt(iu); - // First calculating the wakelock stats final ArrayMap<String, ? extends BatteryStats.Uid.Wakelock> wakelocks = u.getWakelockStats(); for (int iw=wakelocks.size()-1; iw>=0; iw--) { @@ -3469,13 +3487,6 @@ public abstract class BatteryStats implements Parcelable { rawRealtime, which); } } - - // Now calculating the wifi multicast wakelock stats - final Timer mcTimer = u.getMulticastWakelockStats(); - if (mcTimer != null) { - multicastWakeLockTimeTotalMicros += mcTimer.getTotalTimeLocked(rawRealtime, which); - multicastWakeLockCountTotal += mcTimer.getCountLocked(which); - } } // Dump network stats @@ -3592,6 +3603,9 @@ public abstract class BatteryStats implements Parcelable { dumpLine(pw, 0 /* uid */, category, WIFI_SIGNAL_STRENGTH_COUNT_DATA, args); // Dump Multicast total stats + final long multicastWakeLockTimeTotalMicros = + getWifiMulticastWakelockTime(rawRealtime, which); + final int multicastWakeLockCountTotal = getWifiMulticastWakelockCount(which); dumpLine(pw, 0 /* uid */, category, WIFI_MULTICAST_TOTAL_DATA, multicastWakeLockTimeTotalMicros / 1000, multicastWakeLockCountTotal); @@ -4456,18 +4470,15 @@ public abstract class BatteryStats implements Parcelable { pw.print(" Connectivity changes: "); pw.println(connChanges); } - // Calculate both wakelock and wifi multicast wakelock times across all uids. + // Calculate wakelock times across all uids. long fullWakeLockTimeTotalMicros = 0; long partialWakeLockTimeTotalMicros = 0; - long multicastWakeLockTimeTotalMicros = 0; - int multicastWakeLockCountTotal = 0; final ArrayList<TimerEntry> timers = new ArrayList<>(); for (int iu = 0; iu < NU; iu++) { final Uid u = uidStats.valueAt(iu); - // First calculate wakelock statistics final ArrayMap<String, ? extends BatteryStats.Uid.Wakelock> wakelocks = u.getWakelockStats(); for (int iw=wakelocks.size()-1; iw>=0; iw--) { @@ -4495,13 +4506,6 @@ public abstract class BatteryStats implements Parcelable { } } } - - // Next calculate wifi multicast wakelock statistics - final Timer mcTimer = u.getMulticastWakelockStats(); - if (mcTimer != null) { - multicastWakeLockTimeTotalMicros += mcTimer.getTotalTimeLocked(rawRealtime, which); - multicastWakeLockCountTotal += mcTimer.getCountLocked(which); - } } final long mobileRxTotalBytes = getNetworkActivityBytes(NETWORK_MOBILE_RX_DATA, which); @@ -4531,6 +4535,9 @@ public abstract class BatteryStats implements Parcelable { pw.println(sb.toString()); } + final long multicastWakeLockTimeTotalMicros = + getWifiMulticastWakelockTime(rawRealtime, which); + final int multicastWakeLockCountTotal = getWifiMulticastWakelockCount(which); if (multicastWakeLockTimeTotalMicros != 0) { sb.setLength(0); sb.append(prefix); @@ -7051,6 +7058,28 @@ public abstract class BatteryStats implements Parcelable { } } } + + for (int procState = 0; procState < Uid.NUM_PROCESS_STATE; ++procState) { + final long[] timesMs = u.getCpuFreqTimes(which, procState); + if (timesMs != null && timesMs.length == cpuFreqs.length) { + long[] screenOffTimesMs = u.getScreenOffCpuFreqTimes(which, procState); + if (screenOffTimesMs == null) { + screenOffTimesMs = new long[timesMs.length]; + } + final long procToken = proto.start(UidProto.Cpu.BY_PROCESS_STATE); + proto.write(UidProto.Cpu.ByProcessState.PROCESS_STATE, procState); + for (int ic = 0; ic < timesMs.length; ++ic) { + long cToken = proto.start(UidProto.Cpu.ByProcessState.BY_FREQUENCY); + proto.write(UidProto.Cpu.ByFrequency.FREQUENCY_INDEX, ic + 1); + proto.write(UidProto.Cpu.ByFrequency.TOTAL_DURATION_MS, + timesMs[ic]); + proto.write(UidProto.Cpu.ByFrequency.SCREEN_OFF_DURATION_MS, + screenOffTimesMs[ic]); + proto.end(cToken); + } + proto.end(procToken); + } + } proto.end(cpuToken); // Flashlight (FLASHLIGHT_DATA) @@ -7535,22 +7564,9 @@ public abstract class BatteryStats implements Parcelable { proto.end(mToken); // Wifi multicast wakelock total stats (WIFI_MULTICAST_WAKELOCK_TOTAL_DATA) - // Calculate multicast wakelock stats across all uids. - long multicastWakeLockTimeTotalUs = 0; - int multicastWakeLockCountTotal = 0; - - for (int iu = 0; iu < uidStats.size(); iu++) { - final Uid u = uidStats.valueAt(iu); - - final Timer mcTimer = u.getMulticastWakelockStats(); - - if (mcTimer != null) { - multicastWakeLockTimeTotalUs += - mcTimer.getTotalTimeLocked(rawRealtimeUs, which); - multicastWakeLockCountTotal += mcTimer.getCountLocked(which); - } - } - + final long multicastWakeLockTimeTotalUs = + getWifiMulticastWakelockTime(rawRealtimeUs, which); + final int multicastWakeLockCountTotal = getWifiMulticastWakelockCount(which); final long wmctToken = proto.start(SystemProto.WIFI_MULTICAST_WAKELOCK_TOTAL); proto.write(SystemProto.WifiMulticastWakelockTotal.DURATION_MS, multicastWakeLockTimeTotalUs / 1000); diff --git a/core/java/android/os/Handler.java b/core/java/android/os/Handler.java index 3ca1005b8c98..5c5e351d2eeb 100644 --- a/core/java/android/os/Handler.java +++ b/core/java/android/os/Handler.java @@ -388,6 +388,8 @@ public class Handler { * The runnable will be run on the thread to which this handler is attached. * * @param r The Runnable that will be executed. + * @param token An instance which can be used to cancel {@code r} via + * {@link #removeCallbacksAndMessages}. * @param uptimeMillis The absolute time at which the callback should run, * using the {@link android.os.SystemClock#uptimeMillis} time-base. * @@ -430,6 +432,32 @@ public class Handler { } /** + * Causes the Runnable r to be added to the message queue, to be run + * after the specified amount of time elapses. + * The runnable will be run on the thread to which this handler + * is attached. + * <b>The time-base is {@link android.os.SystemClock#uptimeMillis}.</b> + * Time spent in deep sleep will add an additional delay to execution. + * + * @param r The Runnable that will be executed. + * @param token An instance which can be used to cancel {@code r} via + * {@link #removeCallbacksAndMessages}. + * @param delayMillis The delay (in milliseconds) until the Runnable + * will be executed. + * + * @return Returns true if the Runnable was successfully placed in to the + * message queue. Returns false on failure, usually because the + * looper processing the message queue is exiting. Note that a + * result of true does not mean the Runnable will be processed -- + * if the looper is quit before the delivery time of the message + * occurs then the message will be dropped. + */ + public final boolean postDelayed(Runnable r, Object token, long delayMillis) + { + return sendMessageDelayed(getPostMessage(r, token), delayMillis); + } + + /** * Posts a message to an object that implements Runnable. * Causes the Runnable r to executed on the next iteration through the * message queue. The runnable will be run on the thread to which this diff --git a/core/java/android/os/IStatsManager.aidl b/core/java/android/os/IStatsManager.aidl index 3db12ed0815f..29812e8ab06e 100644 --- a/core/java/android/os/IStatsManager.aidl +++ b/core/java/android/os/IStatsManager.aidl @@ -71,7 +71,7 @@ interface IStatsManager { * Fetches data for the specified configuration key. Returns a byte array representing proto * wire-encoded of ConfigMetricsReportList. */ - byte[] getData(in String key); + byte[] getData(in long key); /** * Fetches metadata across statsd. Returns byte array representing wire-encoded proto. @@ -86,7 +86,7 @@ interface IStatsManager { * * Returns if this configuration was correctly registered. */ - boolean addConfiguration(in String configKey, in byte[] config, in String pkg, in String cls); + boolean addConfiguration(in long configKey, in byte[] config, in String pkg, in String cls); /** * Removes the configuration with the matching config key. No-op if this config key does not @@ -94,5 +94,5 @@ interface IStatsManager { * * Returns if this configuration key was removed. */ - boolean removeConfiguration(in String configKey); + boolean removeConfiguration(in long configKey); } diff --git a/core/java/android/os/WorkSource.java b/core/java/android/os/WorkSource.java index e9d4fe82cec2..401b4a36a743 100644 --- a/core/java/android/os/WorkSource.java +++ b/core/java/android/os/WorkSource.java @@ -853,11 +853,11 @@ public class WorkSource implements Parcelable { } /** - * Return the UID to which this WorkChain should be attributed to, i.e, the UID performing - * the actual work. + * Return the UID to which this WorkChain should be attributed to, i.e, the UID that + * initiated the work and not the UID performing it. */ public int getAttributionUid() { - return mUids[mSize - 1]; + return mUids[0]; } // TODO: The following three trivial getters are purely for testing and will be removed @@ -1058,6 +1058,25 @@ public class WorkSource implements Parcelable { } proto.end(contentProto); } + + if (mChains != null) { + for (int i = 0; i < mChains.size(); i++) { + final WorkChain wc = mChains.get(i); + final long workChain = proto.start(WorkSourceProto.WORK_CHAINS); + + final String[] tags = wc.getTags(); + final int[] uids = wc.getUids(); + for (int j = 0; j < tags.length; j++) { + final long contentProto = proto.start(WorkSourceProto.WORK_SOURCE_CONTENTS); + proto.write(WorkSourceProto.WorkSourceContentProto.UID, uids[j]); + proto.write(WorkSourceProto.WorkSourceContentProto.NAME, tags[j]); + proto.end(contentProto); + } + + proto.end(workChain); + } + } + proto.end(workSourceToken); } diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java index 9833fe15dd3c..4c587a836de8 100644 --- a/core/java/android/os/storage/StorageManager.java +++ b/core/java/android/os/storage/StorageManager.java @@ -1115,12 +1115,14 @@ public class StorageManager { /** {@hide} */ public static Pair<String, Long> getPrimaryStoragePathAndSize() { return Pair.create(null, - FileUtils.roundStorageSize(Environment.getDataDirectory().getTotalSpace())); + FileUtils.roundStorageSize(Environment.getDataDirectory().getTotalSpace() + + Environment.getRootDirectory().getTotalSpace())); } /** {@hide} */ public long getPrimaryStorageSize() { - return FileUtils.roundStorageSize(Environment.getDataDirectory().getTotalSpace()); + return FileUtils.roundStorageSize(Environment.getDataDirectory().getTotalSpace() + + Environment.getRootDirectory().getTotalSpace()); } /** {@hide} */ diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index c57eba26f30c..2f8651416057 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -5778,6 +5778,14 @@ public final class Settings { "touch_exploration_granted_accessibility_services"; /** + * Uri of the slice that's presented on the keyguard. + * Defaults to a slice with the date and next alarm. + * + * @hide + */ + public static final String KEYGUARD_SLICE_URI = "keyguard_slice_uri"; + + /** * Whether to speak passwords while in accessibility mode. * * @deprecated The speaking of passwords is controlled by individual accessibility services. @@ -7252,8 +7260,11 @@ public final class Settings { * full_backup_interval_milliseconds (long) * full_backup_require_charging (boolean) * full_backup_required_network_type (int) + * backup_finished_notification_receivers (String[]) * </pre> * + * backup_finished_notification_receivers uses ":" as delimeter for values. + * * <p> * Type: string * @hide diff --git a/core/java/android/security/recoverablekeystore/RecoverableKeyStoreLoader.java b/core/java/android/security/recoverablekeystore/RecoverableKeyStoreLoader.java index 72a138a629cf..0cf8da5b3a86 100644 --- a/core/java/android/security/recoverablekeystore/RecoverableKeyStoreLoader.java +++ b/core/java/android/security/recoverablekeystore/RecoverableKeyStoreLoader.java @@ -426,7 +426,7 @@ public class RecoverableKeyStoreLoader { * Imports keys. * * @param sessionId Id for recovery session, same as in - * {@link #startRecoverySession(String, byte[], byte[], byte[], List)} on}. + * {@link #startRecoverySession(String, byte[], byte[], byte[], List)}. * @param recoveryKeyBlob Recovery blob encrypted by symmetric key generated for this session. * @param applicationKeys Application keys. Key material can be decrypted using recoveryKeyBlob * and session. KeyStore only uses package names from the application info in {@link diff --git a/core/java/android/service/euicc/EuiccService.java b/core/java/android/service/euicc/EuiccService.java index df0842f7fb0d..fb530074d5b0 100644 --- a/core/java/android/service/euicc/EuiccService.java +++ b/core/java/android/service/euicc/EuiccService.java @@ -23,6 +23,7 @@ import android.os.IBinder; import android.os.RemoteException; import android.telephony.euicc.DownloadableSubscription; import android.telephony.euicc.EuiccInfo; +import android.telephony.euicc.EuiccManager.OtaStatus; import android.util.ArraySet; import java.util.concurrent.LinkedBlockingQueue; @@ -203,6 +204,16 @@ public abstract class EuiccService extends Service { public abstract String onGetEid(int slotId); /** + * Return the status of OTA update. + * + * @param slotId ID of the SIM slot to use for the operation. This is currently not populated + * but is here to future-proof the APIs. + * @return The status of Euicc OTA update. + * @see android.telephony.euicc.EuiccManager#getOtaStatus + */ + public abstract @OtaStatus int onGetOtaStatus(int slotId); + + /** * Populate {@link DownloadableSubscription} metadata for the given downloadable subscription. * * @param slotId ID of the SIM slot to use for the operation. This is currently not populated @@ -385,6 +396,21 @@ public abstract class EuiccService extends Service { } @Override + public void getOtaStatus(int slotId, IGetOtaStatusCallback callback) { + mExecutor.execute(new Runnable() { + @Override + public void run() { + int status = EuiccService.this.onGetOtaStatus(slotId); + try { + callback.onSuccess(status); + } catch (RemoteException e) { + // Can't communicate with the phone process; ignore. + } + } + }); + } + + @Override public void getDownloadableSubscriptionMetadata(int slotId, DownloadableSubscription subscription, boolean forceDeactivateSim, diff --git a/core/java/android/service/euicc/IEuiccService.aidl b/core/java/android/service/euicc/IEuiccService.aidl index e10dd8cdf616..a24e5c35c1cb 100644 --- a/core/java/android/service/euicc/IEuiccService.aidl +++ b/core/java/android/service/euicc/IEuiccService.aidl @@ -24,6 +24,7 @@ import android.service.euicc.IGetDownloadableSubscriptionMetadataCallback; import android.service.euicc.IGetEidCallback; import android.service.euicc.IGetEuiccInfoCallback; import android.service.euicc.IGetEuiccProfileInfoListCallback; +import android.service.euicc.IGetOtaStatusCallback; import android.service.euicc.IRetainSubscriptionsForFactoryResetCallback; import android.service.euicc.ISwitchToSubscriptionCallback; import android.service.euicc.IUpdateSubscriptionNicknameCallback; @@ -37,6 +38,7 @@ oneway interface IEuiccService { void getDownloadableSubscriptionMetadata(int slotId, in DownloadableSubscription subscription, boolean forceDeactivateSim, in IGetDownloadableSubscriptionMetadataCallback callback); void getEid(int slotId, in IGetEidCallback callback); + void getOtaStatus(int slotId, in IGetOtaStatusCallback callback); void getEuiccProfileInfoList(int slotId, in IGetEuiccProfileInfoListCallback callback); void getDefaultDownloadableSubscriptionList(int slotId, boolean forceDeactivateSim, in IGetDefaultDownloadableSubscriptionListCallback callback); diff --git a/core/java/android/service/euicc/IGetOtaStatusCallback.aidl b/core/java/android/service/euicc/IGetOtaStatusCallback.aidl new file mode 100644 index 000000000000..f6678889ccc7 --- /dev/null +++ b/core/java/android/service/euicc/IGetOtaStatusCallback.aidl @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2017 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 android.service.euicc; + +/** @hide */ +oneway interface IGetOtaStatusCallback { + void onSuccess(int status); +}
\ No newline at end of file diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java index bfb513092c31..d31bc1fbda76 100644 --- a/core/java/android/util/FeatureFlagUtils.java +++ b/core/java/android/util/FeatureFlagUtils.java @@ -38,7 +38,6 @@ public class FeatureFlagUtils { static { DEFAULT_FLAGS = new HashMap<>(); DEFAULT_FLAGS.put("device_info_v2", "true"); - DEFAULT_FLAGS.put("new_settings_suggestion", "true"); DEFAULT_FLAGS.put("settings_search_v2", "true"); DEFAULT_FLAGS.put("settings_app_info_v2", "false"); DEFAULT_FLAGS.put("settings_connected_device_v2", "true"); diff --git a/core/java/android/util/StatsManager.java b/core/java/android/util/StatsManager.java index 26a3c361e8c1..c25b272c11b9 100644 --- a/core/java/android/util/StatsManager.java +++ b/core/java/android/util/StatsManager.java @@ -53,7 +53,7 @@ public final class StatsManager { * @return true if successful */ @RequiresPermission(Manifest.permission.DUMP) - public boolean addConfiguration(String configKey, byte[] config, String pkg, String cls) { + public boolean addConfiguration(long configKey, byte[] config, String pkg, String cls) { synchronized (this) { try { IStatsManager service = getIStatsManagerLocked(); @@ -76,7 +76,7 @@ public final class StatsManager { * @return true if successful */ @RequiresPermission(Manifest.permission.DUMP) - public boolean removeConfiguration(String configKey) { + public boolean removeConfiguration(long configKey) { synchronized (this) { try { IStatsManager service = getIStatsManagerLocked(); @@ -100,7 +100,7 @@ public final class StatsManager { * @return Serialized ConfigMetricsReportList proto. Returns null on failure. */ @RequiresPermission(Manifest.permission.DUMP) - public byte[] getData(String configKey) { + public byte[] getData(long configKey) { synchronized (this) { try { IStatsManager service = getIStatsManagerLocked(); diff --git a/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java b/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java index 0a54f3a59a7d..530937e720a7 100644 --- a/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java +++ b/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java @@ -16,6 +16,21 @@ package android.util.apk; +import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_DSA_WITH_SHA256; +import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_ECDSA_WITH_SHA256; +import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_ECDSA_WITH_SHA512; +import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256; +import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512; +import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_RSA_PSS_WITH_SHA256; +import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_RSA_PSS_WITH_SHA512; +import static android.util.apk.ApkSigningBlockUtils.compareSignatureAlgorithm; +import static android.util.apk.ApkSigningBlockUtils.getContentDigestAlgorithmJcaDigestAlgorithm; +import static android.util.apk.ApkSigningBlockUtils.getLengthPrefixedSlice; +import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmContentDigestAlgorithm; +import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmJcaKeyAlgorithm; +import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmJcaSignatureAlgorithm; +import static android.util.apk.ApkSigningBlockUtils.readLengthPrefixedByteArray; + import android.util.ArrayMap; import android.util.Pair; @@ -23,56 +38,47 @@ import java.io.ByteArrayInputStream; import java.io.FileDescriptor; import java.io.IOException; import java.io.RandomAccessFile; -import java.math.BigInteger; import java.nio.BufferUnderflowException; import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.security.DigestException; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.KeyFactory; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; -import java.security.NoSuchProviderException; -import java.security.Principal; import java.security.PublicKey; import java.security.Signature; import java.security.SignatureException; -import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateException; -import java.security.cert.CertificateExpiredException; import java.security.cert.CertificateFactory; -import java.security.cert.CertificateNotYetValidException; import java.security.cert.X509Certificate; import java.security.spec.AlgorithmParameterSpec; import java.security.spec.InvalidKeySpecException; -import java.security.spec.MGF1ParameterSpec; -import java.security.spec.PSSParameterSpec; import java.security.spec.X509EncodedKeySpec; import java.util.ArrayList; import java.util.Arrays; -import java.util.Date; import java.util.List; import java.util.Map; -import java.util.Set; /** * APK Signature Scheme v2 verifier. * + * <p>APK Signature Scheme v2 is a whole-file signature scheme which aims to protect every single + * bit of the APK, as opposed to the JAR Signature Scheme which protects only the names and + * uncompressed contents of ZIP entries. + * + * @see <a href="https://source.android.com/security/apksigning/v2.html">APK Signature Scheme v2</a> + * * @hide for internal use only. */ public class ApkSignatureSchemeV2Verifier { /** - * {@code .SF} file header section attribute indicating that the APK is signed not just with - * JAR signature scheme but also with APK Signature Scheme v2 or newer. This attribute - * facilitates v2 signature stripping detection. - * - * <p>The attribute contains a comma-separated set of signature scheme IDs. + * ID of this signature scheme as used in X-Android-APK-Signed header used in JAR signing. */ - public static final String SF_ATTRIBUTE_ANDROID_APK_SIGNED_NAME = "X-Android-APK-Signed"; public static final int SF_ATTRIBUTE_ANDROID_APK_SIGNED_ID = 2; + private static final int APK_SIGNATURE_SCHEME_V2_BLOCK_ID = 0x7109871a; + /** * Returns {@code true} if the provided APK contains an APK Signature Scheme V2 signature. * @@ -103,7 +109,7 @@ public class ApkSignatureSchemeV2Verifier { /** * Returns the certificates associated with each signer for the given APK without verification. * This method is dangerous and should not be used, unless the caller is absolutely certain the - * APK is trusted. Specifically, verification is only done for the APK Signature Scheme V2 + * APK is trusted. Specifically, verification is only done for the APK Signature Scheme v2 * Block while gathering signer information. The APK contents are not verified. * * @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v2. @@ -120,6 +126,7 @@ public class ApkSignatureSchemeV2Verifier { return verify(apk, verifyIntegrity); } } + /** * Verifies APK Signature Scheme v2 signatures of the provided APK and returns the certificates * associated with each signer. @@ -144,30 +151,7 @@ public class ApkSignatureSchemeV2Verifier { */ private static SignatureInfo findSignature(RandomAccessFile apk) throws IOException, SignatureNotFoundException { - // Find the ZIP End of Central Directory (EoCD) record. - Pair<ByteBuffer, Long> eocdAndOffsetInFile = getEocd(apk); - ByteBuffer eocd = eocdAndOffsetInFile.first; - long eocdOffset = eocdAndOffsetInFile.second; - if (ZipUtils.isZip64EndOfCentralDirectoryLocatorPresent(apk, eocdOffset)) { - throw new SignatureNotFoundException("ZIP64 APK not supported"); - } - - // Find the APK Signing Block. The block immediately precedes the Central Directory. - long centralDirOffset = getCentralDirOffset(eocd, eocdOffset); - Pair<ByteBuffer, Long> apkSigningBlockAndOffsetInFile = - findApkSigningBlock(apk, centralDirOffset); - ByteBuffer apkSigningBlock = apkSigningBlockAndOffsetInFile.first; - long apkSigningBlockOffset = apkSigningBlockAndOffsetInFile.second; - - // Find the APK Signature Scheme v2 Block inside the APK Signing Block. - ByteBuffer apkSignatureSchemeV2Block = findApkSignatureSchemeV2Block(apkSigningBlock); - - return new SignatureInfo( - apkSignatureSchemeV2Block, - apkSigningBlockOffset, - centralDirOffset, - eocdOffset, - eocd); + return ApkSigningBlockUtils.findSignature(apk, APK_SIGNATURE_SCHEME_V2_BLOCK_ID); } /** @@ -218,7 +202,7 @@ public class ApkSignatureSchemeV2Verifier { } if (doVerifyIntegrity) { - verifyIntegrity( + ApkSigningBlockUtils.verifyIntegrity( contentDigests, apkFileDescriptor, signatureInfo.apkSigningBlockOffset, @@ -349,7 +333,8 @@ public class ApkSignatureSchemeV2Verifier { } catch (CertificateException e) { throw new SecurityException("Failed to decode certificate #" + certificateCount, e); } - certificate = new VerbatimX509Certificate(certificate, encodedCert); + certificate = new VerbatimX509Certificate( + certificate, encodedCert); certs.add(certificate); } @@ -363,235 +348,44 @@ public class ApkSignatureSchemeV2Verifier { "Public key mismatch between certificate and signature record"); } + ByteBuffer additionalAttrs = getLengthPrefixedSlice(signedData); + verifyAdditionalAttributes(additionalAttrs); + return certs.toArray(new X509Certificate[certs.size()]); } - private static void verifyIntegrity( - Map<Integer, byte[]> expectedDigests, - FileDescriptor apkFileDescriptor, - long apkSigningBlockOffset, - long centralDirOffset, - long eocdOffset, - ByteBuffer eocdBuf) throws SecurityException { - - if (expectedDigests.isEmpty()) { - throw new SecurityException("No digests provided"); - } - - // We need to verify the integrity of the following three sections of the file: - // 1. Everything up to the start of the APK Signing Block. - // 2. ZIP Central Directory. - // 3. ZIP End of Central Directory (EoCD). - // Each of these sections is represented as a separate DataSource instance below. - - // To handle large APKs, these sections are read in 1 MB chunks using memory-mapped I/O to - // avoid wasting physical memory. In most APK verification scenarios, the contents of the - // APK are already there in the OS's page cache and thus mmap does not use additional - // physical memory. - DataSource beforeApkSigningBlock = - new MemoryMappedFileDataSource(apkFileDescriptor, 0, apkSigningBlockOffset); - DataSource centralDir = - new MemoryMappedFileDataSource( - apkFileDescriptor, centralDirOffset, eocdOffset - centralDirOffset); + // Attribute to check whether a newer APK Signature Scheme signature was stripped + private static final int STRIPPING_PROTECTION_ATTR_ID = 0xbeeff00d; - // For the purposes of integrity verification, ZIP End of Central Directory's field Start of - // Central Directory must be considered to point to the offset of the APK Signing Block. - eocdBuf = eocdBuf.duplicate(); - eocdBuf.order(ByteOrder.LITTLE_ENDIAN); - ZipUtils.setZipEocdCentralDirectoryOffset(eocdBuf, apkSigningBlockOffset); - DataSource eocd = new ByteBufferDataSource(eocdBuf); - - int[] digestAlgorithms = new int[expectedDigests.size()]; - int digestAlgorithmCount = 0; - for (int digestAlgorithm : expectedDigests.keySet()) { - digestAlgorithms[digestAlgorithmCount] = digestAlgorithm; - digestAlgorithmCount++; - } - byte[][] actualDigests; - try { - actualDigests = - computeContentDigests( - digestAlgorithms, - new DataSource[] {beforeApkSigningBlock, centralDir, eocd}); - } catch (DigestException e) { - throw new SecurityException("Failed to compute digest(s) of contents", e); - } - for (int i = 0; i < digestAlgorithms.length; i++) { - int digestAlgorithm = digestAlgorithms[i]; - byte[] expectedDigest = expectedDigests.get(digestAlgorithm); - byte[] actualDigest = actualDigests[i]; - if (!MessageDigest.isEqual(expectedDigest, actualDigest)) { - throw new SecurityException( - getContentDigestAlgorithmJcaDigestAlgorithm(digestAlgorithm) - + " digest of contents did not verify"); + private static void verifyAdditionalAttributes(ByteBuffer attrs) + throws SecurityException, IOException { + while (attrs.hasRemaining()) { + ByteBuffer attr = getLengthPrefixedSlice(attrs); + if (attr.remaining() < 4) { + throw new IOException("Remaining buffer too short to contain additional attribute " + + "ID. Remaining: " + attr.remaining()); } - } - } - - private static byte[][] computeContentDigests( - int[] digestAlgorithms, - DataSource[] contents) throws DigestException { - // For each digest algorithm the result is computed as follows: - // 1. Each segment of contents is split into consecutive chunks of 1 MB in size. - // The final chunk will be shorter iff the length of segment is not a multiple of 1 MB. - // No chunks are produced for empty (zero length) segments. - // 2. The digest of each chunk is computed over the concatenation of byte 0xa5, the chunk's - // length in bytes (uint32 little-endian) and the chunk's contents. - // 3. The output digest is computed over the concatenation of the byte 0x5a, the number of - // chunks (uint32 little-endian) and the concatenation of digests of chunks of all - // segments in-order. - - long totalChunkCountLong = 0; - for (DataSource input : contents) { - totalChunkCountLong += getChunkCount(input.size()); - } - if (totalChunkCountLong >= Integer.MAX_VALUE / 1024) { - throw new DigestException("Too many chunks: " + totalChunkCountLong); - } - int totalChunkCount = (int) totalChunkCountLong; - - byte[][] digestsOfChunks = new byte[digestAlgorithms.length][]; - for (int i = 0; i < digestAlgorithms.length; i++) { - int digestAlgorithm = digestAlgorithms[i]; - int digestOutputSizeBytes = getContentDigestAlgorithmOutputSizeBytes(digestAlgorithm); - byte[] concatenationOfChunkCountAndChunkDigests = - new byte[5 + totalChunkCount * digestOutputSizeBytes]; - concatenationOfChunkCountAndChunkDigests[0] = 0x5a; - setUnsignedInt32LittleEndian( - totalChunkCount, - concatenationOfChunkCountAndChunkDigests, - 1); - digestsOfChunks[i] = concatenationOfChunkCountAndChunkDigests; - } - - byte[] chunkContentPrefix = new byte[5]; - chunkContentPrefix[0] = (byte) 0xa5; - int chunkIndex = 0; - MessageDigest[] mds = new MessageDigest[digestAlgorithms.length]; - for (int i = 0; i < digestAlgorithms.length; i++) { - String jcaAlgorithmName = - getContentDigestAlgorithmJcaDigestAlgorithm(digestAlgorithms[i]); - try { - mds[i] = MessageDigest.getInstance(jcaAlgorithmName); - } catch (NoSuchAlgorithmException e) { - throw new RuntimeException(jcaAlgorithmName + " digest not supported", e); - } - } - // TODO: Compute digests of chunks in parallel when beneficial. This requires some research - // into how to parallelize (if at all) based on the capabilities of the hardware on which - // this code is running and based on the size of input. - DataDigester digester = new MultipleDigestDataDigester(mds); - int dataSourceIndex = 0; - for (DataSource input : contents) { - long inputOffset = 0; - long inputRemaining = input.size(); - while (inputRemaining > 0) { - int chunkSize = (int) Math.min(inputRemaining, CHUNK_SIZE_BYTES); - setUnsignedInt32LittleEndian(chunkSize, chunkContentPrefix, 1); - for (int i = 0; i < mds.length; i++) { - mds[i].update(chunkContentPrefix); - } - try { - input.feedIntoDataDigester(digester, inputOffset, chunkSize); - } catch (IOException e) { - throw new DigestException( - "Failed to digest chunk #" + chunkIndex + " of section #" - + dataSourceIndex, - e); - } - for (int i = 0; i < digestAlgorithms.length; i++) { - int digestAlgorithm = digestAlgorithms[i]; - byte[] concatenationOfChunkCountAndChunkDigests = digestsOfChunks[i]; - int expectedDigestSizeBytes = - getContentDigestAlgorithmOutputSizeBytes(digestAlgorithm); - MessageDigest md = mds[i]; - int actualDigestSizeBytes = - md.digest( - concatenationOfChunkCountAndChunkDigests, - 5 + chunkIndex * expectedDigestSizeBytes, - expectedDigestSizeBytes); - if (actualDigestSizeBytes != expectedDigestSizeBytes) { - throw new RuntimeException( - "Unexpected output size of " + md.getAlgorithm() + " digest: " - + actualDigestSizeBytes); + int id = attr.getInt(); + switch (id) { + case STRIPPING_PROTECTION_ATTR_ID: + if (attr.remaining() < 4) { + throw new IOException("V2 Signature Scheme Stripping Protection Attribute " + + " value too small. Expected 4 bytes, but found " + + attr.remaining()); } - } - inputOffset += chunkSize; - inputRemaining -= chunkSize; - chunkIndex++; - } - dataSourceIndex++; - } - - byte[][] result = new byte[digestAlgorithms.length][]; - for (int i = 0; i < digestAlgorithms.length; i++) { - int digestAlgorithm = digestAlgorithms[i]; - byte[] input = digestsOfChunks[i]; - String jcaAlgorithmName = getContentDigestAlgorithmJcaDigestAlgorithm(digestAlgorithm); - MessageDigest md; - try { - md = MessageDigest.getInstance(jcaAlgorithmName); - } catch (NoSuchAlgorithmException e) { - throw new RuntimeException(jcaAlgorithmName + " digest not supported", e); + int vers = attr.getInt(); + if (vers == ApkSignatureSchemeV3Verifier.SF_ATTRIBUTE_ANDROID_APK_SIGNED_ID) { + throw new SecurityException("V2 signature indicates APK is signed using APK" + + " Signature Scheme v3, but none was found. Signature stripped?"); + } + break; + default: + // not the droid we're looking for, move along, move along. + break; } - byte[] output = md.digest(input); - result[i] = output; } - return result; + return; } - - /** - * Returns the ZIP End of Central Directory (EoCD) and its offset in the file. - * - * @throws IOException if an I/O error occurs while reading the file. - * @throws SignatureNotFoundException if the EoCD could not be found. - */ - private static Pair<ByteBuffer, Long> getEocd(RandomAccessFile apk) - throws IOException, SignatureNotFoundException { - Pair<ByteBuffer, Long> eocdAndOffsetInFile = - ZipUtils.findZipEndOfCentralDirectoryRecord(apk); - if (eocdAndOffsetInFile == null) { - throw new SignatureNotFoundException( - "Not an APK file: ZIP End of Central Directory record not found"); - } - return eocdAndOffsetInFile; - } - - private static long getCentralDirOffset(ByteBuffer eocd, long eocdOffset) - throws SignatureNotFoundException { - // Look up the offset of ZIP Central Directory. - long centralDirOffset = ZipUtils.getZipEocdCentralDirectoryOffset(eocd); - if (centralDirOffset > eocdOffset) { - throw new SignatureNotFoundException( - "ZIP Central Directory offset out of range: " + centralDirOffset - + ". ZIP End of Central Directory offset: " + eocdOffset); - } - long centralDirSize = ZipUtils.getZipEocdCentralDirectorySizeBytes(eocd); - if (centralDirOffset + centralDirSize != eocdOffset) { - throw new SignatureNotFoundException( - "ZIP Central Directory is not immediately followed by End of Central" - + " Directory"); - } - return centralDirOffset; - } - - private static final long getChunkCount(long inputSizeBytes) { - return (inputSizeBytes + CHUNK_SIZE_BYTES - 1) / CHUNK_SIZE_BYTES; - } - - private static final int CHUNK_SIZE_BYTES = 1024 * 1024; - - private static final int SIGNATURE_RSA_PSS_WITH_SHA256 = 0x0101; - private static final int SIGNATURE_RSA_PSS_WITH_SHA512 = 0x0102; - private static final int SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256 = 0x0103; - private static final int SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512 = 0x0104; - private static final int SIGNATURE_ECDSA_WITH_SHA256 = 0x0201; - private static final int SIGNATURE_ECDSA_WITH_SHA512 = 0x0202; - private static final int SIGNATURE_DSA_WITH_SHA256 = 0x0301; - - private static final int CONTENT_DIGEST_CHUNKED_SHA256 = 1; - private static final int CONTENT_DIGEST_CHUNKED_SHA512 = 2; - private static boolean isSupportedSignatureAlgorithm(int sigAlgorithm) { switch (sigAlgorithm) { case SIGNATURE_RSA_PSS_WITH_SHA256: @@ -606,519 +400,4 @@ public class ApkSignatureSchemeV2Verifier { return false; } } - - private static int compareSignatureAlgorithm(int sigAlgorithm1, int sigAlgorithm2) { - int digestAlgorithm1 = getSignatureAlgorithmContentDigestAlgorithm(sigAlgorithm1); - int digestAlgorithm2 = getSignatureAlgorithmContentDigestAlgorithm(sigAlgorithm2); - return compareContentDigestAlgorithm(digestAlgorithm1, digestAlgorithm2); - } - - private static int compareContentDigestAlgorithm(int digestAlgorithm1, int digestAlgorithm2) { - switch (digestAlgorithm1) { - case CONTENT_DIGEST_CHUNKED_SHA256: - switch (digestAlgorithm2) { - case CONTENT_DIGEST_CHUNKED_SHA256: - return 0; - case CONTENT_DIGEST_CHUNKED_SHA512: - return -1; - default: - throw new IllegalArgumentException( - "Unknown digestAlgorithm2: " + digestAlgorithm2); - } - case CONTENT_DIGEST_CHUNKED_SHA512: - switch (digestAlgorithm2) { - case CONTENT_DIGEST_CHUNKED_SHA256: - return 1; - case CONTENT_DIGEST_CHUNKED_SHA512: - return 0; - default: - throw new IllegalArgumentException( - "Unknown digestAlgorithm2: " + digestAlgorithm2); - } - default: - throw new IllegalArgumentException("Unknown digestAlgorithm1: " + digestAlgorithm1); - } - } - - private static int getSignatureAlgorithmContentDigestAlgorithm(int sigAlgorithm) { - switch (sigAlgorithm) { - case SIGNATURE_RSA_PSS_WITH_SHA256: - case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256: - case SIGNATURE_ECDSA_WITH_SHA256: - case SIGNATURE_DSA_WITH_SHA256: - return CONTENT_DIGEST_CHUNKED_SHA256; - case SIGNATURE_RSA_PSS_WITH_SHA512: - case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512: - case SIGNATURE_ECDSA_WITH_SHA512: - return CONTENT_DIGEST_CHUNKED_SHA512; - default: - throw new IllegalArgumentException( - "Unknown signature algorithm: 0x" - + Long.toHexString(sigAlgorithm & 0xffffffff)); - } - } - - private static String getContentDigestAlgorithmJcaDigestAlgorithm(int digestAlgorithm) { - switch (digestAlgorithm) { - case CONTENT_DIGEST_CHUNKED_SHA256: - return "SHA-256"; - case CONTENT_DIGEST_CHUNKED_SHA512: - return "SHA-512"; - default: - throw new IllegalArgumentException( - "Unknown content digest algorthm: " + digestAlgorithm); - } - } - - private static int getContentDigestAlgorithmOutputSizeBytes(int digestAlgorithm) { - switch (digestAlgorithm) { - case CONTENT_DIGEST_CHUNKED_SHA256: - return 256 / 8; - case CONTENT_DIGEST_CHUNKED_SHA512: - return 512 / 8; - default: - throw new IllegalArgumentException( - "Unknown content digest algorthm: " + digestAlgorithm); - } - } - - private static String getSignatureAlgorithmJcaKeyAlgorithm(int sigAlgorithm) { - switch (sigAlgorithm) { - case SIGNATURE_RSA_PSS_WITH_SHA256: - case SIGNATURE_RSA_PSS_WITH_SHA512: - case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256: - case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512: - return "RSA"; - case SIGNATURE_ECDSA_WITH_SHA256: - case SIGNATURE_ECDSA_WITH_SHA512: - return "EC"; - case SIGNATURE_DSA_WITH_SHA256: - return "DSA"; - default: - throw new IllegalArgumentException( - "Unknown signature algorithm: 0x" - + Long.toHexString(sigAlgorithm & 0xffffffff)); - } - } - - private static Pair<String, ? extends AlgorithmParameterSpec> - getSignatureAlgorithmJcaSignatureAlgorithm(int sigAlgorithm) { - switch (sigAlgorithm) { - case SIGNATURE_RSA_PSS_WITH_SHA256: - return Pair.create( - "SHA256withRSA/PSS", - new PSSParameterSpec( - "SHA-256", "MGF1", MGF1ParameterSpec.SHA256, 256 / 8, 1)); - case SIGNATURE_RSA_PSS_WITH_SHA512: - return Pair.create( - "SHA512withRSA/PSS", - new PSSParameterSpec( - "SHA-512", "MGF1", MGF1ParameterSpec.SHA512, 512 / 8, 1)); - case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256: - return Pair.create("SHA256withRSA", null); - case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512: - return Pair.create("SHA512withRSA", null); - case SIGNATURE_ECDSA_WITH_SHA256: - return Pair.create("SHA256withECDSA", null); - case SIGNATURE_ECDSA_WITH_SHA512: - return Pair.create("SHA512withECDSA", null); - case SIGNATURE_DSA_WITH_SHA256: - return Pair.create("SHA256withDSA", null); - default: - throw new IllegalArgumentException( - "Unknown signature algorithm: 0x" - + Long.toHexString(sigAlgorithm & 0xffffffff)); - } - } - - /** - * Returns new byte buffer whose content is a shared subsequence of this buffer's content - * between the specified start (inclusive) and end (exclusive) positions. As opposed to - * {@link ByteBuffer#slice()}, the returned buffer's byte order is the same as the source - * buffer's byte order. - */ - private static ByteBuffer sliceFromTo(ByteBuffer source, int start, int end) { - if (start < 0) { - throw new IllegalArgumentException("start: " + start); - } - if (end < start) { - throw new IllegalArgumentException("end < start: " + end + " < " + start); - } - int capacity = source.capacity(); - if (end > source.capacity()) { - throw new IllegalArgumentException("end > capacity: " + end + " > " + capacity); - } - int originalLimit = source.limit(); - int originalPosition = source.position(); - try { - source.position(0); - source.limit(end); - source.position(start); - ByteBuffer result = source.slice(); - result.order(source.order()); - return result; - } finally { - source.position(0); - source.limit(originalLimit); - source.position(originalPosition); - } - } - - /** - * Relative <em>get</em> method for reading {@code size} number of bytes from the current - * position of this buffer. - * - * <p>This method reads the next {@code size} bytes at this buffer's current position, - * returning them as a {@code ByteBuffer} with start set to 0, limit and capacity set to - * {@code size}, byte order set to this buffer's byte order; and then increments the position by - * {@code size}. - */ - private static ByteBuffer getByteBuffer(ByteBuffer source, int size) - throws BufferUnderflowException { - if (size < 0) { - throw new IllegalArgumentException("size: " + size); - } - int originalLimit = source.limit(); - int position = source.position(); - int limit = position + size; - if ((limit < position) || (limit > originalLimit)) { - throw new BufferUnderflowException(); - } - source.limit(limit); - try { - ByteBuffer result = source.slice(); - result.order(source.order()); - source.position(limit); - return result; - } finally { - source.limit(originalLimit); - } - } - - private static ByteBuffer getLengthPrefixedSlice(ByteBuffer source) throws IOException { - if (source.remaining() < 4) { - throw new IOException( - "Remaining buffer too short to contain length of length-prefixed field." - + " Remaining: " + source.remaining()); - } - int len = source.getInt(); - if (len < 0) { - throw new IllegalArgumentException("Negative length"); - } else if (len > source.remaining()) { - throw new IOException("Length-prefixed field longer than remaining buffer." - + " Field length: " + len + ", remaining: " + source.remaining()); - } - return getByteBuffer(source, len); - } - - private static byte[] readLengthPrefixedByteArray(ByteBuffer buf) throws IOException { - int len = buf.getInt(); - if (len < 0) { - throw new IOException("Negative length"); - } else if (len > buf.remaining()) { - throw new IOException("Underflow while reading length-prefixed value. Length: " + len - + ", available: " + buf.remaining()); - } - byte[] result = new byte[len]; - buf.get(result); - return result; - } - - private static void setUnsignedInt32LittleEndian(int value, byte[] result, int offset) { - result[offset] = (byte) (value & 0xff); - result[offset + 1] = (byte) ((value >>> 8) & 0xff); - result[offset + 2] = (byte) ((value >>> 16) & 0xff); - result[offset + 3] = (byte) ((value >>> 24) & 0xff); - } - - private static final long APK_SIG_BLOCK_MAGIC_HI = 0x3234206b636f6c42L; - private static final long APK_SIG_BLOCK_MAGIC_LO = 0x20676953204b5041L; - private static final int APK_SIG_BLOCK_MIN_SIZE = 32; - - private static final int APK_SIGNATURE_SCHEME_V2_BLOCK_ID = 0x7109871a; - - private static Pair<ByteBuffer, Long> findApkSigningBlock( - RandomAccessFile apk, long centralDirOffset) - throws IOException, SignatureNotFoundException { - // FORMAT: - // OFFSET DATA TYPE DESCRIPTION - // * @+0 bytes uint64: size in bytes (excluding this field) - // * @+8 bytes payload - // * @-24 bytes uint64: size in bytes (same as the one above) - // * @-16 bytes uint128: magic - - if (centralDirOffset < APK_SIG_BLOCK_MIN_SIZE) { - throw new SignatureNotFoundException( - "APK too small for APK Signing Block. ZIP Central Directory offset: " - + centralDirOffset); - } - // Read the magic and offset in file from the footer section of the block: - // * uint64: size of block - // * 16 bytes: magic - ByteBuffer footer = ByteBuffer.allocate(24); - footer.order(ByteOrder.LITTLE_ENDIAN); - apk.seek(centralDirOffset - footer.capacity()); - apk.readFully(footer.array(), footer.arrayOffset(), footer.capacity()); - if ((footer.getLong(8) != APK_SIG_BLOCK_MAGIC_LO) - || (footer.getLong(16) != APK_SIG_BLOCK_MAGIC_HI)) { - throw new SignatureNotFoundException( - "No APK Signing Block before ZIP Central Directory"); - } - // Read and compare size fields - long apkSigBlockSizeInFooter = footer.getLong(0); - if ((apkSigBlockSizeInFooter < footer.capacity()) - || (apkSigBlockSizeInFooter > Integer.MAX_VALUE - 8)) { - throw new SignatureNotFoundException( - "APK Signing Block size out of range: " + apkSigBlockSizeInFooter); - } - int totalSize = (int) (apkSigBlockSizeInFooter + 8); - long apkSigBlockOffset = centralDirOffset - totalSize; - if (apkSigBlockOffset < 0) { - throw new SignatureNotFoundException( - "APK Signing Block offset out of range: " + apkSigBlockOffset); - } - ByteBuffer apkSigBlock = ByteBuffer.allocate(totalSize); - apkSigBlock.order(ByteOrder.LITTLE_ENDIAN); - apk.seek(apkSigBlockOffset); - apk.readFully(apkSigBlock.array(), apkSigBlock.arrayOffset(), apkSigBlock.capacity()); - long apkSigBlockSizeInHeader = apkSigBlock.getLong(0); - if (apkSigBlockSizeInHeader != apkSigBlockSizeInFooter) { - throw new SignatureNotFoundException( - "APK Signing Block sizes in header and footer do not match: " - + apkSigBlockSizeInHeader + " vs " + apkSigBlockSizeInFooter); - } - return Pair.create(apkSigBlock, apkSigBlockOffset); - } - - private static ByteBuffer findApkSignatureSchemeV2Block(ByteBuffer apkSigningBlock) - throws SignatureNotFoundException { - checkByteOrderLittleEndian(apkSigningBlock); - // FORMAT: - // OFFSET DATA TYPE DESCRIPTION - // * @+0 bytes uint64: size in bytes (excluding this field) - // * @+8 bytes pairs - // * @-24 bytes uint64: size in bytes (same as the one above) - // * @-16 bytes uint128: magic - ByteBuffer pairs = sliceFromTo(apkSigningBlock, 8, apkSigningBlock.capacity() - 24); - - int entryCount = 0; - while (pairs.hasRemaining()) { - entryCount++; - if (pairs.remaining() < 8) { - throw new SignatureNotFoundException( - "Insufficient data to read size of APK Signing Block entry #" + entryCount); - } - long lenLong = pairs.getLong(); - if ((lenLong < 4) || (lenLong > Integer.MAX_VALUE)) { - throw new SignatureNotFoundException( - "APK Signing Block entry #" + entryCount - + " size out of range: " + lenLong); - } - int len = (int) lenLong; - int nextEntryPos = pairs.position() + len; - if (len > pairs.remaining()) { - throw new SignatureNotFoundException( - "APK Signing Block entry #" + entryCount + " size out of range: " + len - + ", available: " + pairs.remaining()); - } - int id = pairs.getInt(); - if (id == APK_SIGNATURE_SCHEME_V2_BLOCK_ID) { - return getByteBuffer(pairs, len - 4); - } - pairs.position(nextEntryPos); - } - - throw new SignatureNotFoundException( - "No APK Signature Scheme v2 block in APK Signing Block"); - } - - private static void checkByteOrderLittleEndian(ByteBuffer buffer) { - if (buffer.order() != ByteOrder.LITTLE_ENDIAN) { - throw new IllegalArgumentException("ByteBuffer byte order must be little endian"); - } - } - - /** - * {@link DataDigester} that updates multiple {@link MessageDigest}s whenever data is feeded. - */ - private static class MultipleDigestDataDigester implements DataDigester { - private final MessageDigest[] mMds; - - MultipleDigestDataDigester(MessageDigest[] mds) { - mMds = mds; - } - - @Override - public void consume(ByteBuffer buffer) { - buffer = buffer.slice(); - for (MessageDigest md : mMds) { - buffer.position(0); - md.update(buffer); - } - } - - @Override - public void finish() {} - } - - /** - * For legacy reasons we need to return exactly the original encoded certificate bytes, instead - * of letting the underlying implementation have a shot at re-encoding the data. - */ - private static class VerbatimX509Certificate extends WrappedX509Certificate { - private byte[] encodedVerbatim; - - public VerbatimX509Certificate(X509Certificate wrapped, byte[] encodedVerbatim) { - super(wrapped); - this.encodedVerbatim = encodedVerbatim; - } - - @Override - public byte[] getEncoded() throws CertificateEncodingException { - return encodedVerbatim; - } - } - - private static class WrappedX509Certificate extends X509Certificate { - private final X509Certificate wrapped; - - public WrappedX509Certificate(X509Certificate wrapped) { - this.wrapped = wrapped; - } - - @Override - public Set<String> getCriticalExtensionOIDs() { - return wrapped.getCriticalExtensionOIDs(); - } - - @Override - public byte[] getExtensionValue(String oid) { - return wrapped.getExtensionValue(oid); - } - - @Override - public Set<String> getNonCriticalExtensionOIDs() { - return wrapped.getNonCriticalExtensionOIDs(); - } - - @Override - public boolean hasUnsupportedCriticalExtension() { - return wrapped.hasUnsupportedCriticalExtension(); - } - - @Override - public void checkValidity() - throws CertificateExpiredException, CertificateNotYetValidException { - wrapped.checkValidity(); - } - - @Override - public void checkValidity(Date date) - throws CertificateExpiredException, CertificateNotYetValidException { - wrapped.checkValidity(date); - } - - @Override - public int getVersion() { - return wrapped.getVersion(); - } - - @Override - public BigInteger getSerialNumber() { - return wrapped.getSerialNumber(); - } - - @Override - public Principal getIssuerDN() { - return wrapped.getIssuerDN(); - } - - @Override - public Principal getSubjectDN() { - return wrapped.getSubjectDN(); - } - - @Override - public Date getNotBefore() { - return wrapped.getNotBefore(); - } - - @Override - public Date getNotAfter() { - return wrapped.getNotAfter(); - } - - @Override - public byte[] getTBSCertificate() throws CertificateEncodingException { - return wrapped.getTBSCertificate(); - } - - @Override - public byte[] getSignature() { - return wrapped.getSignature(); - } - - @Override - public String getSigAlgName() { - return wrapped.getSigAlgName(); - } - - @Override - public String getSigAlgOID() { - return wrapped.getSigAlgOID(); - } - - @Override - public byte[] getSigAlgParams() { - return wrapped.getSigAlgParams(); - } - - @Override - public boolean[] getIssuerUniqueID() { - return wrapped.getIssuerUniqueID(); - } - - @Override - public boolean[] getSubjectUniqueID() { - return wrapped.getSubjectUniqueID(); - } - - @Override - public boolean[] getKeyUsage() { - return wrapped.getKeyUsage(); - } - - @Override - public int getBasicConstraints() { - return wrapped.getBasicConstraints(); - } - - @Override - public byte[] getEncoded() throws CertificateEncodingException { - return wrapped.getEncoded(); - } - - @Override - public void verify(PublicKey key) throws CertificateException, NoSuchAlgorithmException, - InvalidKeyException, NoSuchProviderException, SignatureException { - wrapped.verify(key); - } - - @Override - public void verify(PublicKey key, String sigProvider) - throws CertificateException, NoSuchAlgorithmException, InvalidKeyException, - NoSuchProviderException, SignatureException { - wrapped.verify(key, sigProvider); - } - - @Override - public String toString() { - return wrapped.toString(); - } - - @Override - public PublicKey getPublicKey() { - return wrapped.getPublicKey(); - } - } } diff --git a/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java b/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java new file mode 100644 index 000000000000..e43dee356064 --- /dev/null +++ b/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java @@ -0,0 +1,558 @@ +/* + * Copyright (C) 2018 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 android.util.apk; + +import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_DSA_WITH_SHA256; +import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_ECDSA_WITH_SHA256; +import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_ECDSA_WITH_SHA512; +import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256; +import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512; +import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_RSA_PSS_WITH_SHA256; +import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_RSA_PSS_WITH_SHA512; +import static android.util.apk.ApkSigningBlockUtils.compareSignatureAlgorithm; +import static android.util.apk.ApkSigningBlockUtils.getContentDigestAlgorithmJcaDigestAlgorithm; +import static android.util.apk.ApkSigningBlockUtils.getLengthPrefixedSlice; +import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmContentDigestAlgorithm; +import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmJcaKeyAlgorithm; +import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmJcaSignatureAlgorithm; +import static android.util.apk.ApkSigningBlockUtils.readLengthPrefixedByteArray; + +import android.os.Build; +import android.util.ArrayMap; +import android.util.Pair; + +import java.io.ByteArrayInputStream; +import java.io.FileDescriptor; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.BufferUnderflowException; +import java.nio.ByteBuffer; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.KeyFactory; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.PublicKey; +import java.security.Signature; +import java.security.SignatureException; +import java.security.cert.CertificateEncodingException; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.security.spec.AlgorithmParameterSpec; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.X509EncodedKeySpec; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +/** + * APK Signature Scheme v3 verifier. + * + * @hide for internal use only. + */ +public class ApkSignatureSchemeV3Verifier { + + /** + * ID of this signature scheme as used in X-Android-APK-Signed header used in JAR signing. + */ + public static final int SF_ATTRIBUTE_ANDROID_APK_SIGNED_ID = 3; + + private static final int APK_SIGNATURE_SCHEME_V3_BLOCK_ID = 0xf05368c0; + + /** + * Returns {@code true} if the provided APK contains an APK Signature Scheme V3 signature. + * + * <p><b>NOTE: This method does not verify the signature.</b> + */ + public static boolean hasSignature(String apkFile) throws IOException { + try (RandomAccessFile apk = new RandomAccessFile(apkFile, "r")) { + findSignature(apk); + return true; + } catch (SignatureNotFoundException e) { + return false; + } + } + + /** + * Verifies APK Signature Scheme v3 signatures of the provided APK and returns the certificates + * associated with each signer. + * + * @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v3. + * @throws SecurityException if the APK Signature Scheme v3 signature of this APK does not + * verify. + * @throws IOException if an I/O error occurs while reading the APK file. + */ + public static VerifiedSigner verify(String apkFile) + throws SignatureNotFoundException, SecurityException, IOException { + return verify(apkFile, true); + } + + /** + * Returns the certificates associated with each signer for the given APK without verification. + * This method is dangerous and should not be used, unless the caller is absolutely certain the + * APK is trusted. Specifically, verification is only done for the APK Signature Scheme v3 + * Block while gathering signer information. The APK contents are not verified. + * + * @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v3. + * @throws IOException if an I/O error occurs while reading the APK file. + */ + public static VerifiedSigner plsCertsNoVerifyOnlyCerts(String apkFile) + throws SignatureNotFoundException, SecurityException, IOException { + return verify(apkFile, false); + } + + private static VerifiedSigner verify(String apkFile, boolean verifyIntegrity) + throws SignatureNotFoundException, SecurityException, IOException { + try (RandomAccessFile apk = new RandomAccessFile(apkFile, "r")) { + return verify(apk, verifyIntegrity); + } + } + + /** + * Verifies APK Signature Scheme v3 signatures of the provided APK and returns the certificates + * associated with each signer. + * + * @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v3. + * @throws SecurityException if an APK Signature Scheme v3 signature of this APK does not + * verify. + * @throws IOException if an I/O error occurs while reading the APK file. + */ + private static VerifiedSigner verify(RandomAccessFile apk, boolean verifyIntegrity) + throws SignatureNotFoundException, SecurityException, IOException { + SignatureInfo signatureInfo = findSignature(apk); + return verify(apk.getFD(), signatureInfo, verifyIntegrity); + } + + /** + * Returns the APK Signature Scheme v3 block contained in the provided APK file and the + * additional information relevant for verifying the block against the file. + * + * @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v3. + * @throws IOException if an I/O error occurs while reading the APK file. + */ + private static SignatureInfo findSignature(RandomAccessFile apk) + throws IOException, SignatureNotFoundException { + return ApkSigningBlockUtils.findSignature(apk, APK_SIGNATURE_SCHEME_V3_BLOCK_ID); + } + + /** + * Verifies the contents of the provided APK file against the provided APK Signature Scheme v3 + * Block. + * + * @param signatureInfo APK Signature Scheme v3 Block and information relevant for verifying it + * against the APK file. + */ + private static VerifiedSigner verify( + FileDescriptor apkFileDescriptor, + SignatureInfo signatureInfo, + boolean doVerifyIntegrity) throws SecurityException { + int signerCount = 0; + Map<Integer, byte[]> contentDigests = new ArrayMap<>(); + VerifiedSigner result = null; + CertificateFactory certFactory; + try { + certFactory = CertificateFactory.getInstance("X.509"); + } catch (CertificateException e) { + throw new RuntimeException("Failed to obtain X.509 CertificateFactory", e); + } + ByteBuffer signers; + try { + signers = getLengthPrefixedSlice(signatureInfo.signatureBlock); + } catch (IOException e) { + throw new SecurityException("Failed to read list of signers", e); + } + while (signers.hasRemaining()) { + try { + ByteBuffer signer = getLengthPrefixedSlice(signers); + result = verifySigner(signer, contentDigests, certFactory); + signerCount++; + } catch (PlatformNotSupportedException e) { + // this signer is for a different platform, ignore it. + continue; + } catch (IOException | BufferUnderflowException | SecurityException e) { + throw new SecurityException( + "Failed to parse/verify signer #" + signerCount + " block", + e); + } + } + + if (signerCount < 1 || result == null) { + throw new SecurityException("No signers found"); + } + + if (signerCount != 1) { + throw new SecurityException("APK Signature Scheme V3 only supports one signer: " + + "multiple signers found."); + } + + if (contentDigests.isEmpty()) { + throw new SecurityException("No content digests found"); + } + + if (doVerifyIntegrity) { + ApkSigningBlockUtils.verifyIntegrity( + contentDigests, + apkFileDescriptor, + signatureInfo.apkSigningBlockOffset, + signatureInfo.centralDirOffset, + signatureInfo.eocdOffset, + signatureInfo.eocd); + } + + return result; + } + + private static VerifiedSigner verifySigner( + ByteBuffer signerBlock, + Map<Integer, byte[]> contentDigests, + CertificateFactory certFactory) + throws SecurityException, IOException, PlatformNotSupportedException { + ByteBuffer signedData = getLengthPrefixedSlice(signerBlock); + int minSdkVersion = signerBlock.getInt(); + int maxSdkVersion = signerBlock.getInt(); + + if (Build.VERSION.SDK_INT < minSdkVersion || Build.VERSION.SDK_INT > maxSdkVersion) { + // this signature isn't meant to be used with this platform, skip it. + throw new PlatformNotSupportedException( + "Signer not supported by this platform " + + "version. This platform: " + Build.VERSION.SDK_INT + + ", signer minSdkVersion: " + minSdkVersion + + ", maxSdkVersion: " + maxSdkVersion); + } + + ByteBuffer signatures = getLengthPrefixedSlice(signerBlock); + byte[] publicKeyBytes = readLengthPrefixedByteArray(signerBlock); + + int signatureCount = 0; + int bestSigAlgorithm = -1; + byte[] bestSigAlgorithmSignatureBytes = null; + List<Integer> signaturesSigAlgorithms = new ArrayList<>(); + while (signatures.hasRemaining()) { + signatureCount++; + try { + ByteBuffer signature = getLengthPrefixedSlice(signatures); + if (signature.remaining() < 8) { + throw new SecurityException("Signature record too short"); + } + int sigAlgorithm = signature.getInt(); + signaturesSigAlgorithms.add(sigAlgorithm); + if (!isSupportedSignatureAlgorithm(sigAlgorithm)) { + continue; + } + if ((bestSigAlgorithm == -1) + || (compareSignatureAlgorithm(sigAlgorithm, bestSigAlgorithm) > 0)) { + bestSigAlgorithm = sigAlgorithm; + bestSigAlgorithmSignatureBytes = readLengthPrefixedByteArray(signature); + } + } catch (IOException | BufferUnderflowException e) { + throw new SecurityException( + "Failed to parse signature record #" + signatureCount, + e); + } + } + if (bestSigAlgorithm == -1) { + if (signatureCount == 0) { + throw new SecurityException("No signatures found"); + } else { + throw new SecurityException("No supported signatures found"); + } + } + + String keyAlgorithm = getSignatureAlgorithmJcaKeyAlgorithm(bestSigAlgorithm); + Pair<String, ? extends AlgorithmParameterSpec> signatureAlgorithmParams = + getSignatureAlgorithmJcaSignatureAlgorithm(bestSigAlgorithm); + String jcaSignatureAlgorithm = signatureAlgorithmParams.first; + AlgorithmParameterSpec jcaSignatureAlgorithmParams = signatureAlgorithmParams.second; + boolean sigVerified; + try { + PublicKey publicKey = + KeyFactory.getInstance(keyAlgorithm) + .generatePublic(new X509EncodedKeySpec(publicKeyBytes)); + Signature sig = Signature.getInstance(jcaSignatureAlgorithm); + sig.initVerify(publicKey); + if (jcaSignatureAlgorithmParams != null) { + sig.setParameter(jcaSignatureAlgorithmParams); + } + sig.update(signedData); + sigVerified = sig.verify(bestSigAlgorithmSignatureBytes); + } catch (NoSuchAlgorithmException | InvalidKeySpecException | InvalidKeyException + | InvalidAlgorithmParameterException | SignatureException e) { + throw new SecurityException( + "Failed to verify " + jcaSignatureAlgorithm + " signature", e); + } + if (!sigVerified) { + throw new SecurityException(jcaSignatureAlgorithm + " signature did not verify"); + } + + // Signature over signedData has verified. + + byte[] contentDigest = null; + signedData.clear(); + ByteBuffer digests = getLengthPrefixedSlice(signedData); + List<Integer> digestsSigAlgorithms = new ArrayList<>(); + int digestCount = 0; + while (digests.hasRemaining()) { + digestCount++; + try { + ByteBuffer digest = getLengthPrefixedSlice(digests); + if (digest.remaining() < 8) { + throw new IOException("Record too short"); + } + int sigAlgorithm = digest.getInt(); + digestsSigAlgorithms.add(sigAlgorithm); + if (sigAlgorithm == bestSigAlgorithm) { + contentDigest = readLengthPrefixedByteArray(digest); + } + } catch (IOException | BufferUnderflowException e) { + throw new IOException("Failed to parse digest record #" + digestCount, e); + } + } + + if (!signaturesSigAlgorithms.equals(digestsSigAlgorithms)) { + throw new SecurityException( + "Signature algorithms don't match between digests and signatures records"); + } + int digestAlgorithm = getSignatureAlgorithmContentDigestAlgorithm(bestSigAlgorithm); + byte[] previousSignerDigest = contentDigests.put(digestAlgorithm, contentDigest); + if ((previousSignerDigest != null) + && (!MessageDigest.isEqual(previousSignerDigest, contentDigest))) { + throw new SecurityException( + getContentDigestAlgorithmJcaDigestAlgorithm(digestAlgorithm) + + " contents digest does not match the digest specified by a preceding signer"); + } + + ByteBuffer certificates = getLengthPrefixedSlice(signedData); + List<X509Certificate> certs = new ArrayList<>(); + int certificateCount = 0; + while (certificates.hasRemaining()) { + certificateCount++; + byte[] encodedCert = readLengthPrefixedByteArray(certificates); + X509Certificate certificate; + try { + certificate = (X509Certificate) + certFactory.generateCertificate(new ByteArrayInputStream(encodedCert)); + } catch (CertificateException e) { + throw new SecurityException("Failed to decode certificate #" + certificateCount, e); + } + certificate = new VerbatimX509Certificate( + certificate, encodedCert); + certs.add(certificate); + } + + if (certs.isEmpty()) { + throw new SecurityException("No certificates listed"); + } + X509Certificate mainCertificate = certs.get(0); + byte[] certificatePublicKeyBytes = mainCertificate.getPublicKey().getEncoded(); + if (!Arrays.equals(publicKeyBytes, certificatePublicKeyBytes)) { + throw new SecurityException( + "Public key mismatch between certificate and signature record"); + } + + int signedMinSDK = signedData.getInt(); + if (signedMinSDK != minSdkVersion) { + throw new SecurityException( + "minSdkVersion mismatch between signed and unsigned in v3 signer block."); + } + + int signedMaxSDK = signedData.getInt(); + if (signedMaxSDK != maxSdkVersion) { + throw new SecurityException( + "maxSdkVersion mismatch between signed and unsigned in v3 signer block."); + } + + ByteBuffer additionalAttrs = getLengthPrefixedSlice(signedData); + return verifyAdditionalAttributes(additionalAttrs, certs, certFactory); + } + + private static final int PROOF_OF_ROTATION_ATTR_ID = 0x3ba06f8c; + + private static VerifiedSigner verifyAdditionalAttributes(ByteBuffer attrs, + List<X509Certificate> certs, CertificateFactory certFactory) throws IOException { + X509Certificate[] certChain = certs.toArray(new X509Certificate[certs.size()]); + VerifiedProofOfRotation por = null; + + while (attrs.hasRemaining()) { + ByteBuffer attr = getLengthPrefixedSlice(attrs); + if (attr.remaining() < 4) { + throw new IOException("Remaining buffer too short to contain additional attribute " + + "ID. Remaining: " + attr.remaining()); + } + int id = attr.getInt(); + switch(id) { + case PROOF_OF_ROTATION_ATTR_ID: + if (por != null) { + throw new SecurityException("Encountered multiple Proof-of-rotation records" + + " when verifying APK Signature Scheme v3 signature"); + } + por = verifyProofOfRotationStruct(attr, certFactory); + // make sure that the last certificate in the Proof-of-rotation record matches + // the one used to sign this APK. + try { + if (por.certs.size() > 0 + && !Arrays.equals(por.certs.get(por.certs.size() - 1).getEncoded(), + certChain[0].getEncoded())) { + throw new SecurityException("Terminal certificate in Proof-of-rotation" + + " record does not match APK signing certificate"); + } + } catch (CertificateEncodingException e) { + throw new SecurityException("Failed to encode certificate when comparing" + + " Proof-of-rotation record and signing certificate", e); + } + + break; + default: + // not the droid we're looking for, move along, move along. + break; + } + } + return new VerifiedSigner(certChain, por); + } + + private static VerifiedProofOfRotation verifyProofOfRotationStruct( + ByteBuffer porBuf, + CertificateFactory certFactory) + throws SecurityException, IOException { + int levelCount = 0; + int lastSigAlgorithm = -1; + X509Certificate lastCert = null; + List<X509Certificate> certs = new ArrayList<>(); + List<Integer> flagsList = new ArrayList<>(); + + // Proof-of-rotation struct: + // is basically a singly linked list of nodes, called levels here, each of which have the + // following structure: + // * length-prefix for the entire level + // - length-prefixed signed data (if previous level exists) + // * length-prefixed X509 Certificate + // * uint32 signature algorithm ID describing how this signed data was signed + // - uint32 flags describing how to treat the cert contained in this level + // - uint32 signature algorithm ID to use to verify the signature of the next level. The + // algorithm here must match the one in the signed data section of the next level. + // - length-prefixed signature over the signed data in this level. The signature here + // is verified using the certificate from the previous level. + // The linking is provided by the certificate of each level signing the one of the next. + while (porBuf.hasRemaining()) { + levelCount++; + try { + ByteBuffer level = getLengthPrefixedSlice(porBuf); + ByteBuffer signedData = getLengthPrefixedSlice(level); + int flags = level.getInt(); + int sigAlgorithm = level.getInt(); + byte[] signature = readLengthPrefixedByteArray(level); + + if (lastCert != null) { + // Use previous level cert to verify current level + Pair<String, ? extends AlgorithmParameterSpec> sigAlgParams = + getSignatureAlgorithmJcaSignatureAlgorithm(lastSigAlgorithm); + PublicKey publicKey = lastCert.getPublicKey(); + Signature sig = Signature.getInstance(sigAlgParams.first); + sig.initVerify(publicKey); + if (sigAlgParams.second != null) { + sig.setParameter(sigAlgParams.second); + } + sig.update(signedData); + if (!sig.verify(signature)) { + throw new SecurityException("Unable to verify signature of certificate #" + + levelCount + " using " + sigAlgParams.first + " when verifying" + + " Proof-of-rotation record"); + } + } + + byte[] encodedCert = readLengthPrefixedByteArray(signedData); + int signedSigAlgorithm = signedData.getInt(); + if (lastCert != null && lastSigAlgorithm != signedSigAlgorithm) { + throw new SecurityException("Signing algorithm ID mismatch for certificate #" + + levelCount + " when verifying Proof-of-rotation record"); + } + lastCert = (X509Certificate) + certFactory.generateCertificate(new ByteArrayInputStream(encodedCert)); + lastCert = new VerbatimX509Certificate(lastCert, encodedCert); + + lastSigAlgorithm = sigAlgorithm; + certs.add(lastCert); + flagsList.add(flags); + } catch (IOException | BufferUnderflowException e) { + throw new IOException("Failed to parse Proof-of-rotation record", e); + } catch (NoSuchAlgorithmException | InvalidKeyException + | InvalidAlgorithmParameterException | SignatureException e) { + throw new SecurityException( + "Failed to verify signature over signed data for certificate #" + + levelCount + " when verifying Proof-of-rotation record", e); + } catch (CertificateException e) { + throw new SecurityException("Failed to decode certificate #" + levelCount + + " when verifying Proof-of-rotation record", e); + } + } + return new VerifiedProofOfRotation(certs, flagsList); + } + + private static boolean isSupportedSignatureAlgorithm(int sigAlgorithm) { + switch (sigAlgorithm) { + case SIGNATURE_RSA_PSS_WITH_SHA256: + case SIGNATURE_RSA_PSS_WITH_SHA512: + case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256: + case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512: + case SIGNATURE_ECDSA_WITH_SHA256: + case SIGNATURE_ECDSA_WITH_SHA512: + case SIGNATURE_DSA_WITH_SHA256: + return true; + default: + return false; + } + } + + /** + * Verified processed proof of rotation. + * + * @hide for internal use only. + */ + public static class VerifiedProofOfRotation { + public final List<X509Certificate> certs; + public final List<Integer> flagsList; + + public VerifiedProofOfRotation(List<X509Certificate> certs, List<Integer> flagsList) { + this.certs = certs; + this.flagsList = flagsList; + } + } + + /** + * Verified APK Signature Scheme v3 signer, including the proof of rotation structure. + * + * @hide for internal use only. + */ + public static class VerifiedSigner { + public final X509Certificate[] certs; + public final VerifiedProofOfRotation por; + + public VerifiedSigner(X509Certificate[] certs, VerifiedProofOfRotation por) { + this.certs = certs; + this.por = por; + } + + } + + private static class PlatformNotSupportedException extends Exception { + + PlatformNotSupportedException(String s) { + super(s); + } + } +} diff --git a/core/java/android/util/apk/ApkSignatureVerifier.java b/core/java/android/util/apk/ApkSignatureVerifier.java index 17b11a9b5170..81467292d491 100644 --- a/core/java/android/util/apk/ApkSignatureVerifier.java +++ b/core/java/android/util/apk/ApkSignatureVerifier.java @@ -54,6 +54,7 @@ public class ApkSignatureVerifier { public static final int VERSION_JAR_SIGNATURE_SCHEME = 1; public static final int VERSION_APK_SIGNATURE_SCHEME_V2 = 2; + public static final int VERSION_APK_SIGNATURE_SCHEME_V3 = 3; private static final AtomicReference<byte[]> sBuffer = new AtomicReference<>(); @@ -65,7 +66,45 @@ public class ApkSignatureVerifier { public static Result verify(String apkPath, int minSignatureSchemeVersion) throws PackageParserException { - // first try v2 + if (minSignatureSchemeVersion > VERSION_APK_SIGNATURE_SCHEME_V3) { + // V3 and before are older than the requested minimum signing version + throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES, + "No signature found in package of version " + minSignatureSchemeVersion + + " or newer for package " + apkPath); + } + + // first try v3 + Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "verifyV3"); + try { + ApkSignatureSchemeV3Verifier.VerifiedSigner vSigner = + ApkSignatureSchemeV3Verifier.verify(apkPath); + Certificate[][] signerCerts = new Certificate[][] { vSigner.certs }; + Signature[] signerSigs = convertToSignatures(signerCerts); + return new Result(signerCerts, signerSigs, VERSION_APK_SIGNATURE_SCHEME_V3); + } catch (SignatureNotFoundException e) { + // not signed with v2, try older if allowed + if (minSignatureSchemeVersion >= VERSION_APK_SIGNATURE_SCHEME_V3) { + throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES, + "No APK Signature Scheme v3 signature in package " + apkPath, e); + } + } catch (Exception e) { + // APK Signature Scheme v2 signature found but did not verify + throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES, + "Failed to collect certificates from " + apkPath + + " using APK Signature Scheme v2", e); + } finally { + Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); + } + + // redundant, protective version check + if (minSignatureSchemeVersion > VERSION_APK_SIGNATURE_SCHEME_V2) { + // V2 and before are older than the requested minimum signing version + throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES, + "No signature found in package of version " + minSignatureSchemeVersion + + " or newer for package " + apkPath); + } + + // try v2 Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "verifyV2"); try { Certificate[][] signerCerts = ApkSignatureSchemeV2Verifier.verify(apkPath); @@ -87,6 +126,14 @@ public class ApkSignatureVerifier { Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); } + // redundant, protective version check + if (minSignatureSchemeVersion > VERSION_JAR_SIGNATURE_SCHEME) { + // V1 and is older than the requested minimum signing version + throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES, + "No signature found in package of version " + minSignatureSchemeVersion + + " or newer for package " + apkPath); + } + // v2 didn't work, try jarsigner return verifyV1Signature(apkPath, true); } @@ -245,6 +292,44 @@ public class ApkSignatureVerifier { public static Result plsCertsNoVerifyOnlyCerts(String apkPath, int minSignatureSchemeVersion) throws PackageParserException { + if (minSignatureSchemeVersion > VERSION_APK_SIGNATURE_SCHEME_V3) { + // V3 and before are older than the requested minimum signing version + throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES, + "No signature found in package of version " + minSignatureSchemeVersion + + " or newer for package " + apkPath); + } + + // first try v3 + Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "verifyV3"); + try { + ApkSignatureSchemeV3Verifier.VerifiedSigner vSigner = + ApkSignatureSchemeV3Verifier.plsCertsNoVerifyOnlyCerts(apkPath); + Certificate[][] signerCerts = new Certificate[][] { vSigner.certs }; + Signature[] signerSigs = convertToSignatures(signerCerts); + return new Result(signerCerts, signerSigs, VERSION_APK_SIGNATURE_SCHEME_V3); + } catch (SignatureNotFoundException e) { + // not signed with v2, try older if allowed + if (minSignatureSchemeVersion >= VERSION_APK_SIGNATURE_SCHEME_V3) { + throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES, + "No APK Signature Scheme v3 signature in package " + apkPath, e); + } + } catch (Exception e) { + // APK Signature Scheme v2 signature found but did not verify + throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES, + "Failed to collect certificates from " + apkPath + + " using APK Signature Scheme v2", e); + } finally { + Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); + } + + // redundant, protective version check + if (minSignatureSchemeVersion > VERSION_APK_SIGNATURE_SCHEME_V2) { + // V2 and before are older than the requested minimum signing version + throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES, + "No signature found in package of version " + minSignatureSchemeVersion + + " or newer for package " + apkPath); + } + // first try v2 Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "certsOnlyV2"); try { @@ -267,6 +352,14 @@ public class ApkSignatureVerifier { Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); } + // redundant, protective version check + if (minSignatureSchemeVersion > VERSION_JAR_SIGNATURE_SCHEME) { + // V1 and is older than the requested minimum signing version + throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES, + "No signature found in package of version " + minSignatureSchemeVersion + + " or newer for package " + apkPath); + } + // v2 didn't work, try jarsigner return verifyV1Signature(apkPath, false); } diff --git a/core/java/android/util/apk/ApkSigningBlockUtils.java b/core/java/android/util/apk/ApkSigningBlockUtils.java new file mode 100644 index 000000000000..9279510ae58f --- /dev/null +++ b/core/java/android/util/apk/ApkSigningBlockUtils.java @@ -0,0 +1,663 @@ +/* + * Copyright (C) 2018 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 android.util.apk; + +import android.util.Pair; + +import java.io.FileDescriptor; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.BufferUnderflowException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.security.DigestException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.spec.AlgorithmParameterSpec; +import java.security.spec.MGF1ParameterSpec; +import java.security.spec.PSSParameterSpec; +import java.util.Map; + +/** + * Utility class for an APK Signature Scheme using the APK Signing Block. + * + * @hide for internal use only. + */ +final class ApkSigningBlockUtils { + + private ApkSigningBlockUtils() { + } + + /** + * Returns the APK Signature Scheme block contained in the provided APK file and the + * additional information relevant for verifying the block against the file. + * + * @param blockId the ID value in the APK Signing Block's sequence of ID-value pairs + * identifying the appropriate block to find, e.g. the APK Signature Scheme v2 + * block ID. + * + * @throws SignatureNotFoundException if the APK is not signed using this scheme. + * @throws IOException if an I/O error occurs while reading the APK file. + */ + static SignatureInfo findSignature(RandomAccessFile apk, int blockId) + throws IOException, SignatureNotFoundException { + // Find the ZIP End of Central Directory (EoCD) record. + Pair<ByteBuffer, Long> eocdAndOffsetInFile = getEocd(apk); + ByteBuffer eocd = eocdAndOffsetInFile.first; + long eocdOffset = eocdAndOffsetInFile.second; + if (ZipUtils.isZip64EndOfCentralDirectoryLocatorPresent(apk, eocdOffset)) { + throw new SignatureNotFoundException("ZIP64 APK not supported"); + } + + // Find the APK Signing Block. The block immediately precedes the Central Directory. + long centralDirOffset = getCentralDirOffset(eocd, eocdOffset); + Pair<ByteBuffer, Long> apkSigningBlockAndOffsetInFile = + findApkSigningBlock(apk, centralDirOffset); + ByteBuffer apkSigningBlock = apkSigningBlockAndOffsetInFile.first; + long apkSigningBlockOffset = apkSigningBlockAndOffsetInFile.second; + + // Find the APK Signature Scheme Block inside the APK Signing Block. + ByteBuffer apkSignatureSchemeBlock = findApkSignatureSchemeBlock(apkSigningBlock, + blockId); + + return new SignatureInfo( + apkSignatureSchemeBlock, + apkSigningBlockOffset, + centralDirOffset, + eocdOffset, + eocd); + } + + static void verifyIntegrity( + Map<Integer, byte[]> expectedDigests, + FileDescriptor apkFileDescriptor, + long apkSigningBlockOffset, + long centralDirOffset, + long eocdOffset, + ByteBuffer eocdBuf) throws SecurityException { + + if (expectedDigests.isEmpty()) { + throw new SecurityException("No digests provided"); + } + + // We need to verify the integrity of the following three sections of the file: + // 1. Everything up to the start of the APK Signing Block. + // 2. ZIP Central Directory. + // 3. ZIP End of Central Directory (EoCD). + // Each of these sections is represented as a separate DataSource instance below. + + // To handle large APKs, these sections are read in 1 MB chunks using memory-mapped I/O to + // avoid wasting physical memory. In most APK verification scenarios, the contents of the + // APK are already there in the OS's page cache and thus mmap does not use additional + // physical memory. + DataSource beforeApkSigningBlock = + new MemoryMappedFileDataSource(apkFileDescriptor, 0, apkSigningBlockOffset); + DataSource centralDir = + new MemoryMappedFileDataSource( + apkFileDescriptor, centralDirOffset, eocdOffset - centralDirOffset); + + // For the purposes of integrity verification, ZIP End of Central Directory's field Start of + // Central Directory must be considered to point to the offset of the APK Signing Block. + eocdBuf = eocdBuf.duplicate(); + eocdBuf.order(ByteOrder.LITTLE_ENDIAN); + ZipUtils.setZipEocdCentralDirectoryOffset(eocdBuf, apkSigningBlockOffset); + DataSource eocd = new ByteBufferDataSource(eocdBuf); + + int[] digestAlgorithms = new int[expectedDigests.size()]; + int digestAlgorithmCount = 0; + for (int digestAlgorithm : expectedDigests.keySet()) { + digestAlgorithms[digestAlgorithmCount] = digestAlgorithm; + digestAlgorithmCount++; + } + byte[][] actualDigests; + try { + actualDigests = + computeContentDigests( + digestAlgorithms, + new DataSource[] {beforeApkSigningBlock, centralDir, eocd}); + } catch (DigestException e) { + throw new SecurityException("Failed to compute digest(s) of contents", e); + } + for (int i = 0; i < digestAlgorithms.length; i++) { + int digestAlgorithm = digestAlgorithms[i]; + byte[] expectedDigest = expectedDigests.get(digestAlgorithm); + byte[] actualDigest = actualDigests[i]; + if (!MessageDigest.isEqual(expectedDigest, actualDigest)) { + throw new SecurityException( + getContentDigestAlgorithmJcaDigestAlgorithm(digestAlgorithm) + + " digest of contents did not verify"); + } + } + } + + private static byte[][] computeContentDigests( + int[] digestAlgorithms, + DataSource[] contents) throws DigestException { + // For each digest algorithm the result is computed as follows: + // 1. Each segment of contents is split into consecutive chunks of 1 MB in size. + // The final chunk will be shorter iff the length of segment is not a multiple of 1 MB. + // No chunks are produced for empty (zero length) segments. + // 2. The digest of each chunk is computed over the concatenation of byte 0xa5, the chunk's + // length in bytes (uint32 little-endian) and the chunk's contents. + // 3. The output digest is computed over the concatenation of the byte 0x5a, the number of + // chunks (uint32 little-endian) and the concatenation of digests of chunks of all + // segments in-order. + + long totalChunkCountLong = 0; + for (DataSource input : contents) { + totalChunkCountLong += getChunkCount(input.size()); + } + if (totalChunkCountLong >= Integer.MAX_VALUE / 1024) { + throw new DigestException("Too many chunks: " + totalChunkCountLong); + } + int totalChunkCount = (int) totalChunkCountLong; + + byte[][] digestsOfChunks = new byte[digestAlgorithms.length][]; + for (int i = 0; i < digestAlgorithms.length; i++) { + int digestAlgorithm = digestAlgorithms[i]; + int digestOutputSizeBytes = getContentDigestAlgorithmOutputSizeBytes(digestAlgorithm); + byte[] concatenationOfChunkCountAndChunkDigests = + new byte[5 + totalChunkCount * digestOutputSizeBytes]; + concatenationOfChunkCountAndChunkDigests[0] = 0x5a; + setUnsignedInt32LittleEndian( + totalChunkCount, + concatenationOfChunkCountAndChunkDigests, + 1); + digestsOfChunks[i] = concatenationOfChunkCountAndChunkDigests; + } + + byte[] chunkContentPrefix = new byte[5]; + chunkContentPrefix[0] = (byte) 0xa5; + int chunkIndex = 0; + MessageDigest[] mds = new MessageDigest[digestAlgorithms.length]; + for (int i = 0; i < digestAlgorithms.length; i++) { + String jcaAlgorithmName = + getContentDigestAlgorithmJcaDigestAlgorithm(digestAlgorithms[i]); + try { + mds[i] = MessageDigest.getInstance(jcaAlgorithmName); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(jcaAlgorithmName + " digest not supported", e); + } + } + // TODO: Compute digests of chunks in parallel when beneficial. This requires some research + // into how to parallelize (if at all) based on the capabilities of the hardware on which + // this code is running and based on the size of input. + DataDigester digester = new MultipleDigestDataDigester(mds); + int dataSourceIndex = 0; + for (DataSource input : contents) { + long inputOffset = 0; + long inputRemaining = input.size(); + while (inputRemaining > 0) { + int chunkSize = (int) Math.min(inputRemaining, CHUNK_SIZE_BYTES); + setUnsignedInt32LittleEndian(chunkSize, chunkContentPrefix, 1); + for (int i = 0; i < mds.length; i++) { + mds[i].update(chunkContentPrefix); + } + try { + input.feedIntoDataDigester(digester, inputOffset, chunkSize); + } catch (IOException e) { + throw new DigestException( + "Failed to digest chunk #" + chunkIndex + " of section #" + + dataSourceIndex, + e); + } + for (int i = 0; i < digestAlgorithms.length; i++) { + int digestAlgorithm = digestAlgorithms[i]; + byte[] concatenationOfChunkCountAndChunkDigests = digestsOfChunks[i]; + int expectedDigestSizeBytes = + getContentDigestAlgorithmOutputSizeBytes(digestAlgorithm); + MessageDigest md = mds[i]; + int actualDigestSizeBytes = + md.digest( + concatenationOfChunkCountAndChunkDigests, + 5 + chunkIndex * expectedDigestSizeBytes, + expectedDigestSizeBytes); + if (actualDigestSizeBytes != expectedDigestSizeBytes) { + throw new RuntimeException( + "Unexpected output size of " + md.getAlgorithm() + " digest: " + + actualDigestSizeBytes); + } + } + inputOffset += chunkSize; + inputRemaining -= chunkSize; + chunkIndex++; + } + dataSourceIndex++; + } + + byte[][] result = new byte[digestAlgorithms.length][]; + for (int i = 0; i < digestAlgorithms.length; i++) { + int digestAlgorithm = digestAlgorithms[i]; + byte[] input = digestsOfChunks[i]; + String jcaAlgorithmName = getContentDigestAlgorithmJcaDigestAlgorithm(digestAlgorithm); + MessageDigest md; + try { + md = MessageDigest.getInstance(jcaAlgorithmName); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(jcaAlgorithmName + " digest not supported", e); + } + byte[] output = md.digest(input); + result[i] = output; + } + return result; + } + + /** + * Returns the ZIP End of Central Directory (EoCD) and its offset in the file. + * + * @throws IOException if an I/O error occurs while reading the file. + * @throws SignatureNotFoundException if the EoCD could not be found. + */ + static Pair<ByteBuffer, Long> getEocd(RandomAccessFile apk) + throws IOException, SignatureNotFoundException { + Pair<ByteBuffer, Long> eocdAndOffsetInFile = + ZipUtils.findZipEndOfCentralDirectoryRecord(apk); + if (eocdAndOffsetInFile == null) { + throw new SignatureNotFoundException( + "Not an APK file: ZIP End of Central Directory record not found"); + } + return eocdAndOffsetInFile; + } + + static long getCentralDirOffset(ByteBuffer eocd, long eocdOffset) + throws SignatureNotFoundException { + // Look up the offset of ZIP Central Directory. + long centralDirOffset = ZipUtils.getZipEocdCentralDirectoryOffset(eocd); + if (centralDirOffset > eocdOffset) { + throw new SignatureNotFoundException( + "ZIP Central Directory offset out of range: " + centralDirOffset + + ". ZIP End of Central Directory offset: " + eocdOffset); + } + long centralDirSize = ZipUtils.getZipEocdCentralDirectorySizeBytes(eocd); + if (centralDirOffset + centralDirSize != eocdOffset) { + throw new SignatureNotFoundException( + "ZIP Central Directory is not immediately followed by End of Central" + + " Directory"); + } + return centralDirOffset; + } + + private static long getChunkCount(long inputSizeBytes) { + return (inputSizeBytes + CHUNK_SIZE_BYTES - 1) / CHUNK_SIZE_BYTES; + } + + private static final int CHUNK_SIZE_BYTES = 1024 * 1024; + + static final int SIGNATURE_RSA_PSS_WITH_SHA256 = 0x0101; + static final int SIGNATURE_RSA_PSS_WITH_SHA512 = 0x0102; + static final int SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256 = 0x0103; + static final int SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512 = 0x0104; + static final int SIGNATURE_ECDSA_WITH_SHA256 = 0x0201; + static final int SIGNATURE_ECDSA_WITH_SHA512 = 0x0202; + static final int SIGNATURE_DSA_WITH_SHA256 = 0x0301; + + static final int CONTENT_DIGEST_CHUNKED_SHA256 = 1; + static final int CONTENT_DIGEST_CHUNKED_SHA512 = 2; + + static int compareSignatureAlgorithm(int sigAlgorithm1, int sigAlgorithm2) { + int digestAlgorithm1 = getSignatureAlgorithmContentDigestAlgorithm(sigAlgorithm1); + int digestAlgorithm2 = getSignatureAlgorithmContentDigestAlgorithm(sigAlgorithm2); + return compareContentDigestAlgorithm(digestAlgorithm1, digestAlgorithm2); + } + + private static int compareContentDigestAlgorithm(int digestAlgorithm1, int digestAlgorithm2) { + switch (digestAlgorithm1) { + case CONTENT_DIGEST_CHUNKED_SHA256: + switch (digestAlgorithm2) { + case CONTENT_DIGEST_CHUNKED_SHA256: + return 0; + case CONTENT_DIGEST_CHUNKED_SHA512: + return -1; + default: + throw new IllegalArgumentException( + "Unknown digestAlgorithm2: " + digestAlgorithm2); + } + case CONTENT_DIGEST_CHUNKED_SHA512: + switch (digestAlgorithm2) { + case CONTENT_DIGEST_CHUNKED_SHA256: + return 1; + case CONTENT_DIGEST_CHUNKED_SHA512: + return 0; + default: + throw new IllegalArgumentException( + "Unknown digestAlgorithm2: " + digestAlgorithm2); + } + default: + throw new IllegalArgumentException("Unknown digestAlgorithm1: " + digestAlgorithm1); + } + } + + static int getSignatureAlgorithmContentDigestAlgorithm(int sigAlgorithm) { + switch (sigAlgorithm) { + case SIGNATURE_RSA_PSS_WITH_SHA256: + case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256: + case SIGNATURE_ECDSA_WITH_SHA256: + case SIGNATURE_DSA_WITH_SHA256: + return CONTENT_DIGEST_CHUNKED_SHA256; + case SIGNATURE_RSA_PSS_WITH_SHA512: + case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512: + case SIGNATURE_ECDSA_WITH_SHA512: + return CONTENT_DIGEST_CHUNKED_SHA512; + default: + throw new IllegalArgumentException( + "Unknown signature algorithm: 0x" + + Long.toHexString(sigAlgorithm & 0xffffffff)); + } + } + + static String getContentDigestAlgorithmJcaDigestAlgorithm(int digestAlgorithm) { + switch (digestAlgorithm) { + case CONTENT_DIGEST_CHUNKED_SHA256: + return "SHA-256"; + case CONTENT_DIGEST_CHUNKED_SHA512: + return "SHA-512"; + default: + throw new IllegalArgumentException( + "Unknown content digest algorthm: " + digestAlgorithm); + } + } + + private static int getContentDigestAlgorithmOutputSizeBytes(int digestAlgorithm) { + switch (digestAlgorithm) { + case CONTENT_DIGEST_CHUNKED_SHA256: + return 256 / 8; + case CONTENT_DIGEST_CHUNKED_SHA512: + return 512 / 8; + default: + throw new IllegalArgumentException( + "Unknown content digest algorthm: " + digestAlgorithm); + } + } + + static String getSignatureAlgorithmJcaKeyAlgorithm(int sigAlgorithm) { + switch (sigAlgorithm) { + case SIGNATURE_RSA_PSS_WITH_SHA256: + case SIGNATURE_RSA_PSS_WITH_SHA512: + case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256: + case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512: + return "RSA"; + case SIGNATURE_ECDSA_WITH_SHA256: + case SIGNATURE_ECDSA_WITH_SHA512: + return "EC"; + case SIGNATURE_DSA_WITH_SHA256: + return "DSA"; + default: + throw new IllegalArgumentException( + "Unknown signature algorithm: 0x" + + Long.toHexString(sigAlgorithm & 0xffffffff)); + } + } + + static Pair<String, ? extends AlgorithmParameterSpec> + getSignatureAlgorithmJcaSignatureAlgorithm(int sigAlgorithm) { + switch (sigAlgorithm) { + case SIGNATURE_RSA_PSS_WITH_SHA256: + return Pair.create( + "SHA256withRSA/PSS", + new PSSParameterSpec( + "SHA-256", "MGF1", MGF1ParameterSpec.SHA256, 256 / 8, 1)); + case SIGNATURE_RSA_PSS_WITH_SHA512: + return Pair.create( + "SHA512withRSA/PSS", + new PSSParameterSpec( + "SHA-512", "MGF1", MGF1ParameterSpec.SHA512, 512 / 8, 1)); + case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256: + return Pair.create("SHA256withRSA", null); + case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512: + return Pair.create("SHA512withRSA", null); + case SIGNATURE_ECDSA_WITH_SHA256: + return Pair.create("SHA256withECDSA", null); + case SIGNATURE_ECDSA_WITH_SHA512: + return Pair.create("SHA512withECDSA", null); + case SIGNATURE_DSA_WITH_SHA256: + return Pair.create("SHA256withDSA", null); + default: + throw new IllegalArgumentException( + "Unknown signature algorithm: 0x" + + Long.toHexString(sigAlgorithm & 0xffffffff)); + } + } + + /** + * Returns new byte buffer whose content is a shared subsequence of this buffer's content + * between the specified start (inclusive) and end (exclusive) positions. As opposed to + * {@link ByteBuffer#slice()}, the returned buffer's byte order is the same as the source + * buffer's byte order. + */ + static ByteBuffer sliceFromTo(ByteBuffer source, int start, int end) { + if (start < 0) { + throw new IllegalArgumentException("start: " + start); + } + if (end < start) { + throw new IllegalArgumentException("end < start: " + end + " < " + start); + } + int capacity = source.capacity(); + if (end > source.capacity()) { + throw new IllegalArgumentException("end > capacity: " + end + " > " + capacity); + } + int originalLimit = source.limit(); + int originalPosition = source.position(); + try { + source.position(0); + source.limit(end); + source.position(start); + ByteBuffer result = source.slice(); + result.order(source.order()); + return result; + } finally { + source.position(0); + source.limit(originalLimit); + source.position(originalPosition); + } + } + + /** + * Relative <em>get</em> method for reading {@code size} number of bytes from the current + * position of this buffer. + * + * <p>This method reads the next {@code size} bytes at this buffer's current position, + * returning them as a {@code ByteBuffer} with start set to 0, limit and capacity set to + * {@code size}, byte order set to this buffer's byte order; and then increments the position by + * {@code size}. + */ + static ByteBuffer getByteBuffer(ByteBuffer source, int size) + throws BufferUnderflowException { + if (size < 0) { + throw new IllegalArgumentException("size: " + size); + } + int originalLimit = source.limit(); + int position = source.position(); + int limit = position + size; + if ((limit < position) || (limit > originalLimit)) { + throw new BufferUnderflowException(); + } + source.limit(limit); + try { + ByteBuffer result = source.slice(); + result.order(source.order()); + source.position(limit); + return result; + } finally { + source.limit(originalLimit); + } + } + + static ByteBuffer getLengthPrefixedSlice(ByteBuffer source) throws IOException { + if (source.remaining() < 4) { + throw new IOException( + "Remaining buffer too short to contain length of length-prefixed field." + + " Remaining: " + source.remaining()); + } + int len = source.getInt(); + if (len < 0) { + throw new IllegalArgumentException("Negative length"); + } else if (len > source.remaining()) { + throw new IOException("Length-prefixed field longer than remaining buffer." + + " Field length: " + len + ", remaining: " + source.remaining()); + } + return getByteBuffer(source, len); + } + + static byte[] readLengthPrefixedByteArray(ByteBuffer buf) throws IOException { + int len = buf.getInt(); + if (len < 0) { + throw new IOException("Negative length"); + } else if (len > buf.remaining()) { + throw new IOException("Underflow while reading length-prefixed value. Length: " + len + + ", available: " + buf.remaining()); + } + byte[] result = new byte[len]; + buf.get(result); + return result; + } + + static void setUnsignedInt32LittleEndian(int value, byte[] result, int offset) { + result[offset] = (byte) (value & 0xff); + result[offset + 1] = (byte) ((value >>> 8) & 0xff); + result[offset + 2] = (byte) ((value >>> 16) & 0xff); + result[offset + 3] = (byte) ((value >>> 24) & 0xff); + } + + private static final long APK_SIG_BLOCK_MAGIC_HI = 0x3234206b636f6c42L; + private static final long APK_SIG_BLOCK_MAGIC_LO = 0x20676953204b5041L; + private static final int APK_SIG_BLOCK_MIN_SIZE = 32; + + static Pair<ByteBuffer, Long> findApkSigningBlock( + RandomAccessFile apk, long centralDirOffset) + throws IOException, SignatureNotFoundException { + // FORMAT: + // OFFSET DATA TYPE DESCRIPTION + // * @+0 bytes uint64: size in bytes (excluding this field) + // * @+8 bytes payload + // * @-24 bytes uint64: size in bytes (same as the one above) + // * @-16 bytes uint128: magic + + if (centralDirOffset < APK_SIG_BLOCK_MIN_SIZE) { + throw new SignatureNotFoundException( + "APK too small for APK Signing Block. ZIP Central Directory offset: " + + centralDirOffset); + } + // Read the magic and offset in file from the footer section of the block: + // * uint64: size of block + // * 16 bytes: magic + ByteBuffer footer = ByteBuffer.allocate(24); + footer.order(ByteOrder.LITTLE_ENDIAN); + apk.seek(centralDirOffset - footer.capacity()); + apk.readFully(footer.array(), footer.arrayOffset(), footer.capacity()); + if ((footer.getLong(8) != APK_SIG_BLOCK_MAGIC_LO) + || (footer.getLong(16) != APK_SIG_BLOCK_MAGIC_HI)) { + throw new SignatureNotFoundException( + "No APK Signing Block before ZIP Central Directory"); + } + // Read and compare size fields + long apkSigBlockSizeInFooter = footer.getLong(0); + if ((apkSigBlockSizeInFooter < footer.capacity()) + || (apkSigBlockSizeInFooter > Integer.MAX_VALUE - 8)) { + throw new SignatureNotFoundException( + "APK Signing Block size out of range: " + apkSigBlockSizeInFooter); + } + int totalSize = (int) (apkSigBlockSizeInFooter + 8); + long apkSigBlockOffset = centralDirOffset - totalSize; + if (apkSigBlockOffset < 0) { + throw new SignatureNotFoundException( + "APK Signing Block offset out of range: " + apkSigBlockOffset); + } + ByteBuffer apkSigBlock = ByteBuffer.allocate(totalSize); + apkSigBlock.order(ByteOrder.LITTLE_ENDIAN); + apk.seek(apkSigBlockOffset); + apk.readFully(apkSigBlock.array(), apkSigBlock.arrayOffset(), apkSigBlock.capacity()); + long apkSigBlockSizeInHeader = apkSigBlock.getLong(0); + if (apkSigBlockSizeInHeader != apkSigBlockSizeInFooter) { + throw new SignatureNotFoundException( + "APK Signing Block sizes in header and footer do not match: " + + apkSigBlockSizeInHeader + " vs " + apkSigBlockSizeInFooter); + } + return Pair.create(apkSigBlock, apkSigBlockOffset); + } + + static ByteBuffer findApkSignatureSchemeBlock(ByteBuffer apkSigningBlock, int blockId) + throws SignatureNotFoundException { + checkByteOrderLittleEndian(apkSigningBlock); + // FORMAT: + // OFFSET DATA TYPE DESCRIPTION + // * @+0 bytes uint64: size in bytes (excluding this field) + // * @+8 bytes pairs + // * @-24 bytes uint64: size in bytes (same as the one above) + // * @-16 bytes uint128: magic + ByteBuffer pairs = sliceFromTo(apkSigningBlock, 8, apkSigningBlock.capacity() - 24); + + int entryCount = 0; + while (pairs.hasRemaining()) { + entryCount++; + if (pairs.remaining() < 8) { + throw new SignatureNotFoundException( + "Insufficient data to read size of APK Signing Block entry #" + entryCount); + } + long lenLong = pairs.getLong(); + if ((lenLong < 4) || (lenLong > Integer.MAX_VALUE)) { + throw new SignatureNotFoundException( + "APK Signing Block entry #" + entryCount + + " size out of range: " + lenLong); + } + int len = (int) lenLong; + int nextEntryPos = pairs.position() + len; + if (len > pairs.remaining()) { + throw new SignatureNotFoundException( + "APK Signing Block entry #" + entryCount + " size out of range: " + len + + ", available: " + pairs.remaining()); + } + int id = pairs.getInt(); + if (id == blockId) { + return getByteBuffer(pairs, len - 4); + } + pairs.position(nextEntryPos); + } + + throw new SignatureNotFoundException( + "No block with ID " + blockId + " in APK Signing Block."); + } + + private static void checkByteOrderLittleEndian(ByteBuffer buffer) { + if (buffer.order() != ByteOrder.LITTLE_ENDIAN) { + throw new IllegalArgumentException("ByteBuffer byte order must be little endian"); + } + } + + /** + * {@link DataDigester} that updates multiple {@link MessageDigest}s whenever data is fed. + */ + private static class MultipleDigestDataDigester implements DataDigester { + private final MessageDigest[] mMds; + + MultipleDigestDataDigester(MessageDigest[] mds) { + mMds = mds; + } + + @Override + public void consume(ByteBuffer buffer) { + buffer = buffer.slice(); + for (MessageDigest md : mMds) { + buffer.position(0); + md.update(buffer); + } + } + + @Override + public void finish() {} + } + +} diff --git a/core/java/android/util/apk/VerbatimX509Certificate.java b/core/java/android/util/apk/VerbatimX509Certificate.java new file mode 100644 index 000000000000..9984c6d26c64 --- /dev/null +++ b/core/java/android/util/apk/VerbatimX509Certificate.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2018 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 android.util.apk; + +import java.security.cert.CertificateEncodingException; +import java.security.cert.X509Certificate; + +/** + * For legacy reasons we need to return exactly the original encoded certificate bytes, instead + * of letting the underlying implementation have a shot at re-encoding the data. + */ +class VerbatimX509Certificate extends WrappedX509Certificate { + private final byte[] mEncodedVerbatim; + + VerbatimX509Certificate(X509Certificate wrapped, byte[] encodedVerbatim) { + super(wrapped); + this.mEncodedVerbatim = encodedVerbatim; + } + + @Override + public byte[] getEncoded() throws CertificateEncodingException { + return mEncodedVerbatim; + } +} diff --git a/core/java/android/util/apk/WrappedX509Certificate.java b/core/java/android/util/apk/WrappedX509Certificate.java new file mode 100644 index 000000000000..fdaa42028e8d --- /dev/null +++ b/core/java/android/util/apk/WrappedX509Certificate.java @@ -0,0 +1,175 @@ +/* + * Copyright (C) 2018 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 android.util.apk; + +import java.math.BigInteger; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.Principal; +import java.security.PublicKey; +import java.security.SignatureException; +import java.security.cert.CertificateEncodingException; +import java.security.cert.CertificateException; +import java.security.cert.CertificateExpiredException; +import java.security.cert.CertificateNotYetValidException; +import java.security.cert.X509Certificate; +import java.util.Date; +import java.util.Set; + +class WrappedX509Certificate extends X509Certificate { + private final X509Certificate mWrapped; + + WrappedX509Certificate(X509Certificate wrapped) { + this.mWrapped = wrapped; + } + + @Override + public Set<String> getCriticalExtensionOIDs() { + return mWrapped.getCriticalExtensionOIDs(); + } + + @Override + public byte[] getExtensionValue(String oid) { + return mWrapped.getExtensionValue(oid); + } + + @Override + public Set<String> getNonCriticalExtensionOIDs() { + return mWrapped.getNonCriticalExtensionOIDs(); + } + + @Override + public boolean hasUnsupportedCriticalExtension() { + return mWrapped.hasUnsupportedCriticalExtension(); + } + + @Override + public void checkValidity() + throws CertificateExpiredException, CertificateNotYetValidException { + mWrapped.checkValidity(); + } + + @Override + public void checkValidity(Date date) + throws CertificateExpiredException, CertificateNotYetValidException { + mWrapped.checkValidity(date); + } + + @Override + public int getVersion() { + return mWrapped.getVersion(); + } + + @Override + public BigInteger getSerialNumber() { + return mWrapped.getSerialNumber(); + } + + @Override + public Principal getIssuerDN() { + return mWrapped.getIssuerDN(); + } + + @Override + public Principal getSubjectDN() { + return mWrapped.getSubjectDN(); + } + + @Override + public Date getNotBefore() { + return mWrapped.getNotBefore(); + } + + @Override + public Date getNotAfter() { + return mWrapped.getNotAfter(); + } + + @Override + public byte[] getTBSCertificate() throws CertificateEncodingException { + return mWrapped.getTBSCertificate(); + } + + @Override + public byte[] getSignature() { + return mWrapped.getSignature(); + } + + @Override + public String getSigAlgName() { + return mWrapped.getSigAlgName(); + } + + @Override + public String getSigAlgOID() { + return mWrapped.getSigAlgOID(); + } + + @Override + public byte[] getSigAlgParams() { + return mWrapped.getSigAlgParams(); + } + + @Override + public boolean[] getIssuerUniqueID() { + return mWrapped.getIssuerUniqueID(); + } + + @Override + public boolean[] getSubjectUniqueID() { + return mWrapped.getSubjectUniqueID(); + } + + @Override + public boolean[] getKeyUsage() { + return mWrapped.getKeyUsage(); + } + + @Override + public int getBasicConstraints() { + return mWrapped.getBasicConstraints(); + } + + @Override + public byte[] getEncoded() throws CertificateEncodingException { + return mWrapped.getEncoded(); + } + + @Override + public void verify(PublicKey key) throws CertificateException, NoSuchAlgorithmException, + InvalidKeyException, NoSuchProviderException, SignatureException { + mWrapped.verify(key); + } + + @Override + public void verify(PublicKey key, String sigProvider) + throws CertificateException, NoSuchAlgorithmException, InvalidKeyException, + NoSuchProviderException, SignatureException { + mWrapped.verify(key, sigProvider); + } + + @Override + public String toString() { + return mWrapped.toString(); + } + + @Override + public PublicKey getPublicKey() { + return mWrapped.getPublicKey(); + } +} diff --git a/core/java/android/util/jar/StrictJarVerifier.java b/core/java/android/util/jar/StrictJarVerifier.java index debc170fa537..45254908c5c9 100644 --- a/core/java/android/util/jar/StrictJarVerifier.java +++ b/core/java/android/util/jar/StrictJarVerifier.java @@ -18,6 +18,8 @@ package android.util.jar; import android.util.apk.ApkSignatureSchemeV2Verifier; +import android.util.apk.ApkSignatureSchemeV3Verifier; + import java.io.IOException; import java.io.OutputStream; import java.nio.charset.StandardCharsets; @@ -36,6 +38,7 @@ import java.util.Map; import java.util.StringTokenizer; import java.util.jar.Attributes; import java.util.jar.JarFile; + import sun.security.jca.Providers; import sun.security.pkcs.PKCS7; import sun.security.pkcs.SignerInfo; @@ -56,6 +59,15 @@ import sun.security.pkcs.SignerInfo; */ class StrictJarVerifier { /** + * {@code .SF} file header section attribute indicating that the APK is signed not just with + * JAR signature scheme but also with APK Signature Scheme v2 or newer. This attribute + * facilitates v2 signature stripping detection. + * + * <p>The attribute contains a comma-separated set of signature scheme IDs. + */ + private static final String SF_ATTRIBUTE_ANDROID_APK_SIGNED_NAME = "X-Android-APK-Signed"; + + /** * List of accepted digest algorithms. This list is in order from most * preferred to least preferred. */ @@ -373,17 +385,17 @@ class StrictJarVerifier { return; } - // If requested, check whether APK Signature Scheme v2 signature was stripped. + // If requested, check whether a newer APK Signature Scheme signature was stripped. if (signatureSchemeRollbackProtectionsEnforced) { String apkSignatureSchemeIdList = - attributes.getValue( - ApkSignatureSchemeV2Verifier.SF_ATTRIBUTE_ANDROID_APK_SIGNED_NAME); + attributes.getValue(SF_ATTRIBUTE_ANDROID_APK_SIGNED_NAME); if (apkSignatureSchemeIdList != null) { // This field contains a comma-separated list of APK signature scheme IDs which // were used to sign this APK. If an ID is known to us, it means signatures of that // scheme were stripped from the APK because otherwise we wouldn't have fallen back // to verifying the APK using the JAR signature scheme. boolean v2SignatureGenerated = false; + boolean v3SignatureGenerated = false; StringTokenizer tokenizer = new StringTokenizer(apkSignatureSchemeIdList, ","); while (tokenizer.hasMoreTokens()) { String idText = tokenizer.nextToken().trim(); @@ -402,6 +414,12 @@ class StrictJarVerifier { v2SignatureGenerated = true; break; } + if (id == ApkSignatureSchemeV3Verifier.SF_ATTRIBUTE_ANDROID_APK_SIGNED_ID) { + // This APK was supposed to be signed with APK Signature Scheme v3 but no + // such signature was found. + v3SignatureGenerated = true; + break; + } } if (v2SignatureGenerated) { @@ -409,6 +427,11 @@ class StrictJarVerifier { + " is signed using APK Signature Scheme v2, but no such signature was" + " found. Signature stripped?"); } + if (v3SignatureGenerated) { + throw new SecurityException(signatureFile + " indicates " + jarName + + " is signed using APK Signature Scheme v3, but no such signature was" + + " found. Signature stripped?"); + } } } diff --git a/core/java/android/widget/EditText.java b/core/java/android/widget/EditText.java index 56c3e4a5ee77..336c20cdcdc0 100644 --- a/core/java/android/widget/EditText.java +++ b/core/java/android/widget/EditText.java @@ -105,6 +105,11 @@ public class EditText extends TextView { @Override public Editable getText() { + CharSequence text = super.getText(); + if (text instanceof Editable) { + return (Editable) super.getText(); + } + super.setText(text, BufferType.EDITABLE); return (Editable) super.getText(); } diff --git a/core/java/android/widget/Magnifier.java b/core/java/android/widget/Magnifier.java index bd48f4554c5d..26dfcc2d668a 100644 --- a/core/java/android/widget/Magnifier.java +++ b/core/java/android/widget/Magnifier.java @@ -125,7 +125,7 @@ public final class Magnifier { mView.getWidth() - mBitmap.getWidth())); final int startY = mCenterZoomCoords.y - mBitmap.getHeight() / 2; - if (startX != mPrevStartCoordsInSurface.x || startY != mPrevStartCoordsInSurface.y) { + if (xPosInView != mPrevPosInView.x || yPosInView != mPrevPosInView.y) { performPixelCopy(startX, startY); mPrevPosInView.x = xPosInView; diff --git a/core/java/com/android/internal/net/INetworkWatchlistManager.aidl b/core/java/com/android/internal/net/INetworkWatchlistManager.aidl index 7e88369055b8..ee01a23af686 100644 --- a/core/java/com/android/internal/net/INetworkWatchlistManager.aidl +++ b/core/java/com/android/internal/net/INetworkWatchlistManager.aidl @@ -22,5 +22,6 @@ import android.os.SharedMemory; interface INetworkWatchlistManager { boolean startWatchlistLogging(); boolean stopWatchlistLogging(); + void reloadWatchlist(); void reportWatchlistIfNecessary(); } diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java index 89665854e713..1739bed7112f 100644 --- a/core/java/com/android/internal/os/BatteryStatsImpl.java +++ b/core/java/com/android/internal/os/BatteryStatsImpl.java @@ -506,8 +506,8 @@ public class BatteryStatsImpl extends BatteryStats { final HistoryEventTracker mActiveEvents = new HistoryEventTracker(); long mHistoryBaseTime; - boolean mHaveBatteryLevel = false; - boolean mRecordingHistory = false; + protected boolean mHaveBatteryLevel = false; + protected boolean mRecordingHistory = false; int mNumHistoryItems; final Parcel mHistoryBuffer = Parcel.obtain(); @@ -652,6 +652,14 @@ public class BatteryStatsImpl extends BatteryStats { new LongSamplingCounter[NUM_NETWORK_ACTIVITY_TYPES]; /** + * The WiFi Overall wakelock timer + * This timer tracks the actual aggregate time for which MC wakelocks are enabled + * since addition of per UID timers would not result in an accurate value due to overlapp of + * per uid wakelock timers + */ + StopwatchTimer mWifiMulticastWakelockTimer; + + /** * The WiFi controller activity (time in tx, rx, idle, and power consumed) for the device. */ ControllerActivityCounterImpl mWifiActivity; @@ -3975,30 +3983,85 @@ public class BatteryStatsImpl extends BatteryStats { addHistoryEventLocked(elapsedRealtime, uptime, HistoryItem.EVENT_JOB_FINISH, name, uid); } - public void noteAlarmStartLocked(String name, int uid) { + public void noteAlarmStartLocked(String name, WorkSource workSource, int uid) { + noteAlarmStartOrFinishLocked(HistoryItem.EVENT_ALARM_START, name, workSource, uid); + } + + public void noteAlarmFinishLocked(String name, WorkSource workSource, int uid) { + noteAlarmStartOrFinishLocked(HistoryItem.EVENT_ALARM_FINISH, name, workSource, uid); + } + + private void noteAlarmStartOrFinishLocked(int historyItem, String name, WorkSource workSource, + int uid) { if (!mRecordAllHistory) { return; } - uid = mapUid(uid); + final long elapsedRealtime = mClocks.elapsedRealtime(); final long uptime = mClocks.uptimeMillis(); - if (!mActiveEvents.updateState(HistoryItem.EVENT_ALARM_START, name, uid, 0)) { - return; + + if (workSource != null) { + for (int i = 0; i < workSource.size(); ++i) { + uid = mapUid(workSource.get(i)); + if (mActiveEvents.updateState(historyItem, name, uid, 0)) { + addHistoryEventLocked(elapsedRealtime, uptime, historyItem, name, uid); + } + } + + List<WorkChain> workChains = workSource.getWorkChains(); + if (workChains != null) { + for (int i = 0; i < workChains.size(); ++i) { + uid = mapUid(workChains.get(i).getAttributionUid()); + if (mActiveEvents.updateState(historyItem, name, uid, 0)) { + addHistoryEventLocked(elapsedRealtime, uptime, historyItem, name, uid); + } + } + } + } else { + uid = mapUid(uid); + + if (mActiveEvents.updateState(historyItem, name, uid, 0)) { + addHistoryEventLocked(elapsedRealtime, uptime, historyItem, name, uid); + } } - addHistoryEventLocked(elapsedRealtime, uptime, HistoryItem.EVENT_ALARM_START, name, uid); } - public void noteAlarmFinishLocked(String name, int uid) { - if (!mRecordAllHistory) { + public void noteWakupAlarmLocked(String packageName, int uid, WorkSource workSource, + String tag) { + if (!isOnBattery()) { return; } - uid = mapUid(uid); - final long elapsedRealtime = mClocks.elapsedRealtime(); - final long uptime = mClocks.uptimeMillis(); - if (!mActiveEvents.updateState(HistoryItem.EVENT_ALARM_FINISH, name, uid, 0)) { - return; + + if (workSource != null) { + for (int i = 0; i < workSource.size(); ++i) { + uid = workSource.get(i); + final String workSourceName = workSource.getName(i); + + BatteryStatsImpl.Uid.Pkg pkg = getPackageStatsLocked(uid, + workSourceName != null ? workSourceName : packageName); + pkg.noteWakeupAlarmLocked(tag); + + StatsLog.write(StatsLog.WAKEUP_ALARM_OCCURRED, uid, tag); + } + + ArrayList<WorkChain> workChains = workSource.getWorkChains(); + if (workChains != null) { + for (int i = 0; i < workChains.size(); ++i) { + final WorkChain wc = workChains.get(i); + uid = wc.getAttributionUid(); + + BatteryStatsImpl.Uid.Pkg pkg = getPackageStatsLocked(uid, packageName); + pkg.noteWakeupAlarmLocked(tag); + + // TODO(statsd): Log the full attribution chain here once it's available + StatsLog.write(StatsLog.WAKEUP_ALARM_OCCURRED, uid, tag); + } + } + } else { + BatteryStatsImpl.Uid.Pkg pkg = getPackageStatsLocked(uid, packageName); + pkg.noteWakeupAlarmLocked(tag); + StatsLog.write(StatsLog.WAKEUP_ALARM_OCCURRED, uid, tag); } - addHistoryEventLocked(elapsedRealtime, uptime, HistoryItem.EVENT_ALARM_FINISH, name, uid); } private void requestWakelockCpuUpdate() { @@ -4118,13 +4181,10 @@ public class BatteryStatsImpl extends BatteryStats { getUidStatsLocked(uid).noteStartWakeLocked(pid, name, type, elapsedRealtime); - // TODO(statsd): Use the attribution chain specified in WorkChain instead of uid. - // The debug logging here can be deleted once statsd is wired up. - if (DEBUG) { - Slog.w(TAG, "StatsLog [start]: uid=" + uid + ", type=" + type + ", name=" + name - + ", wc=" + wc); + if (wc != null) { + StatsLog.write( + StatsLog.WAKELOCK_STATE_CHANGED, wc.getUids(), wc.getTags(), type, name, 1); } - StatsLog.write(StatsLog.WAKELOCK_STATE_CHANGED, uid, type, name, 1); } } @@ -4161,14 +4221,10 @@ public class BatteryStatsImpl extends BatteryStats { } getUidStatsLocked(uid).noteStopWakeLocked(pid, name, type, elapsedRealtime); - - // TODO(statsd): Use the attribution chain specified in WorkChain instead of uid. - // The debug logging here can be deleted once statsd is wired up. - if (DEBUG) { - Slog.w(TAG, "StatsLog [stop]: uid=" + uid + ", type=" + type + ", name=" + name - + ", wc=" + wc); + if (wc != null) { + StatsLog.write( + StatsLog.WAKELOCK_STATE_CHANGED, wc.getUids(), wc.getTags(), type, name, 0); } - StatsLog.write(StatsLog.WAKELOCK_STATE_CHANGED, uid, type, name, 0); } } @@ -5541,6 +5597,12 @@ public class BatteryStatsImpl extends BatteryStats { if (DEBUG_HISTORY) Slog.v(TAG, "WIFI multicast on to: " + Integer.toHexString(mHistoryCur.states)); addHistoryRecordLocked(elapsedRealtime, uptime); + + // Start Wifi Multicast overall timer + if (!mWifiMulticastWakelockTimer.isRunningLocked()) { + if (DEBUG_HISTORY) Slog.v(TAG, "WiFi Multicast Overall Timer Started"); + mWifiMulticastWakelockTimer.startRunningLocked(elapsedRealtime); + } } mWifiMulticastNesting++; getUidStatsLocked(uid).noteWifiMulticastEnabledLocked(elapsedRealtime); @@ -5556,6 +5618,12 @@ public class BatteryStatsImpl extends BatteryStats { if (DEBUG_HISTORY) Slog.v(TAG, "WIFI multicast off to: " + Integer.toHexString(mHistoryCur.states)); addHistoryRecordLocked(elapsedRealtime, uptime); + + // Stop Wifi Multicast overall timer + if (mWifiMulticastWakelockTimer.isRunningLocked()) { + if (DEBUG_HISTORY) Slog.v(TAG, "Multicast Overall Timer Stopped"); + mWifiMulticastWakelockTimer.stopRunningLocked(elapsedRealtime); + } } getUidStatsLocked(uid).noteWifiMulticastDisabledLocked(elapsedRealtime); } @@ -5842,6 +5910,16 @@ public class BatteryStatsImpl extends BatteryStats { return (int)mMobileRadioActiveUnknownCount.getCountLocked(which); } + @Override public long getWifiMulticastWakelockTime( + long elapsedRealtimeUs, int which) { + return mWifiMulticastWakelockTimer.getTotalTimeLocked( + elapsedRealtimeUs, which); + } + + @Override public int getWifiMulticastWakelockCount(int which) { + return mWifiMulticastWakelockTimer.getCountLocked(which); + } + @Override public long getWifiOnTime(long elapsedRealtimeUs, int which) { return mWifiOnTimer.getTotalTimeLocked(elapsedRealtimeUs, which); } @@ -9442,6 +9520,8 @@ public class BatteryStatsImpl extends BatteryStats { mMobileRadioActiveAdjustedTime = new LongSamplingCounter(mOnBatteryTimeBase); mMobileRadioActiveUnknownTime = new LongSamplingCounter(mOnBatteryTimeBase); mMobileRadioActiveUnknownCount = new LongSamplingCounter(mOnBatteryTimeBase); + mWifiMulticastWakelockTimer = new StopwatchTimer(mClocks, null, + WIFI_AGGREGATE_MULTICAST_ENABLED, null, mOnBatteryTimeBase); mWifiOnTimer = new StopwatchTimer(mClocks, null, -4, null, mOnBatteryTimeBase); mGlobalWifiRunningTimer = new StopwatchTimer(mClocks, null, -5, null, mOnBatteryTimeBase); for (int i=0; i<NUM_WIFI_STATES; i++) { @@ -10143,6 +10223,7 @@ public class BatteryStatsImpl extends BatteryStats { for (int i=0; i<NUM_WIFI_SIGNAL_STRENGTH_BINS; i++) { mWifiSignalStrengthsTimer[i].reset(false); } + mWifiMulticastWakelockTimer.reset(false); mWifiActivity.reset(false); mBluetoothActivity.reset(false); mModemActivity.reset(false); @@ -12589,6 +12670,7 @@ public class BatteryStatsImpl extends BatteryStats { mMobileRadioActiveAdjustedTime.readSummaryFromParcelLocked(in); mMobileRadioActiveUnknownTime.readSummaryFromParcelLocked(in); mMobileRadioActiveUnknownCount.readSummaryFromParcelLocked(in); + mWifiMulticastWakelockTimer.readSummaryFromParcelLocked(in); mWifiRadioPowerState = DataConnectionRealTimeInfo.DC_POWER_STATE_LOW; mWifiOn = false; mWifiOnTimer.readSummaryFromParcelLocked(in); @@ -13029,6 +13111,7 @@ public class BatteryStatsImpl extends BatteryStats { mMobileRadioActiveAdjustedTime.writeSummaryFromParcelLocked(out); mMobileRadioActiveUnknownTime.writeSummaryFromParcelLocked(out); mMobileRadioActiveUnknownCount.writeSummaryFromParcelLocked(out); + mWifiMulticastWakelockTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS); mWifiOnTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS); mGlobalWifiRunningTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS); for (int i=0; i<NUM_WIFI_STATES; i++) { @@ -13492,6 +13575,8 @@ public class BatteryStatsImpl extends BatteryStats { mMobileRadioActiveAdjustedTime = new LongSamplingCounter(mOnBatteryTimeBase, in); mMobileRadioActiveUnknownTime = new LongSamplingCounter(mOnBatteryTimeBase, in); mMobileRadioActiveUnknownCount = new LongSamplingCounter(mOnBatteryTimeBase, in); + mWifiMulticastWakelockTimer = new StopwatchTimer(mClocks, null, -4, null, + mOnBatteryTimeBase, in); mWifiRadioPowerState = DataConnectionRealTimeInfo.DC_POWER_STATE_LOW; mWifiOn = false; mWifiOnTimer = new StopwatchTimer(mClocks, null, -4, null, mOnBatteryTimeBase, in); @@ -13698,6 +13783,7 @@ public class BatteryStatsImpl extends BatteryStats { mMobileRadioActiveAdjustedTime.writeToParcel(out); mMobileRadioActiveUnknownTime.writeToParcel(out); mMobileRadioActiveUnknownCount.writeToParcel(out); + mWifiMulticastWakelockTimer.writeToParcel(out, uSecRealtime); mWifiOnTimer.writeToParcel(out, uSecRealtime); mGlobalWifiRunningTimer.writeToParcel(out, uSecRealtime); for (int i=0; i<NUM_WIFI_STATES; i++) { @@ -13884,6 +13970,8 @@ public class BatteryStatsImpl extends BatteryStats { mMobileRadioActiveTimer.logState(pr, " "); pr.println("*** Mobile network active adjusted timer:"); mMobileRadioActiveAdjustedTime.logState(pr, " "); + pr.println("*** Wifi Multicast WakeLock Timer:"); + mWifiMulticastWakelockTimer.logState(pr, " "); pr.println("*** mWifiRadioPowerState=" + mWifiRadioPowerState); pr.println("*** Wifi timer:"); mWifiOnTimer.logState(pr, " "); diff --git a/core/java/com/android/internal/widget/ButtonBarLayout.java b/core/java/com/android/internal/widget/ButtonBarLayout.java index 82affe2f86b1..ab8be33599fa 100644 --- a/core/java/com/android/internal/widget/ButtonBarLayout.java +++ b/core/java/com/android/internal/widget/ButtonBarLayout.java @@ -30,9 +30,6 @@ import com.android.internal.R; * orientation when it can't fit its child views horizontally. */ public class ButtonBarLayout extends LinearLayout { - /** Minimum screen height required for button stacking. */ - private static final int ALLOW_STACKING_MIN_HEIGHT_DP = 320; - /** Amount of the second button to "peek" above the fold when stacked. */ private static final int PEEK_BUTTON_DP = 16; @@ -46,12 +43,8 @@ public class ButtonBarLayout extends LinearLayout { public ButtonBarLayout(Context context, AttributeSet attrs) { super(context, attrs); - final boolean allowStackingDefault = - context.getResources().getConfiguration().screenHeightDp - >= ALLOW_STACKING_MIN_HEIGHT_DP; final TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.ButtonBarLayout); - mAllowStacking = ta.getBoolean(R.styleable.ButtonBarLayout_allowStacking, - allowStackingDefault); + mAllowStacking = ta.getBoolean(R.styleable.ButtonBarLayout_allowStacking, true); ta.recycle(); } diff --git a/core/proto/android/os/batterystats.proto b/core/proto/android/os/batterystats.proto index 0fa428e9c114..da21258bc58a 100644 --- a/core/proto/android/os/batterystats.proto +++ b/core/proto/android/os/batterystats.proto @@ -537,7 +537,24 @@ message UidProto { // Screen-off CPU time in milliseconds. optional int64 screen_off_duration_ms = 3; } + // CPU times accumulated across all process states. repeated ByFrequency by_frequency = 3; + + enum ProcessState { + TOP = 0; + FOREGROUND_SERVICE = 1; + FOREGROUND = 2; + BACKGROUND = 3; + TOP_SLEEPING = 4; + HEAVY_WEIGHT = 5; + CACHED = 6; + } + // CPU times at different process states. + message ByProcessState { + optional ProcessState process_state = 1; + repeated ByFrequency by_frequency = 2; + } + repeated ByProcessState by_process_state = 4; } optional Cpu cpu = 7; diff --git a/core/proto/android/os/batterytype.proto b/core/proto/android/os/batterytype.proto new file mode 100644 index 000000000000..75d0dd3504e3 --- /dev/null +++ b/core/proto/android/os/batterytype.proto @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2017 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. + */ + +syntax = "proto2"; + +package android.os; + +option java_multiple_files = true; + +message BatteryTypeProto { + optional string type = 1; +} diff --git a/core/proto/android/os/cpuinfo.proto b/core/proto/android/os/cpuinfo.proto index 522ff24c1e60..cd151e253e7a 100644 --- a/core/proto/android/os/cpuinfo.proto +++ b/core/proto/android/os/cpuinfo.proto @@ -81,7 +81,9 @@ message CpuInfo { optional string virt = 8; // virtual memory size, i.e. 14.0G, 13.5M optional string res = 9; // Resident size, i.e. 0, 3.1G - // How Android memory manager will treat the task + // How Android memory manager will treat the task. + // TODO: use PsDumpProto.Process.Policy instead once we extern variables + // and are able to include the same .h file in two files. enum Policy { POLICY_UNKNOWN = 0; POLICY_fg = 1; // foreground, the name is lower case for parsing the value diff --git a/core/proto/android/os/incident.proto b/core/proto/android/os/incident.proto index ac8f26d94ca2..a6db31f67b33 100644 --- a/core/proto/android/os/incident.proto +++ b/core/proto/android/os/incident.proto @@ -18,12 +18,14 @@ syntax = "proto2"; option java_multiple_files = true; option java_outer_classname = "IncidentProtoMetadata"; +import "frameworks/base/core/proto/android/os/batterytype.proto"; import "frameworks/base/core/proto/android/os/cpufreq.proto"; import "frameworks/base/core/proto/android/os/cpuinfo.proto"; import "frameworks/base/core/proto/android/os/incidentheader.proto"; import "frameworks/base/core/proto/android/os/kernelwake.proto"; import "frameworks/base/core/proto/android/os/pagetypeinfo.proto"; import "frameworks/base/core/proto/android/os/procrank.proto"; +import "frameworks/base/core/proto/android/os/ps.proto"; import "frameworks/base/core/proto/android/os/system_properties.proto"; import "frameworks/base/core/proto/android/providers/settings.proto"; import "frameworks/base/core/proto/android/server/activitymanagerservice.proto"; @@ -65,7 +67,7 @@ message IncidentProto { // Device information optional SystemPropertiesProto system_properties = 1000 [ (section).type = SECTION_COMMAND, - (section).args = "/system/bin/getprop" + (section).args = "getprop" ]; // Linux services @@ -86,7 +88,7 @@ message IncidentProto { optional CpuInfo cpu_info = 2003 [ (section).type = SECTION_COMMAND, - (section).args = "/system/bin/top -b -n 1 -H -s 6 -o pid,tid,user,pr,ni,%cpu,s,virt,res,pcy,cmd,name" + (section).args = "top -b -n 1 -H -s 6 -o pid,tid,user,pr,ni,%cpu,s,virt,res,pcy,cmd,name" ]; optional CpuFreq cpu_freq = 2004 [ @@ -94,6 +96,16 @@ message IncidentProto { (section).args = "/sys/devices/system/cpu/cpufreq/all_time_in_state" ]; + optional PsDumpProto processes_and_threads = 2005 [ + (section).type = SECTION_COMMAND, + (section).args = "ps -A -T -Z -O pri,nice,rtprio,sched,pcy,time" + ]; + + optional BatteryTypeProto battery_type = 2006 [ + (section).type = SECTION_FILE, + (section).args = "/sys/class/power_supply/bms/battery_type" + ]; + // System Services optional com.android.server.fingerprint.FingerprintServiceDumpProto fingerprint = 3000 [ (section).type = SECTION_DUMPSYS, diff --git a/core/proto/android/os/ps.proto b/core/proto/android/os/ps.proto new file mode 100644 index 000000000000..88c6609a92b6 --- /dev/null +++ b/core/proto/android/os/ps.proto @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2017 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. + */ + +syntax = "proto2"; + +package android.os; + +option java_multiple_files = true; + +message PsDumpProto { + message Process { + // Security label, most commonly used for SELinux context data. + optional string label = 1; + optional string user = 2; + // Process ID number. + optional int32 pid = 3; + // The unique number representing a dispatchable entity (alias lwp, + // spid). This value may also appear as: a process ID (pid); a process + // group ID (pgrp); a session ID for the session leader (sid); a thread + // group ID for the thread group leader (tgid); and a tty process group + // ID for the process group leader (tpgid). + optional int32 tid = 4; + // Parent process ID. + optional int32 ppid = 5; + // Virtual set size (memory size) of the process, in KiB. + optional int32 vsz = 6; + // Resident set size. How many physical pages are associated with the + // process; real memory usage, in KiB. + optional int32 rss = 7; + // Name of the kernel function in which the process is sleeping, a "-" + // if the process is running, or a "*" if the process is multi-threaded + // and ps is not displaying threads. + optional string wchan = 8; + // Memory address of the process. + optional string addr = 9; + + enum ProcessStateCode { + STATE_UNKNOWN = 0; + // Uninterruptible sleep (usually IO). + STATE_D = 1; + // Running or runnable (on run queue). + STATE_R = 2; + // Interruptible sleep (waiting for an event to complete). + STATE_S = 3; + // Stopped by job control signal. + STATE_T = 4; + // Stopped by debugger during the tracing. + STATE_TRACING = 5; + // Dead (should never be seen). + STATE_X = 6; + // Defunct ("zombie") process. Terminated but not reaped by its + // parent. + STATE_Z = 7; + } + // Minimal state display + optional ProcessStateCode s = 10; + // Priority of the process. Higher number means lower priority. + optional int32 pri = 11; + // Nice value. This ranges from 19 (nicest) to -20 (not nice to others). + optional sint32 ni = 12; + // Realtime priority. + optional string rtprio = 13; // Number or - + + enum SchedulingPolicy { + option allow_alias = true; + + // Regular names conflict with macros defined in + // bionic/libc/kernel/uapi/linux/sched.h. + SCH_OTHER = 0; + SCH_NORMAL = 0; + + SCH_FIFO = 1; + SCH_RR = 2; + SCH_BATCH = 3; + SCH_ISO = 4; + SCH_IDLE = 5; + } + // Scheduling policy of the process. + optional SchedulingPolicy sch = 14; + + // How Android memory manager will treat the task + enum Policy { + POLICY_UNKNOWN = 0; + // Foreground. + POLICY_FG = 1; + // Background. + POLICY_BG = 2; + POLICY_TA = 3; // TODO: figure out what this value is + } + optional Policy pcy = 15; + // Total CPU time, "[DD-]HH:MM:SS" format. + optional string time = 16; + // Command with all its arguments as a string. + optional string cmd = 17; + } + repeated Process processes = 1; +} diff --git a/core/proto/android/os/worksource.proto b/core/proto/android/os/worksource.proto index c60edfc3862c..2f8b2fbbaa2b 100644 --- a/core/proto/android/os/worksource.proto +++ b/core/proto/android/os/worksource.proto @@ -25,5 +25,10 @@ message WorkSourceProto { optional string name = 2; } + message WorkChain { + repeated WorkSourceContentProto nodes = 1; + } + repeated WorkSourceContentProto work_source_contents = 1; -}
\ No newline at end of file + repeated WorkChain work_chains = 2; +} diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 272e3c73bf89..52bfcdebf436 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -3870,6 +3870,14 @@ </intent-filter> </receiver> + <receiver android:name="com.android.server.updates.NetworkWatchlistInstallReceiver" + android:permission="android.permission.UPDATE_CONFIG"> + <intent-filter> + <action android:name="android.intent.action.UPDATE_NETWORK_WATCHLIST" /> + <data android:scheme="content" android:host="*" android:mimeType="*/*" /> + </intent-filter> + </receiver> + <receiver android:name="com.android.server.updates.ApnDbInstallReceiver" android:permission="android.permission.UPDATE_CONFIG"> <intent-filter> diff --git a/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java b/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java index aefc47e95512..c19a343957c0 100644 --- a/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java +++ b/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java @@ -42,7 +42,8 @@ import org.junit.runner.RunWith; @RunWith(AndroidJUnit4.class) @SmallTest -@Presubmit +// TODO: b/70616950 +//@Presubmit public class ObjectPoolTests { // 1. Check if two obtained objects from pool are not the same. diff --git a/core/tests/coretests/src/android/os/WorkSourceTest.java b/core/tests/coretests/src/android/os/WorkSourceTest.java index e2f800fb7db3..90b457561180 100644 --- a/core/tests/coretests/src/android/os/WorkSourceTest.java +++ b/core/tests/coretests/src/android/os/WorkSourceTest.java @@ -322,4 +322,13 @@ public class WorkSourceTest extends TestCase { assertEquals(new WorkChain().addNode(0, "tag0"), diffs[1].get(0)); assertEquals(new WorkChain().addNode(2, "tag2"), diffs[1].get(1)); } + + public void testGetAttributionId() { + WorkSource ws1 = new WorkSource(); + WorkChain wc = ws1.createWorkChain(); + wc.addNode(100, "tag"); + assertEquals(100, wc.getAttributionUid()); + wc.addNode(200, "tag2"); + assertEquals(100, wc.getAttributionUid()); + } } diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java index eef9866d7691..fc8650086cd8 100644 --- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java +++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java @@ -525,7 +525,8 @@ public class SettingsBackupTest { Settings.Secure.VOICE_INTERACTION_SERVICE, Settings.Secure.VOICE_RECOGNITION_SERVICE, Settings.Secure.INSTANT_APPS_ENABLED, - Settings.Secure.BACKUP_MANAGER_CONSTANTS); + Settings.Secure.BACKUP_MANAGER_CONSTANTS, + Settings.Secure.KEYGUARD_SLICE_URI); @Test public void systemSettingsBackedUpOrBlacklisted() { diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java index 0afec34da958..a55563afbcc9 100644 --- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java +++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java @@ -15,16 +15,19 @@ */ package com.android.internal.os; +import static android.os.BatteryStats.STATS_CURRENT; import static android.os.BatteryStats.STATS_SINCE_CHARGED; import static android.os.BatteryStats.WAKE_TYPE_PARTIAL; import android.app.ActivityManager; import android.os.BatteryManager; import android.os.BatteryStats; +import android.os.BatteryStats.HistoryItem; import android.os.WorkSource; import android.support.test.filters.SmallTest; import android.view.Display; +import com.android.internal.os.BatteryStatsImpl.Uid; import junit.framework.TestCase; import java.util.ArrayList; @@ -44,11 +47,14 @@ import java.util.Map; * Run: adb shell am instrument -e class com.android.internal.os.BatteryStatsNoteTest -w \ * com.android.frameworks.coretests/android.support.test.runner.AndroidJUnitRunner */ -public class BatteryStatsNoteTest extends TestCase{ +public class BatteryStatsNoteTest extends TestCase { + private static final int UID = 10500; private static final WorkSource WS = new WorkSource(UID); - /** Test BatteryStatsImpl.Uid.noteBluetoothScanResultLocked. */ + /** + * Test BatteryStatsImpl.Uid.noteBluetoothScanResultLocked. + */ @SmallTest public void testNoteBluetoothScanResultLocked() throws Exception { MockBatteryStatsImpl bi = new MockBatteryStatsImpl(new MockClocks()); @@ -75,7 +81,9 @@ public class BatteryStatsNoteTest extends TestCase{ .getCountLocked(STATS_SINCE_CHARGED)); } - /** Test BatteryStatsImpl.Uid.noteStartWakeLocked. */ + /** + * Test BatteryStatsImpl.Uid.noteStartWakeLocked. + */ @SmallTest public void testNoteStartWakeLocked() throws Exception { final MockClocks clocks = new MockClocks(); // holds realtime and uptime in ms @@ -86,7 +94,8 @@ public class BatteryStatsNoteTest extends TestCase{ bi.updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0); bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_TOP); - bi.getUidStatsLocked(UID).noteStartWakeLocked(pid, name, WAKE_TYPE_PARTIAL, clocks.realtime); + bi.getUidStatsLocked(UID) + .noteStartWakeLocked(pid, name, WAKE_TYPE_PARTIAL, clocks.realtime); clocks.realtime = clocks.uptime = 100; bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND); @@ -94,7 +103,8 @@ public class BatteryStatsNoteTest extends TestCase{ clocks.realtime = clocks.uptime = 220; bi.getUidStatsLocked(UID).noteStopWakeLocked(pid, name, WAKE_TYPE_PARTIAL, clocks.realtime); - BatteryStats.Timer aggregTimer = bi.getUidStats().get(UID).getAggregatedPartialWakelockTimer(); + BatteryStats.Timer aggregTimer = bi.getUidStats().get(UID) + .getAggregatedPartialWakelockTimer(); long actualTime = aggregTimer.getTotalTimeLocked(300_000, STATS_SINCE_CHARGED); long bgTime = aggregTimer.getSubTimer().getTotalTimeLocked(300_000, STATS_SINCE_CHARGED); assertEquals(220_000, actualTime); @@ -102,7 +112,9 @@ public class BatteryStatsNoteTest extends TestCase{ } - /** Test BatteryStatsImpl.noteUidProcessStateLocked. */ + /** + * Test BatteryStatsImpl.noteUidProcessStateLocked. + */ @SmallTest public void testNoteUidProcessStateLocked() throws Exception { final MockClocks clocks = new MockClocks(); @@ -145,7 +157,6 @@ public class BatteryStatsNoteTest extends TestCase{ expectedRunTimeMs = stateRuntimeMap.get(ActivityManager.PROCESS_STATE_TOP); assertEquals(expectedRunTimeMs * 1000, actualRunTimeUs); - actualRunTimeUs = uid.getProcessStateTime(BatteryStats.Uid.PROCESS_STATE_FOREGROUND_SERVICE, elapsedTimeUs, STATS_SINCE_CHARGED); expectedRunTimeMs = stateRuntimeMap.get( @@ -153,19 +164,16 @@ public class BatteryStatsNoteTest extends TestCase{ + stateRuntimeMap.get(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE); assertEquals(expectedRunTimeMs * 1000, actualRunTimeUs); - actualRunTimeUs = uid.getProcessStateTime(BatteryStats.Uid.PROCESS_STATE_TOP_SLEEPING, elapsedTimeUs, STATS_SINCE_CHARGED); expectedRunTimeMs = stateRuntimeMap.get(ActivityManager.PROCESS_STATE_TOP_SLEEPING); assertEquals(expectedRunTimeMs * 1000, actualRunTimeUs); - actualRunTimeUs = uid.getProcessStateTime(BatteryStats.Uid.PROCESS_STATE_FOREGROUND, elapsedTimeUs, STATS_SINCE_CHARGED); expectedRunTimeMs = stateRuntimeMap.get(ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND); assertEquals(expectedRunTimeMs * 1000, actualRunTimeUs); - actualRunTimeUs = uid.getProcessStateTime(BatteryStats.Uid.PROCESS_STATE_BACKGROUND, elapsedTimeUs, STATS_SINCE_CHARGED); expectedRunTimeMs = stateRuntimeMap.get(ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND) @@ -174,7 +182,6 @@ public class BatteryStatsNoteTest extends TestCase{ + stateRuntimeMap.get(ActivityManager.PROCESS_STATE_RECEIVER); assertEquals(expectedRunTimeMs * 1000, actualRunTimeUs); - actualRunTimeUs = uid.getProcessStateTime(BatteryStats.Uid.PROCESS_STATE_CACHED, elapsedTimeUs, STATS_SINCE_CHARGED); expectedRunTimeMs = stateRuntimeMap.get(ActivityManager.PROCESS_STATE_HOME) @@ -191,7 +198,9 @@ public class BatteryStatsNoteTest extends TestCase{ assertEquals(expectedRunTimeMs * 1000, actualRunTimeUs); } - /** Test BatteryStatsImpl.updateTimeBasesLocked. */ + /** + * Test BatteryStatsImpl.updateTimeBasesLocked. + */ @SmallTest public void testUpdateTimeBasesLocked() throws Exception { final MockClocks clocks = new MockClocks(); // holds realtime and uptime in ms @@ -213,7 +222,9 @@ public class BatteryStatsNoteTest extends TestCase{ assertTrue(bi.getOnBatteryScreenOffTimeBase().isRunning()); } - /** Test BatteryStatsImpl.noteScreenStateLocked sets timebases and screen states correctly. */ + /** + * Test BatteryStatsImpl.noteScreenStateLocked sets timebases and screen states correctly. + */ @SmallTest public void testNoteScreenStateLocked() throws Exception { final MockClocks clocks = new MockClocks(); // holds realtime and uptime in ms @@ -232,15 +243,13 @@ public class BatteryStatsNoteTest extends TestCase{ assertEquals(bi.getScreenState(), Display.STATE_OFF); } - /** Test BatteryStatsImpl.noteScreenStateLocked updates timers correctly. + /** + * Test BatteryStatsImpl.noteScreenStateLocked updates timers correctly. * - * Unknown and doze should both be subset of off state + * Unknown and doze should both be subset of off state * - * Timeline 0----100----200----310----400------------1000 - * Unknown ------- - * On ------- - * Off ------- ---------------------- - * Doze ---------------- + * Timeline 0----100----200----310----400------------1000 Unknown ------- On ------- Off + * ------- ---------------------- Doze ---------------- */ @SmallTest public void testNoteScreenStateTimersLocked() throws Exception { @@ -280,4 +289,161 @@ public class BatteryStatsNoteTest extends TestCase{ assertEquals(600_000, bi.getScreenDozeTime(1500_000, STATS_SINCE_CHARGED)); } + @SmallTest + public void testAlarmStartAndFinishLocked() throws Exception { + final MockClocks clocks = new MockClocks(); // holds realtime and uptime in ms + MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks); + bi.setRecordAllHistoryLocked(true); + bi.forceRecordAllHistory(); + + bi.updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0); + bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_TOP); + + clocks.realtime = clocks.uptime = 100; + bi.noteAlarmStartLocked("foo", null, UID); + clocks.realtime = clocks.uptime = 5000; + bi.noteAlarmFinishLocked("foo", null, UID); + + HistoryItem item = new HistoryItem(); + assertTrue(bi.startIteratingHistoryLocked()); + + assertTrue(bi.getNextHistoryLocked(item)); + assertEquals(HistoryItem.EVENT_ALARM_START, item.eventCode); + assertEquals("foo", item.eventTag.string); + assertEquals(UID, item.eventTag.uid); + + // TODO(narayan): Figure out why this event is written to the history buffer. See + // test below where it is being interspersed between multiple START events too. + assertTrue(bi.getNextHistoryLocked(item)); + assertEquals(HistoryItem.EVENT_NONE, item.eventCode); + + assertTrue(bi.getNextHistoryLocked(item)); + assertEquals(HistoryItem.EVENT_ALARM_FINISH, item.eventCode); + assertTrue(item.isDeltaData()); + assertEquals("foo", item.eventTag.string); + assertEquals(UID, item.eventTag.uid); + + assertFalse(bi.getNextHistoryLocked(item)); + } + + @SmallTest + public void testAlarmStartAndFinishLocked_workSource() throws Exception { + final MockClocks clocks = new MockClocks(); // holds realtime and uptime in ms + MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks); + bi.setRecordAllHistoryLocked(true); + bi.forceRecordAllHistory(); + + bi.updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0); + bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_TOP); + + WorkSource ws = new WorkSource(); + ws.add(100); + ws.createWorkChain().addNode(500, "tag"); + bi.noteAlarmStartLocked("foo", ws, UID); + clocks.realtime = clocks.uptime = 5000; + bi.noteAlarmFinishLocked("foo", ws, UID); + + HistoryItem item = new HistoryItem(); + assertTrue(bi.startIteratingHistoryLocked()); + + assertTrue(bi.getNextHistoryLocked(item)); + assertEquals(HistoryItem.EVENT_ALARM_START, item.eventCode); + assertEquals("foo", item.eventTag.string); + assertEquals(100, item.eventTag.uid); + + assertTrue(bi.getNextHistoryLocked(item)); + assertEquals(HistoryItem.EVENT_NONE, item.eventCode); + + assertTrue(bi.getNextHistoryLocked(item)); + assertEquals(HistoryItem.EVENT_ALARM_START, item.eventCode); + assertEquals("foo", item.eventTag.string); + assertEquals(500, item.eventTag.uid); + + assertTrue(bi.getNextHistoryLocked(item)); + assertEquals(HistoryItem.EVENT_ALARM_FINISH, item.eventCode); + assertEquals("foo", item.eventTag.string); + assertEquals(100, item.eventTag.uid); + + assertTrue(bi.getNextHistoryLocked(item)); + assertEquals(HistoryItem.EVENT_ALARM_FINISH, item.eventCode); + assertEquals("foo", item.eventTag.string); + assertEquals(500, item.eventTag.uid); + } + + @SmallTest + public void testNoteWakupAlarmLocked() { + final MockClocks clocks = new MockClocks(); // holds realtime and uptime in ms + MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks); + bi.setRecordAllHistoryLocked(true); + bi.forceRecordAllHistory(); + bi.mForceOnBattery = true; + + bi.updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0); + bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_TOP); + + bi.noteWakupAlarmLocked("com.foo.bar", UID, null, "tag"); + + Uid.Pkg pkg = bi.getPackageStatsLocked(UID, "com.foo.bar"); + assertEquals(1, pkg.getWakeupAlarmStats().get("tag").getCountLocked(STATS_CURRENT)); + assertEquals(1, pkg.getWakeupAlarmStats().size()); + } + + @SmallTest + public void testNoteWakupAlarmLocked_workSource_uid() { + final MockClocks clocks = new MockClocks(); // holds realtime and uptime in ms + MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks); + bi.setRecordAllHistoryLocked(true); + bi.forceRecordAllHistory(); + bi.mForceOnBattery = true; + + bi.updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0); + bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_TOP); + + WorkSource ws = new WorkSource(); + ws.add(100); + + // When a WorkSource is present, "UID" should not be used - only the uids present in the + // WorkSource should be reported. + bi.noteWakupAlarmLocked("com.foo.bar", UID, ws, "tag"); + Uid.Pkg pkg = bi.getPackageStatsLocked(UID, "com.foo.bar"); + assertEquals(0, pkg.getWakeupAlarmStats().size()); + pkg = bi.getPackageStatsLocked(100, "com.foo.bar"); + assertEquals(1, pkg.getWakeupAlarmStats().size()); + + // If the WorkSource contains a "name", it should be interpreted as a package name and + // the packageName supplied as an argument must be ignored. + ws = new WorkSource(); + ws.add(100, "com.foo.baz_alternate"); + bi.noteWakupAlarmLocked("com.foo.baz", UID, ws, "tag"); + pkg = bi.getPackageStatsLocked(100, "com.foo.baz"); + assertEquals(0, pkg.getWakeupAlarmStats().size()); + pkg = bi.getPackageStatsLocked(100, "com.foo.baz_alternate"); + assertEquals(1, pkg.getWakeupAlarmStats().size()); + } + + @SmallTest + public void testNoteWakupAlarmLocked_workSource_workChain() { + final MockClocks clocks = new MockClocks(); // holds realtime and uptime in ms + MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks); + bi.setRecordAllHistoryLocked(true); + bi.forceRecordAllHistory(); + bi.mForceOnBattery = true; + + bi.updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0); + bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_TOP); + + WorkSource ws = new WorkSource(); + ws.createWorkChain().addNode(100, "com.foo.baz_alternate"); + bi.noteWakupAlarmLocked("com.foo.bar", UID, ws, "tag"); + + // For WorkChains, again we must only attribute to the uids present in the WorkSource + // (and not to "UID"). However, unlike the older "tags" we do not change the packagename + // supplied as an argument, given that we're logging the entire attribution chain. + Uid.Pkg pkg = bi.getPackageStatsLocked(UID, "com.foo.bar"); + assertEquals(0, pkg.getWakeupAlarmStats().size()); + pkg = bi.getPackageStatsLocked(100, "com.foo.bar"); + assertEquals(1, pkg.getWakeupAlarmStats().size()); + pkg = bi.getPackageStatsLocked(100, "com.foo.baz_alternate"); + assertEquals(0, pkg.getWakeupAlarmStats().size()); + } } diff --git a/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java b/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java index de2fd12267d5..f19ff6708c69 100644 --- a/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java +++ b/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java @@ -16,6 +16,8 @@ package com.android.internal.os; +import android.os.Handler; +import android.os.Looper; import android.util.SparseIntArray; import java.util.ArrayList; @@ -37,6 +39,9 @@ public class MockBatteryStatsImpl extends BatteryStatsImpl { mOnBatteryTimeBase); mBluetoothScanTimer = new StopwatchTimer(mClocks, null, -14, null, mOnBatteryTimeBase); setExternalStatsSyncLocked(new DummyExternalStatsSync()); + + // A no-op handler. + mHandler = new Handler(Looper.getMainLooper()) {}; } MockBatteryStatsImpl() { @@ -59,6 +64,12 @@ public class MockBatteryStatsImpl extends BatteryStatsImpl { return mForceOnBattery ? true : super.isOnBattery(); } + public void forceRecordAllHistory() { + mHaveBatteryLevel = true; + mRecordingHistory = true; + mRecordAllHistory = true; + } + public TimeBase getOnBatteryBackgroundTimeBase(int uid) { return getUidStatsLocked(uid).mOnBatteryBackgroundTimeBase; } diff --git a/libs/hwui/tests/common/TestUtils.cpp b/libs/hwui/tests/common/TestUtils.cpp index 1e424258b016..4a0d6ee19978 100644 --- a/libs/hwui/tests/common/TestUtils.cpp +++ b/libs/hwui/tests/common/TestUtils.cpp @@ -122,15 +122,19 @@ void TestUtils::layoutTextUnscaled(const SkPaint& paint, const char* text, void TestUtils::drawUtf8ToCanvas(Canvas* canvas, const char* text, const SkPaint& paint, float x, float y) { auto utf16 = asciiToUtf16(text); - canvas->drawText(utf16.get(), 0, strlen(text), strlen(text), x, y, minikin::Bidi::LTR, paint, - nullptr); + SkPaint glyphPaint(paint); + glyphPaint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); + canvas->drawText(utf16.get(), 0, strlen(text), strlen(text), x, y, minikin::Bidi::LTR, + glyphPaint, nullptr); } void TestUtils::drawUtf8ToCanvas(Canvas* canvas, const char* text, const SkPaint& paint, const SkPath& path) { auto utf16 = asciiToUtf16(text); - canvas->drawTextOnPath(utf16.get(), strlen(text), minikin::Bidi::LTR, path, 0, 0, paint, - nullptr); + SkPaint glyphPaint(paint); + glyphPaint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); + canvas->drawTextOnPath(utf16.get(), strlen(text), minikin::Bidi::LTR, path, 0, 0, glyphPaint, + nullptr); } void TestUtils::TestTask::run() { diff --git a/libs/hwui/tests/common/scenes/GlyphStressAnimation.cpp b/libs/hwui/tests/common/scenes/GlyphStressAnimation.cpp index bf0aed98cd70..38999cb1d2ec 100644 --- a/libs/hwui/tests/common/scenes/GlyphStressAnimation.cpp +++ b/libs/hwui/tests/common/scenes/GlyphStressAnimation.cpp @@ -40,22 +40,18 @@ public: } void doFrame(int frameNr) override { - std::unique_ptr<uint16_t[]> text = - TestUtils::asciiToUtf16("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"); - ssize_t textLength = 26 * 2; + const char* text = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; std::unique_ptr<Canvas> canvas( Canvas::create_recording_canvas(container->stagingProperties().getWidth(), container->stagingProperties().getHeight())); Paint paint; - paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); paint.setAntiAlias(true); paint.setColor(Color::Black); for (int i = 0; i < 5; i++) { paint.setTextSize(10 + (frameNr % 20) + i * 20); - canvas->drawText(text.get(), 0, textLength, textLength, 0, 100 * (i + 2), - minikin::Bidi::FORCE_LTR, paint, nullptr); + TestUtils::drawUtf8ToCanvas(canvas.get(), text, paint, 0, 100 * (i + 2)); } container->setStagingDisplayList(canvas->finishRecording()); diff --git a/libs/hwui/tests/common/scenes/ListOfFadedTextAnimation.cpp b/libs/hwui/tests/common/scenes/ListOfFadedTextAnimation.cpp index 6bae80c120ab..58c99800875b 100644 --- a/libs/hwui/tests/common/scenes/ListOfFadedTextAnimation.cpp +++ b/libs/hwui/tests/common/scenes/ListOfFadedTextAnimation.cpp @@ -36,7 +36,6 @@ class ListOfFadedTextAnimation : public TestListViewSceneBase { SkPaint textPaint; textPaint.setTextSize(dp(20)); textPaint.setAntiAlias(true); - textPaint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); TestUtils::drawUtf8ToCanvas(&canvas, "not that long long text", textPaint, dp(10), dp(30)); SkPoint pts[2]; diff --git a/libs/hwui/tests/common/scenes/ListViewAnimation.cpp b/libs/hwui/tests/common/scenes/ListViewAnimation.cpp index d7ec288a8a7b..fd8c252ff318 100644 --- a/libs/hwui/tests/common/scenes/ListViewAnimation.cpp +++ b/libs/hwui/tests/common/scenes/ListViewAnimation.cpp @@ -83,7 +83,6 @@ class ListViewAnimation : public TestListViewSceneBase { canvas.drawRoundRect(0, 0, itemWidth, itemHeight, dp(6), dp(6), roundRectPaint); SkPaint textPaint; - textPaint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); textPaint.setColor(rand() % 2 ? Color::Black : Color::Grey_500); textPaint.setTextSize(dp(20)); textPaint.setAntiAlias(true); diff --git a/libs/hwui/tests/common/scenes/MagnifierAnimation.cpp b/libs/hwui/tests/common/scenes/MagnifierAnimation.cpp index 9eddc209a9a9..aa537b4f329c 100644 --- a/libs/hwui/tests/common/scenes/MagnifierAnimation.cpp +++ b/libs/hwui/tests/common/scenes/MagnifierAnimation.cpp @@ -38,7 +38,6 @@ public: card = TestUtils::createNode( 0, 0, width, height, [&](RenderProperties& props, Canvas& canvas) { SkPaint paint; - paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); paint.setAntiAlias(true); paint.setTextSize(50); diff --git a/libs/hwui/tests/common/scenes/SaveLayer2Animation.cpp b/libs/hwui/tests/common/scenes/SaveLayer2Animation.cpp index fee0659fa81d..3befce4a395f 100644 --- a/libs/hwui/tests/common/scenes/SaveLayer2Animation.cpp +++ b/libs/hwui/tests/common/scenes/SaveLayer2Animation.cpp @@ -42,10 +42,8 @@ public: mBluePaint.setColor(SkColorSetARGB(255, 0, 0, 255)); mBluePaint.setTextSize(padding); - mBluePaint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); mGreenPaint.setColor(SkColorSetARGB(255, 0, 255, 0)); mGreenPaint.setTextSize(padding); - mGreenPaint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); // interleave drawText and drawRect with saveLayer ops for (int i = 0; i < regions; i++, top += smallRectHeight) { @@ -54,18 +52,15 @@ public: canvas.drawColor(SkColorSetARGB(255, 255, 255, 0), SkBlendMode::kSrcOver); std::string stri = std::to_string(i); std::string offscreen = "offscreen line " + stri; - std::unique_ptr<uint16_t[]> offtext = TestUtils::asciiToUtf16(offscreen.c_str()); - canvas.drawText(offtext.get(), 0, offscreen.length(), offscreen.length(), bounds.fLeft, - top + padding, minikin::Bidi::FORCE_LTR, mBluePaint, nullptr); + TestUtils::drawUtf8ToCanvas(&canvas, offscreen.c_str(), mBluePaint, bounds.fLeft, + top + padding); canvas.restore(); canvas.drawRect(bounds.fLeft, top + padding, bounds.fRight, top + smallRectHeight - padding, mBluePaint); std::string onscreen = "onscreen line " + stri; - std::unique_ptr<uint16_t[]> ontext = TestUtils::asciiToUtf16(onscreen.c_str()); - canvas.drawText(ontext.get(), 0, onscreen.length(), onscreen.length(), bounds.fLeft, - top + smallRectHeight - padding, minikin::Bidi::FORCE_LTR, mGreenPaint, - nullptr); + TestUtils::drawUtf8ToCanvas(&canvas, onscreen.c_str(), mGreenPaint, bounds.fLeft, + top + smallRectHeight - padding); } } void doFrame(int frameNr) override {} diff --git a/libs/hwui/tests/common/scenes/TextAnimation.cpp b/libs/hwui/tests/common/scenes/TextAnimation.cpp index a502116c30a0..a16b17849fc6 100644 --- a/libs/hwui/tests/common/scenes/TextAnimation.cpp +++ b/libs/hwui/tests/common/scenes/TextAnimation.cpp @@ -29,7 +29,6 @@ public: card = TestUtils::createNode(0, 0, width, height, [](RenderProperties& props, Canvas& canvas) { SkPaint paint; - paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); paint.setAntiAlias(true); paint.setTextSize(50); diff --git a/libs/hwui/tests/common/scenes/TvApp.cpp b/libs/hwui/tests/common/scenes/TvApp.cpp index c845e6c89747..003d8e92fd9f 100644 --- a/libs/hwui/tests/common/scenes/TvApp.cpp +++ b/libs/hwui/tests/common/scenes/TvApp.cpp @@ -117,7 +117,6 @@ private: canvas.drawColor(0xFFFFEEEE, SkBlendMode::kSrcOver); SkPaint paint; - paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); paint.setAntiAlias(true); paint.setTextSize(24); diff --git a/libs/hwui/tests/unit/FrameBuilderTests.cpp b/libs/hwui/tests/unit/FrameBuilderTests.cpp index e56d2f864f77..4eb77514f4ae 100644 --- a/libs/hwui/tests/unit/FrameBuilderTests.cpp +++ b/libs/hwui/tests/unit/FrameBuilderTests.cpp @@ -537,7 +537,6 @@ RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, regionClipStopsMerge) { canvas.save(SaveFlags::MatrixClip); canvas.clipPath(&path, SkClipOp::kIntersect); SkPaint paint; - paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); paint.setAntiAlias(true); paint.setTextSize(50); TestUtils::drawUtf8ToCanvas(&canvas, "Test string1", paint, 100, 100); @@ -569,7 +568,6 @@ RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, textMerging) { auto node = TestUtils::createNode<RecordingCanvas>(0, 0, 400, 400, [](RenderProperties& props, RecordingCanvas& canvas) { SkPaint paint; - paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); paint.setAntiAlias(true); paint.setTextSize(50); TestUtils::drawUtf8ToCanvas(&canvas, "Test string1", paint, 100, 0); // will be top clipped @@ -603,7 +601,6 @@ RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, textStrikethrough) { textPaint.setAntiAlias(true); textPaint.setTextSize(20); textPaint.setFlags(textPaint.getFlags() | SkPaint::kStrikeThruText_ReserveFlag); - textPaint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); for (int i = 0; i < LOOPS; i++) { TestUtils::drawUtf8ToCanvas(&canvas, "test text", textPaint, 10, 100 * (i + 1)); } @@ -654,7 +651,6 @@ RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, textStyle) { auto node = TestUtils::createNode<RecordingCanvas>( 0, 0, 400, 400, [](RenderProperties& props, RecordingCanvas& canvas) { SkPaint paint; - paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); paint.setAntiAlias(true); paint.setTextSize(50); paint.setStrokeWidth(10); diff --git a/libs/hwui/tests/unit/RecordingCanvasTests.cpp b/libs/hwui/tests/unit/RecordingCanvasTests.cpp index 5aae15f478b8..8a9e34f81c6d 100644 --- a/libs/hwui/tests/unit/RecordingCanvasTests.cpp +++ b/libs/hwui/tests/unit/RecordingCanvasTests.cpp @@ -175,7 +175,6 @@ OPENGL_PIPELINE_TEST(RecordingCanvas, drawGlyphs) { SkPaint paint; paint.setAntiAlias(true); paint.setTextSize(20); - paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); TestUtils::drawUtf8ToCanvas(&canvas, "test text", paint, 25, 25); }); @@ -196,7 +195,6 @@ OPENGL_PIPELINE_TEST(RecordingCanvas, drawGlyphs_strikeThruAndUnderline) { SkPaint paint; paint.setAntiAlias(true); paint.setTextSize(20); - paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); for (int i = 0; i < 2; i++) { for (int j = 0; j < 2; j++) { uint32_t flags = paint.getFlags(); @@ -238,7 +236,6 @@ OPENGL_PIPELINE_TEST(RecordingCanvas, drawGlyphs_forceAlignLeft) { SkPaint paint; paint.setAntiAlias(true); paint.setTextSize(20); - paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); paint.setTextAlign(SkPaint::kLeft_Align); TestUtils::drawUtf8ToCanvas(&canvas, "test text", paint, 25, 25); paint.setTextAlign(SkPaint::kCenter_Align); @@ -805,9 +802,7 @@ OPENGL_PIPELINE_TEST(RecordingCanvas, drawText) { Paint paint; paint.setAntiAlias(true); paint.setTextSize(20); - paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); - std::unique_ptr<uint16_t[]> dst = TestUtils::asciiToUtf16("HELLO"); - canvas.drawText(dst.get(), 0, 5, 5, 25, 25, minikin::Bidi::FORCE_LTR, paint, NULL); + TestUtils::drawUtf8ToCanvas(&canvas, "HELLO", paint, 25, 25); }); int count = 0; @@ -829,9 +824,7 @@ OPENGL_PIPELINE_TEST(RecordingCanvas, drawTextInHighContrast) { paint.setColor(SK_ColorWHITE); paint.setAntiAlias(true); paint.setTextSize(20); - paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); - std::unique_ptr<uint16_t[]> dst = TestUtils::asciiToUtf16("HELLO"); - canvas.drawText(dst.get(), 0, 5, 5, 25, 25, minikin::Bidi::FORCE_LTR, paint, NULL); + TestUtils::drawUtf8ToCanvas(&canvas, "HELLO", paint, 25, 25); }); Properties::enableHighContrastText = false; diff --git a/libs/hwui/tests/unit/SkiaCanvasTests.cpp b/libs/hwui/tests/unit/SkiaCanvasTests.cpp index 4138f595b091..1d7dc3d06ee4 100644 --- a/libs/hwui/tests/unit/SkiaCanvasTests.cpp +++ b/libs/hwui/tests/unit/SkiaCanvasTests.cpp @@ -36,7 +36,6 @@ OPENGL_PIPELINE_TEST(SkiaCanvasProxy, drawGlyphsViaPicture) { SkPaint paint; paint.setAntiAlias(true); paint.setTextSize(20); - paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); static const char* text = "testing text bounds"; // draw text directly into Recording canvas diff --git a/libs/hwui/tests/unit/TextDropShadowCacheTests.cpp b/libs/hwui/tests/unit/TextDropShadowCacheTests.cpp index 78d75d65837c..92d05e44c6ca 100644 --- a/libs/hwui/tests/unit/TextDropShadowCacheTests.cpp +++ b/libs/hwui/tests/unit/TextDropShadowCacheTests.cpp @@ -29,6 +29,7 @@ using namespace android::uirenderer; RENDERTHREAD_OPENGL_PIPELINE_TEST(TextDropShadowCache, addRemove) { SkPaint paint; paint.setTextSize(20); + paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); GammaFontRenderer gammaFontRenderer; FontRenderer& fontRenderer = gammaFontRenderer.getFontRenderer(); diff --git a/media/java/android/media/AudioFocusInfo.java b/media/java/android/media/AudioFocusInfo.java index 6d9c5e2ad5fc..5d0c8e234d40 100644 --- a/media/java/android/media/AudioFocusInfo.java +++ b/media/java/android/media/AudioFocusInfo.java @@ -130,13 +130,11 @@ public final class AudioFocusInfo implements Parcelable { dest.writeInt(mSdkTarget); } - @SystemApi @Override public int hashCode() { return Objects.hash(mAttributes, mClientUid, mClientId, mPackageName, mGainRequest, mFlags); } - @SystemApi @Override public boolean equals(Object obj) { if (this == obj) diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index f87c8461c7c6..913b5e841112 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -1635,6 +1635,21 @@ public class AudioManager { } /** + * Broadcast Action: microphone muting state changed. + * + * You <em>cannot</em> receive this through components declared + * in manifests, only by explicitly registering for it with + * {@link Context#registerReceiver(BroadcastReceiver, IntentFilter) + * Context.registerReceiver()}. + * + * <p>The intent has no extra values, use {@link #isMicrophoneMute} to check whether the + * microphone is muted. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_MICROPHONE_MUTE_CHANGED = + "android.media.action.MICROPHONE_MUTE_CHANGED"; + + /** * Sets the audio mode. * <p> * The audio mode encompasses audio routing AND the behavior of diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java index f4ec93666490..bef2bcbd80f1 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java @@ -1710,18 +1710,9 @@ public class SettingsProvider extends ContentProvider { } private List<String> getSettingsNamesLocked(int settingsType, int userId) { - boolean instantApp; - if (UserHandle.getAppId(Binder.getCallingUid()) < Process.FIRST_APPLICATION_UID) { - instantApp = false; - } else { - ApplicationInfo ai = getCallingApplicationInfoOrThrow(); - instantApp = ai.isInstantApp(); - } - if (instantApp) { - return new ArrayList<String>(getInstantAppAccessibleSettings(settingsType)); - } else { - return mSettingsRegistry.getSettingsNamesLocked(settingsType, userId); - } + // Don't enforce the instant app whitelist for now -- its too prone to unintended breakage + // in the current form. + return mSettingsRegistry.getSettingsNamesLocked(settingsType, userId); } private void enforceSettingReadable(String settingName, int settingsType, int userId) { @@ -1734,8 +1725,10 @@ public class SettingsProvider extends ContentProvider { } if (!getInstantAppAccessibleSettings(settingsType).contains(settingName) && !getOverlayInstantAppAccessibleSettings(settingsType).contains(settingName)) { - throw new SecurityException("Setting " + settingName + " is not accessible from" - + " ephemeral package " + getCallingPackage()); + // Don't enforce the instant app whitelist for now -- its too prone to unintended + // breakage in the current form. + Slog.w(LOG_TAG, "Instant App " + ai.packageName + + " trying to access unexposed setting, this will be an error in the future."); } } diff --git a/packages/SystemUI/Android.mk b/packages/SystemUI/Android.mk index 2c5eb27abe3d..73fcdd7aa90d 100644 --- a/packages/SystemUI/Android.mk +++ b/packages/SystemUI/Android.mk @@ -39,7 +39,12 @@ LOCAL_STATIC_ANDROID_LIBRARIES := \ android-support-v7-mediarouter \ android-support-v7-palette \ android-support-v14-preference \ - android-support-v17-leanback + android-support-v17-leanback \ + android-slices-core \ + android-slices-view \ + android-slices-builders \ + apptoolkit-arch-core-runtime \ + apptoolkit-lifecycle-extensions \ LOCAL_STATIC_JAVA_LIBRARIES := \ SystemUI-tags \ diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_status_area.xml b/packages/SystemUI/res-keyguard/layout/keyguard_status_area.xml index 020cfeeaa194..b154d46f162c 100644 --- a/packages/SystemUI/res-keyguard/layout/keyguard_status_area.xml +++ b/packages/SystemUI/res-keyguard/layout/keyguard_status_area.xml @@ -24,31 +24,20 @@ android:layout_marginEnd="16dp" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginTop="@dimen/date_owner_info_margin" android:layout_gravity="center_horizontal" - android:paddingTop="4dp" android:clipToPadding="false" android:orientation="vertical" android:layout_centerHorizontal="true"> <TextView android:id="@+id/title" android:layout_width="match_parent" android:layout_height="wrap_content" - android:singleLine="true" - android:ellipsize="end" - android:fadingEdge="horizontal" - android:gravity="center" - android:textSize="22sp" - android:textColor="?attr/wallpaperTextColor" + android:layout_marginBottom="@dimen/widget_vertical_padding" + android:theme="@style/TextAppearance.Keyguard" /> - <TextView android:id="@+id/text" + <LinearLayout android:id="@+id/row" android:layout_width="match_parent" android:layout_height="wrap_content" - android:singleLine="true" + android:orientation="horizontal" android:gravity="center" - android:visibility="gone" - android:textSize="16sp" - android:textColor="?attr/wallpaperTextColor" - android:layout_marginTop="4dp" - android:ellipsize="end" /> </com.android.keyguard.KeyguardSliceView>
\ No newline at end of file diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_status_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_status_view.xml index 138733e646ce..c97cfc4bb835 100644 --- a/packages/SystemUI/res-keyguard/layout/keyguard_status_view.xml +++ b/packages/SystemUI/res-keyguard/layout/keyguard_status_view.xml @@ -34,6 +34,7 @@ android:orientation="vertical"> <RelativeLayout android:id="@+id/keyguard_clock_container" + android:animateLayoutChanges="true" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="center_horizontal|top"> @@ -59,14 +60,23 @@ android:layout_toEndOf="@id/clock_view" android:visibility="invisible" android:src="@drawable/ic_aod_charging_24dp" - android:contentDescription="@string/accessibility_ambient_display_charging" - /> + android:contentDescription="@string/accessibility_ambient_display_charging" /> + <View + android:id="@+id/clock_separator" + android:layout_width="16dp" + android:layout_height="1dp" + android:layout_marginTop="10dp" + android:layout_below="@id/clock_view" + android:background="#f00" + android:layout_centerHorizontal="true" /> <include layout="@layout/keyguard_status_area" android:id="@+id/keyguard_status_area" + android:layout_marginTop="10dp" + android:layout_marginBottom="@dimen/widget_vertical_padding" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_below="@id/clock_view" /> + android:layout_below="@id/clock_separator" /> </RelativeLayout> <TextView @@ -83,6 +93,5 @@ android:letterSpacing="0.05" android:ellipsize="marquee" android:singleLine="true" /> - </LinearLayout> </com.android.keyguard.KeyguardStatusView> diff --git a/packages/SystemUI/res-keyguard/values-h560dp/dimens.xml b/packages/SystemUI/res-keyguard/values-h560dp/dimens.xml index 1b6fa4cf4892..3fb86d03a167 100644 --- a/packages/SystemUI/res-keyguard/values-h560dp/dimens.xml +++ b/packages/SystemUI/res-keyguard/values-h560dp/dimens.xml @@ -16,5 +16,5 @@ --> <resources> - <dimen name="widget_big_font_size">72dp</dimen> + <dimen name="widget_big_font_size">64dp</dimen> </resources>
\ No newline at end of file diff --git a/packages/SystemUI/res-keyguard/values-h650dp/dimens.xml b/packages/SystemUI/res-keyguard/values-h650dp/dimens.xml index 1b6fa4cf4892..3fb86d03a167 100644 --- a/packages/SystemUI/res-keyguard/values-h650dp/dimens.xml +++ b/packages/SystemUI/res-keyguard/values-h650dp/dimens.xml @@ -16,5 +16,5 @@ --> <resources> - <dimen name="widget_big_font_size">72dp</dimen> + <dimen name="widget_big_font_size">64dp</dimen> </resources>
\ No newline at end of file diff --git a/packages/SystemUI/res-keyguard/values/dimens.xml b/packages/SystemUI/res-keyguard/values/dimens.xml index bcac07295cce..463af61c16f1 100644 --- a/packages/SystemUI/res-keyguard/values/dimens.xml +++ b/packages/SystemUI/res-keyguard/values/dimens.xml @@ -42,9 +42,22 @@ <dimen name="eca_overlap">-10dip</dimen> <!-- Default clock parameters --> - <dimen name="bottom_text_spacing_digital">-1dp</dimen> - <dimen name="widget_label_font_size">14sp</dimen> - <dimen name="widget_big_font_size">72dp</dimen> + <dimen name="bottom_text_spacing_digital">-10dp</dimen> + <!-- Slice header --> + <dimen name="widget_title_font_size">28sp</dimen> + <!-- Slice subtitle --> + <dimen name="widget_label_font_size">16sp</dimen> + <!-- Clock without header --> + <dimen name="widget_big_font_size">64dp</dimen> + <!-- Clock with header --> + <dimen name="widget_small_font_size">22dp</dimen> + <!-- Dash between clock and header --> + <dimen name="widget_vertical_padding">16dp</dimen> + <!-- Subtitle paddings --> + <dimen name="widget_separator_thickness">2dp</dimen> + <dimen name="widget_horizontal_padding">8dp</dimen> + <dimen name="widget_icon_size">16dp</dimen> + <dimen name="widget_icon_padding">4dp</dimen> <!-- The y translation to apply at the start in appear animations. --> <dimen name="appear_y_translation_start">32dp</dimen> diff --git a/packages/SystemUI/res-keyguard/values/styles.xml b/packages/SystemUI/res-keyguard/values/styles.xml index 826e3ea1f7e5..d50bab533856 100644 --- a/packages/SystemUI/res-keyguard/values/styles.xml +++ b/packages/SystemUI/res-keyguard/values/styles.xml @@ -78,4 +78,18 @@ <item name="wallpaperTextColorSecondary">@*android:color/secondary_text_material_dark</item> </style> + <style name="TextAppearance.Keyguard" parent="Theme.SystemUI"> + <item name="android:textSize">@dimen/widget_title_font_size</item> + <item name="android:gravity">center</item> + <item name="android:ellipsize">end</item> + <item name="android:maxLines">2</item> + </style> + + <style name="TextAppearance.Keyguard.Secondary"> + <item name="android:layout_width">wrap_content</item> + <item name="android:layout_height">wrap_content</item> + <item name="android:textSize">@dimen/widget_label_font_size</item> + <item name="android:singleLine">true</item> + </style> + </resources> diff --git a/packages/SystemUI/res/layout/pip_menu_activity.xml b/packages/SystemUI/res/layout/pip_menu_activity.xml index 8b7f6926c9f7..03d587bd447c 100644 --- a/packages/SystemUI/res/layout/pip_menu_activity.xml +++ b/packages/SystemUI/res/layout/pip_menu_activity.xml @@ -60,14 +60,24 @@ </FrameLayout> </FrameLayout> - <ImageView - android:id="@+id/dismiss" - android:layout_width="@dimen/pip_action_size" - android:layout_height="@dimen/pip_action_size" - android:layout_gravity="top|end" - android:padding="@dimen/pip_action_padding" - android:contentDescription="@string/pip_phone_close" - android:src="@drawable/ic_close_white" - android:background="?android:selectableItemBackgroundBorderless" /> + <ImageView + android:id="@+id/settings" + android:layout_width="@dimen/pip_action_size" + android:layout_height="@dimen/pip_action_size" + android:layout_gravity="top|start" + android:padding="@dimen/pip_action_padding" + android:contentDescription="@string/pip_phone_settings" + android:src="@drawable/ic_settings" + android:background="?android:selectableItemBackgroundBorderless" /> + + <ImageView + android:id="@+id/dismiss" + android:layout_width="@dimen/pip_action_size" + android:layout_height="@dimen/pip_action_size" + android:layout_gravity="top|end" + android:padding="@dimen/pip_action_padding" + android:contentDescription="@string/pip_phone_close" + android:src="@drawable/ic_close_white" + android:background="?android:selectableItemBackgroundBorderless" /> </FrameLayout> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index fd205dd55662..98537a102e1a 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -1895,6 +1895,9 @@ <!-- Label for PIP close button [CHAR LIMIT=NONE]--> <string name="pip_phone_close">Close</string> + <!-- Label for PIP settings button [CHAR LIMIT=NONE]--> + <string name="pip_phone_settings">Settings</string> + <!-- Label for PIP the drag to dismiss hint [CHAR LIMIT=NONE]--> <string name="pip_phone_dismiss_hint">Drag down to dismiss</string> diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java index cb3d59c4dff6..b9bf80de304b 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java @@ -17,38 +17,56 @@ package com.android.keyguard; import android.app.PendingIntent; -import android.app.slice.Slice; -import android.app.slice.SliceItem; -import android.app.slice.SliceQuery; +import android.arch.lifecycle.LiveData; +import android.arch.lifecycle.Observer; import android.content.Context; -import android.database.ContentObserver; +import android.graphics.Canvas; import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.drawable.Drawable; import android.net.Uri; -import android.os.Handler; +import android.provider.Settings; import android.util.AttributeSet; +import android.util.Log; +import android.view.View; +import android.widget.Button; import android.widget.LinearLayout; import android.widget.TextView; import com.android.internal.graphics.ColorUtils; +import com.android.settingslib.Utils; +import com.android.systemui.Dependency; import com.android.systemui.R; import com.android.systemui.keyguard.KeyguardSliceProvider; +import com.android.systemui.tuner.TunerService; -import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.function.Consumer; + +import androidx.app.slice.Slice; +import androidx.app.slice.SliceItem; +import androidx.app.slice.core.SliceQuery; +import androidx.app.slice.widget.SliceLiveData; /** * View visible under the clock on the lock screen and AoD. */ -public class KeyguardSliceView extends LinearLayout { +public class KeyguardSliceView extends LinearLayout implements View.OnClickListener, + Observer<Slice>, TunerService.Tunable { - private final Uri mKeyguardSliceUri; + private static final String TAG = "KeyguardSliceView"; + private final HashMap<View, PendingIntent> mClickActions; + private Uri mKeyguardSliceUri; private TextView mTitle; - private TextView mText; - private Slice mSlice; - private PendingIntent mSliceAction; + private LinearLayout mRow; private int mTextColor; private float mDarkAmount = 0; - private final ContentObserver mObserver; + private LiveData<Slice> mLiveData; + private int mIconSize; + private Consumer<Boolean> mListener; + private boolean mHasHeader; public KeyguardSliceView(Context context) { this(context, null, 0); @@ -60,16 +78,20 @@ public class KeyguardSliceView extends LinearLayout { public KeyguardSliceView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); - mObserver = new KeyguardSliceObserver(new Handler()); - mKeyguardSliceUri = Uri.parse(KeyguardSliceProvider.KEYGUARD_SLICE_URI);; + + TunerService tunerService = Dependency.get(TunerService.class); + tunerService.addTunable(this, Settings.Secure.KEYGUARD_SLICE_URI); + + mClickActions = new HashMap<>(); } @Override protected void onFinishInflate() { super.onFinishInflate(); mTitle = findViewById(R.id.title); - mText = findViewById(R.id.text); - mTextColor = mTitle.getCurrentTextColor(); + mRow = findViewById(R.id.row); + mTextColor = Utils.getColorAttr(mContext, R.attr.wallpaperTextColor); + mIconSize = (int) mContext.getResources().getDimension(R.dimen.widget_icon_size); } @Override @@ -77,57 +99,103 @@ public class KeyguardSliceView extends LinearLayout { super.onAttachedToWindow(); // Set initial content - showSlice(Slice.bindSlice(getContext().getContentResolver(), mKeyguardSliceUri, - Collections.emptyList())); + showSlice(Slice.bindSlice(getContext(), mKeyguardSliceUri)); // Make sure we always have the most current slice - getContext().getContentResolver().registerContentObserver(mKeyguardSliceUri, - false /* notifyDescendants */, mObserver); + mLiveData.observeForever(this); } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); - getContext().getContentResolver().unregisterContentObserver(mObserver); + mLiveData.removeObserver(this); } private void showSlice(Slice slice) { - // Items will be wrapped into an action when they have tap targets. - SliceItem actionSlice = SliceQuery.find(slice, SliceItem.FORMAT_ACTION); - if (actionSlice != null) { - mSlice = actionSlice.getSlice(); - mSliceAction = actionSlice.getAction(); - } else { - mSlice = slice; - mSliceAction = null; - } - if (mSlice == null) { - setVisibility(GONE); - return; - } + // Main area + SliceItem mainItem = SliceQuery.find(slice, android.app.slice.SliceItem.FORMAT_SLICE, + null /* hints */, new String[]{android.app.slice.Slice.HINT_LIST_ITEM}); + mHasHeader = mainItem != null; - SliceItem title = SliceQuery.find(mSlice, SliceItem.FORMAT_TEXT, Slice.HINT_TITLE, null); - if (title == null) { + List<SliceItem> subItems = SliceQuery.findAll(slice, + android.app.slice.SliceItem.FORMAT_SLICE, + new String[]{android.app.slice.Slice.HINT_LIST_ITEM}, + null /* nonHints */); + + if (!mHasHeader) { mTitle.setVisibility(GONE); } else { mTitle.setVisibility(VISIBLE); - mTitle.setText(title.getText()); + SliceItem mainTitle = SliceQuery.find(mainItem.getSlice(), + android.app.slice.SliceItem.FORMAT_TEXT, + new String[]{android.app.slice.Slice.HINT_TITLE}, + null /* nonHints */); + mTitle.setText(mainTitle.getText()); } - SliceItem text = SliceQuery.find(mSlice, SliceItem.FORMAT_TEXT, null, Slice.HINT_TITLE); - if (text == null) { - mText.setVisibility(GONE); - } else { - mText.setVisibility(VISIBLE); - mText.setText(text.getText()); + mClickActions.clear(); + final int subItemsCount = subItems.size(); + + for (int i = 0; i < subItemsCount; i++) { + SliceItem item = subItems.get(i); + final Uri itemTag = item.getSlice().getUri(); + // Try to reuse the view if already exists in the layout + KeyguardSliceButton button = mRow.findViewWithTag(itemTag); + if (button == null) { + button = new KeyguardSliceButton(mContext); + button.setTextColor(mTextColor); + button.setTag(itemTag); + } else { + mRow.removeView(button); + } + button.setHasDivider(i < subItemsCount - 1); + mRow.addView(button, i); + + PendingIntent pendingIntent; + try { + pendingIntent = item.getAction(); + } catch (RuntimeException e) { + Log.w(TAG, "Cannot retrieve action from keyguard slice", e); + pendingIntent = null; + } + mClickActions.put(button, pendingIntent); + + SliceItem title = SliceQuery.find(item.getSlice(), + android.app.slice.SliceItem.FORMAT_TEXT, + new String[]{android.app.slice.Slice.HINT_TITLE}, + null /* nonHints */); + button.setText(title.getText()); + + Drawable iconDrawable = null; + SliceItem icon = SliceQuery.find(item.getSlice(), + android.app.slice.SliceItem.FORMAT_IMAGE); + if (icon != null) { + iconDrawable = icon.getIcon().loadDrawable(mContext); + final int width = (int) (iconDrawable.getIntrinsicWidth() + / (float) iconDrawable.getIntrinsicHeight() * mIconSize); + iconDrawable.setBounds(0, 0, Math.max(width, 1), mIconSize); + } + button.setCompoundDrawablesRelative(iconDrawable, null, null, null); + button.setOnClickListener(this); + } + + // Removing old views + for (int i = 0; i < mRow.getChildCount(); i++) { + View child = mRow.getChildAt(i); + if (!mClickActions.containsKey(child)) { + mRow.removeView(child); + i--; + } } - final int visibility = title == null && text == null ? GONE : VISIBLE; + final int visibility = mHasHeader || subItemsCount > 0 ? VISIBLE : GONE; if (visibility != getVisibility()) { setVisibility(visibility); } + + mListener.accept(mHasHeader); } public void setDark(float darkAmount) { @@ -135,30 +203,113 @@ public class KeyguardSliceView extends LinearLayout { updateTextColors(); } - public void setTextColor(int textColor) { - mTextColor = textColor; - } - private void updateTextColors() { final int blendedColor = ColorUtils.blendARGB(mTextColor, Color.WHITE, mDarkAmount); mTitle.setTextColor(blendedColor); - mText.setTextColor(blendedColor); + int childCount = mRow.getChildCount(); + for (int i = 0; i < childCount; i++) { + View v = mRow.getChildAt(i); + if (v instanceof Button) { + ((Button) v).setTextColor(blendedColor); + } + } + } + + @Override + public void onClick(View v) { + final PendingIntent action = mClickActions.get(v); + if (action != null) { + try { + action.send(); + } catch (PendingIntent.CanceledException e) { + Log.i(TAG, "Pending intent cancelled, nothing to launch", e); + } + } } - private class KeyguardSliceObserver extends ContentObserver { - KeyguardSliceObserver(Handler handler) { - super(handler); + public void setListener(Consumer<Boolean> listener) { + mListener = listener; + } + + public boolean hasHeader() { + return mHasHeader; + } + + /** + * LiveData observer lifecycle. + * @param slice the new slice content. + */ + @Override + public void onChanged(Slice slice) { + showSlice(slice); + } + + @Override + public void onTuningChanged(String key, String newValue) { + setupUri(newValue); + } + + public void setupUri(String uriString) { + if (uriString == null) { + uriString = KeyguardSliceProvider.KEYGUARD_SLICE_URI; + } + + boolean wasObserving = false; + if (mLiveData != null && mLiveData.hasActiveObservers()) { + wasObserving = true; + mLiveData.removeObserver(this); + } + + mKeyguardSliceUri = Uri.parse(uriString); + mLiveData = SliceLiveData.fromUri(mContext, mKeyguardSliceUri); + + if (wasObserving) { + mLiveData.observeForever(this); + showSlice(Slice.bindSlice(getContext(), mKeyguardSliceUri)); + } + } + + /** + * Representation of an item that appears under the clock on main keyguard message. + * Shows optional separator. + */ + private class KeyguardSliceButton extends Button { + + private final Paint mPaint; + private boolean mHasDivider; + + public KeyguardSliceButton(Context context) { + super(context, null /* attrs */, + com.android.keyguard.R.style.TextAppearance_Keyguard_Secondary); + mPaint = new Paint(); + mPaint.setStyle(Paint.Style.STROKE); + float dividerWidth = context.getResources() + .getDimension(R.dimen.widget_separator_thickness); + mPaint.setStrokeWidth(dividerWidth); + int horizontalPadding = (int) context.getResources() + .getDimension(R.dimen.widget_horizontal_padding); + setPadding(horizontalPadding, 0, horizontalPadding, 0); + setCompoundDrawablePadding((int) context.getResources() + .getDimension(R.dimen.widget_icon_padding)); + } + + public void setHasDivider(boolean hasDivider) { + mHasDivider = hasDivider; } @Override - public void onChange(boolean selfChange) { - this.onChange(selfChange, null); + public void setTextColor(int color) { + super.setTextColor(color); + mPaint.setColor(color); } @Override - public void onChange(boolean selfChange, Uri uri) { - showSlice(Slice.bindSlice(getContext().getContentResolver(), mKeyguardSliceUri, - Collections.emptyList())); + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + if (mHasDivider) { + final int lineX = getLayoutDirection() == LAYOUT_DIRECTION_RTL ? 0 : getWidth(); + canvas.drawLine(lineX, 0, lineX, getHeight(), mPaint); + } } } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java index 78cf2b9bf1ed..4b9a8744900d 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java @@ -28,6 +28,7 @@ import android.os.UserHandle; import android.support.v4.graphics.ColorUtils; import android.text.TextUtils; import android.text.format.DateFormat; +import android.util.ArraySet; import android.util.AttributeSet; import android.util.Log; import android.util.Slog; @@ -38,11 +39,11 @@ import android.widget.GridLayout; import android.widget.TextClock; import android.widget.TextView; -import com.android.internal.util.ArrayUtils; import com.android.internal.widget.LockPatternUtils; -import com.android.settingslib.Utils; import com.android.systemui.ChargingView; +import com.google.android.collect.Sets; + import java.util.Locale; public class KeyguardStatusView extends GridLayout { @@ -52,8 +53,11 @@ public class KeyguardStatusView extends GridLayout { private final LockPatternUtils mLockPatternUtils; private final AlarmManager mAlarmManager; + private final float mSmallClockScale; + private final float mWidgetPadding; private TextClock mClockView; + private View mClockSeparator; private TextView mOwnerInfo; private ViewGroup mClockContainer; private ChargingView mBatteryDoze; @@ -61,7 +65,7 @@ public class KeyguardStatusView extends GridLayout { private Runnable mPendingMarqueeStart; private Handler mHandler; - private View[] mVisibleInDoze; + private ArraySet<View> mVisibleInDoze; private boolean mPulsing; private float mDarkAmount = 0; private int mTextColor; @@ -112,6 +116,9 @@ public class KeyguardStatusView extends GridLayout { mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); mLockPatternUtils = new LockPatternUtils(getContext()); mHandler = new Handler(Looper.myLooper()); + mSmallClockScale = getResources().getDimension(R.dimen.widget_small_font_size) + / getResources().getDimension(R.dimen.widget_big_font_size); + mWidgetPadding = getResources().getDimension(R.dimen.widget_vertical_padding); } private void setEnableMarquee(boolean enabled) { @@ -150,9 +157,14 @@ public class KeyguardStatusView extends GridLayout { mOwnerInfo = findViewById(R.id.owner_info); mBatteryDoze = findViewById(R.id.battery_doze); mKeyguardSlice = findViewById(R.id.keyguard_status_area); - mVisibleInDoze = new View[]{mBatteryDoze, mClockView, mKeyguardSlice}; + mClockSeparator = findViewById(R.id.clock_separator); + mVisibleInDoze = Sets.newArraySet(mBatteryDoze, mClockView, mKeyguardSlice, + mClockSeparator); mTextColor = mClockView.getCurrentTextColor(); + mKeyguardSlice.setListener(this::onSliceContentChanged); + onSliceContentChanged(mKeyguardSlice.hasHeader()); + boolean shouldMarquee = KeyguardUpdateMonitor.getInstance(mContext).isDeviceInteractive(); setEnableMarquee(shouldMarquee); refresh(); @@ -163,6 +175,22 @@ public class KeyguardStatusView extends GridLayout { mClockView.setElegantTextHeight(false); } + private void onSliceContentChanged(boolean hasHeader) { + final float clockScale = hasHeader ? mSmallClockScale : 1; + float translation = (mClockView.getHeight() - (mClockView.getHeight() * clockScale)) / 2f; + if (hasHeader) { + translation -= mWidgetPadding; + } + mClockView.setTranslationY(translation); + mClockView.setScaleX(clockScale); + mClockView.setScaleY(clockScale); + final float batteryTranslation = + -(mClockView.getWidth() - (mClockView.getWidth() * clockScale)) / 2; + mBatteryDoze.setTranslationX(batteryTranslation); + mBatteryDoze.setTranslationY(translation); + mClockSeparator.setVisibility(hasHeader ? VISIBLE : GONE); + } + @Override protected void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); @@ -201,17 +229,6 @@ public class KeyguardStatusView extends GridLayout { return mClockView.getTextSize(); } - public static String formatNextAlarm(Context context, AlarmManager.AlarmClockInfo info) { - if (info == null) { - return ""; - } - String skeleton = DateFormat.is24HourFormat(context, ActivityManager.getCurrentUser()) - ? "EHm" - : "Ehma"; - String pattern = DateFormat.getBestDateTimePattern(Locale.getDefault(), skeleton); - return DateFormat.format(pattern, info.getTriggerTime()).toString(); - } - private void updateOwnerInfo() { if (mOwnerInfo == null) return; String ownerInfo = getOwnerInfo(); @@ -303,7 +320,7 @@ public class KeyguardStatusView extends GridLayout { final int N = mClockContainer.getChildCount(); for (int i = 0; i < N; i++) { View child = mClockContainer.getChildAt(i); - if (ArrayUtils.contains(mVisibleInDoze, child)) { + if (mVisibleInDoze.contains(child)) { continue; } child.setAlpha(dark ? 0 : 1); @@ -312,10 +329,12 @@ public class KeyguardStatusView extends GridLayout { mOwnerInfo.setAlpha(dark ? 0 : 1); } + final int blendedTextColor = ColorUtils.blendARGB(mTextColor, Color.WHITE, darkAmount); updateDozeVisibleViews(); mBatteryDoze.setDark(dark); mKeyguardSlice.setDark(darkAmount); - mClockView.setTextColor(ColorUtils.blendARGB(mTextColor, Color.WHITE, darkAmount)); + mClockView.setTextColor(blendedTextColor); + mClockSeparator.setBackgroundColor(blendedTextColor); } public void setPulsing(boolean pulsing) { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java index 6ddc76b595b2..bd46c5f8ad0b 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java @@ -16,38 +16,55 @@ package com.android.systemui.keyguard; +import android.app.ActivityManager; +import android.app.AlarmManager; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.graphics.drawable.Icon; import android.icu.text.DateFormat; import android.icu.text.DisplayContext; import android.net.Uri; import android.os.Handler; -import android.app.slice.Slice; -import android.app.slice.SliceProvider; +import android.text.TextUtils; import com.android.internal.annotations.VisibleForTesting; import com.android.systemui.R; +import com.android.systemui.statusbar.policy.NextAlarmController; +import com.android.systemui.statusbar.policy.NextAlarmControllerImpl; import java.util.Date; import java.util.Locale; +import androidx.app.slice.Slice; +import androidx.app.slice.SliceProvider; +import androidx.app.slice.builders.ListBuilder; +import androidx.app.slice.builders.ListBuilder.RowBuilder; + /** * Simple Slice provider that shows the current date. */ -public class KeyguardSliceProvider extends SliceProvider { +public class KeyguardSliceProvider extends SliceProvider implements + NextAlarmController.NextAlarmChangeCallback { public static final String KEYGUARD_SLICE_URI = "content://com.android.systemui.keyguard/main"; + public static final String KEYGUARD_DATE_URI = "content://com.android.systemui.keyguard/date"; + public static final String KEYGUARD_NEXT_ALARM_URI = + "content://com.android.systemui.keyguard/alarm"; private final Date mCurrentTime = new Date(); protected final Uri mSliceUri; + protected final Uri mDateUri; + protected final Uri mAlarmUri; private final Handler mHandler; private String mDatePattern; private DateFormat mDateFormat; private String mLastText; private boolean mRegistered; private boolean mRegisteredEveryMinute; + private String mNextAlarm; + private NextAlarmController mNextAlarmController; /** * Receiver responsible for time ticking and updating the date format. @@ -80,23 +97,49 @@ public class KeyguardSliceProvider extends SliceProvider { KeyguardSliceProvider(Handler handler) { mHandler = handler; mSliceUri = Uri.parse(KEYGUARD_SLICE_URI); + mDateUri = Uri.parse(KEYGUARD_DATE_URI); + mAlarmUri = Uri.parse(KEYGUARD_NEXT_ALARM_URI); } @Override public Slice onBindSlice(Uri sliceUri) { - return new Slice.Builder(sliceUri).addText(mLastText, null, Slice.HINT_TITLE).build(); + ListBuilder builder = new ListBuilder(mSliceUri) + .addRow(new RowBuilder(mDateUri).setTitle(mLastText)); + if (!TextUtils.isEmpty(mNextAlarm)) { + Icon icon = Icon.createWithResource(getContext(), R.drawable.ic_access_alarms_big); + builder.addRow(new RowBuilder(mAlarmUri).setTitle(mNextAlarm).addEndItem(icon)); + } + + return builder.build(); } @Override - public boolean onCreate() { - + public boolean onCreateSliceProvider() { + mNextAlarmController = new NextAlarmControllerImpl(getContext()); + mNextAlarmController.addCallback(this); mDatePattern = getContext().getString(R.string.system_ui_date_pattern); - registerClockUpdate(false /* everyMinute */); updateClock(); return true; } + public static String formatNextAlarm(Context context, AlarmManager.AlarmClockInfo info) { + if (info == null) { + return ""; + } + String skeleton = android.text.format.DateFormat + .is24HourFormat(context, ActivityManager.getCurrentUser()) ? "EHm" : "Ehma"; + String pattern = android.text.format.DateFormat + .getBestDateTimePattern(Locale.getDefault(), skeleton); + return android.text.format.DateFormat.format(pattern, info.getTriggerTime()).toString(); + } + + /** + * Registers a broadcast receiver for clock updates, include date, time zone and manually + * changing the date/time via the settings app. + * + * @param everyMinute {@code true} if you also want updates every minute. + */ protected void registerClockUpdate(boolean everyMinute) { if (mRegistered) { if (mRegisteredEveryMinute == everyMinute) { @@ -156,4 +199,10 @@ public class KeyguardSliceProvider extends SliceProvider { void cleanDateFormat() { mDateFormat = null; } + + @Override + public void onNextAlarmChanged(AlarmManager.AlarmClockInfo nextAlarm) { + mNextAlarm = formatNextAlarm(getContext(), nextAlarm); + getContext().getContentResolver().notifyChange(mSliceUri, null /* observer */); + } } diff --git a/packages/SystemUI/src/com/android/systemui/media/NotificationPlayer.java b/packages/SystemUI/src/com/android/systemui/media/NotificationPlayer.java index b5c0d5386767..f5f06db348ab 100644 --- a/packages/SystemUI/src/com/android/systemui/media/NotificationPlayer.java +++ b/packages/SystemUI/src/com/android/systemui/media/NotificationPlayer.java @@ -133,12 +133,19 @@ public class NotificationPlayer implements OnCompletionListener, OnErrorListener + mNotificationRampTimeMs + "ms"); } try { Thread.sleep(mNotificationRampTimeMs); - player.start(); } catch (InterruptedException e) { Log.e(mTag, "Exception while sleeping to sync notification playback" + " with ducking", e); } - if (DEBUG) { Log.d(mTag, "player.start"); } + try { + player.start(); + if (DEBUG) { Log.d(mTag, "player.start"); } + } catch (Exception e) { + player.release(); + player = null; + // playing the notification didn't work, revert the focus request + abandonAudioFocusAfterError(); + } if (mPlayer != null) { if (DEBUG) { Log.d(mTag, "mPlayer.release"); } mPlayer.release(); @@ -147,6 +154,8 @@ public class NotificationPlayer implements OnCompletionListener, OnErrorListener } catch (Exception e) { Log.w(mTag, "error loading sound for " + mCmd.uri, e); + // playing the notification didn't work, revert the focus request + abandonAudioFocusAfterError(); } this.notify(); } @@ -154,6 +163,16 @@ public class NotificationPlayer implements OnCompletionListener, OnErrorListener } }; + private void abandonAudioFocusAfterError() { + synchronized (mQueueAudioFocusLock) { + if (mAudioManagerWithAudioFocus != null) { + if (DEBUG) Log.d(mTag, "abandoning focus after playback error"); + mAudioManagerWithAudioFocus.abandonAudioFocus(null); + mAudioManagerWithAudioFocus = null; + } + } + } + private void startSound(Command cmd) { // Preparing can be slow, so if there is something else // is playing, let it continue until we're done, so there diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipAppOpsListener.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipAppOpsListener.java new file mode 100644 index 000000000000..f0e4ccc139ca --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipAppOpsListener.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2017 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.systemui.pip.phone; + +import static android.app.AppOpsManager.MODE_ALLOWED; +import static android.app.AppOpsManager.OP_PICTURE_IN_PICTURE; + +import android.app.AppOpsManager; +import android.app.AppOpsManager.OnOpChangedListener; +import android.app.IActivityManager; +import android.content.ComponentName; +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager.NameNotFoundException; +import android.util.Pair; + +public class PipAppOpsListener { + private static final String TAG = PipAppOpsListener.class.getSimpleName(); + + private Context mContext; + private IActivityManager mActivityManager; + private AppOpsManager mAppOpsManager; + + private PipMotionHelper mMotionHelper; + + private AppOpsManager.OnOpChangedListener mAppOpsChangedListener = new OnOpChangedListener() { + @Override + public void onOpChanged(String op, String packageName) { + try { + // Dismiss the PiP once the user disables the app ops setting for that package + final Pair<ComponentName, Integer> topPipActivityInfo = + PipUtils.getTopPinnedActivity(mContext, mActivityManager); + if (topPipActivityInfo.first != null) { + final ApplicationInfo appInfo = mContext.getPackageManager() + .getApplicationInfoAsUser(packageName, 0, topPipActivityInfo.second); + if (appInfo.packageName.equals(topPipActivityInfo.first.getPackageName()) && + mAppOpsManager.checkOpNoThrow(OP_PICTURE_IN_PICTURE, appInfo.uid, + packageName) != MODE_ALLOWED) { + mMotionHelper.dismissPip(); + } + } + } catch (NameNotFoundException e) { + // Unregister the listener if the package can't be found + unregisterAppOpsListener(); + } + } + }; + + public PipAppOpsListener(Context context, IActivityManager activityManager, + PipMotionHelper motionHelper) { + mContext = context; + mActivityManager = activityManager; + mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); + mMotionHelper = motionHelper; + } + + public void onActivityPinned(String packageName) { + // Register for changes to the app ops setting for this package while it is in PiP + registerAppOpsListener(packageName); + } + + public void onActivityUnpinned() { + // Unregister for changes to the previously PiP'ed package + unregisterAppOpsListener(); + } + + private void registerAppOpsListener(String packageName) { + mAppOpsManager.startWatchingMode(OP_PICTURE_IN_PICTURE, packageName, + mAppOpsChangedListener); + } + + private void unregisterAppOpsListener() { + mAppOpsManager.stopWatchingMode(mAppOpsChangedListener); + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java index db999c45dbde..36531bb727a4 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java @@ -64,8 +64,8 @@ public class PipManager implements BasePipManager { private InputConsumerController mInputConsumerController; private PipMenuActivityController mMenuController; private PipMediaController mMediaController; - private PipNotificationController mNotificationController; private PipTouchHandler mTouchHandler; + private PipAppOpsListener mAppOpsListener; /** * Handler for system task stack changes. @@ -76,8 +76,7 @@ public class PipManager implements BasePipManager { mTouchHandler.onActivityPinned(); mMediaController.onActivityPinned(); mMenuController.onActivityPinned(); - mNotificationController.onActivityPinned(packageName, userId, - true /* deferUntilAnimationEnds */); + mAppOpsListener.onActivityPinned(packageName); SystemServicesProxy.getInstance(mContext).setPipVisibility(true); } @@ -90,7 +89,7 @@ public class PipManager implements BasePipManager { final int userId = topActivity != null ? topPipActivityInfo.second : 0; mMenuController.onActivityUnpinned(); mTouchHandler.onActivityUnpinned(topActivity); - mNotificationController.onActivityUnpinned(topActivity, userId); + mAppOpsListener.onActivityUnpinned(); SystemServicesProxy.getInstance(mContext).setPipVisibility(topActivity != null); } @@ -107,7 +106,6 @@ public class PipManager implements BasePipManager { mTouchHandler.setTouchEnabled(true); mTouchHandler.onPinnedStackAnimationEnded(); mMenuController.onPinnedStackAnimationEnded(); - mNotificationController.onPinnedStackAnimationEnded(); } @Override @@ -182,7 +180,7 @@ public class PipManager implements BasePipManager { mInputConsumerController); mTouchHandler = new PipTouchHandler(context, mActivityManager, mMenuController, mInputConsumerController); - mNotificationController = new PipNotificationController(context, mActivityManager, + mAppOpsListener = new PipAppOpsListener(context, mActivityManager, mTouchHandler.getMotionHelper()); EventBus.getDefault().register(this); } diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java index 90f7b8db1c59..bfe07a980ce7 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java @@ -16,6 +16,10 @@ package com.android.systemui.pip.phone; +import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK; +import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; +import static android.provider.Settings.ACTION_PICTURE_IN_PICTURE_SETTINGS; + import static com.android.systemui.pip.phone.PipMenuActivityController.EXTRA_ACTIONS; import static com.android.systemui.pip.phone.PipMenuActivityController.EXTRA_ALLOW_TIMEOUT; import static com.android.systemui.pip.phone.PipMenuActivityController.EXTRA_CONTROLLER_MESSENGER; @@ -39,6 +43,7 @@ import android.app.Activity; import android.app.ActivityManager; import android.app.PendingIntent.CanceledException; import android.app.RemoteAction; +import android.content.ComponentName; import android.content.Intent; import android.content.pm.ParceledListSlice; import android.graphics.Color; @@ -46,12 +51,15 @@ import android.graphics.PointF; import android.graphics.Rect; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; +import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.os.Messenger; import android.os.RemoteException; +import android.os.UserHandle; import android.util.Log; +import android.util.Pair; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; @@ -105,6 +113,7 @@ public class PipMenuActivity extends Activity { private Drawable mBackgroundDrawable; private View mMenuContainer; private LinearLayout mActionsGroup; + private View mSettingsButton; private View mDismissButton; private ImageView mExpandButton; private int mBetweenActionPaddingLand; @@ -218,6 +227,11 @@ public class PipMenuActivity extends Activity { } return true; }); + mSettingsButton = findViewById(R.id.settings); + mSettingsButton.setAlpha(0); + mSettingsButton.setOnClickListener((v) -> { + showSettings(); + }); mDismissButton = findViewById(R.id.dismiss); mDismissButton.setAlpha(0); mDismissButton.setOnClickListener((v) -> { @@ -352,12 +366,14 @@ public class PipMenuActivity extends Activity { ObjectAnimator menuAnim = ObjectAnimator.ofFloat(mMenuContainer, View.ALPHA, mMenuContainer.getAlpha(), 1f); menuAnim.addUpdateListener(mMenuBgUpdateListener); + ObjectAnimator settingsAnim = ObjectAnimator.ofFloat(mSettingsButton, View.ALPHA, + mSettingsButton.getAlpha(), 1f); ObjectAnimator dismissAnim = ObjectAnimator.ofFloat(mDismissButton, View.ALPHA, mDismissButton.getAlpha(), 1f); if (menuState == MENU_STATE_FULL) { - mMenuContainerAnimator.playTogether(menuAnim, dismissAnim); + mMenuContainerAnimator.playTogether(menuAnim, settingsAnim, dismissAnim); } else { - mMenuContainerAnimator.play(dismissAnim); + mMenuContainerAnimator.playTogether(settingsAnim, dismissAnim); } mMenuContainerAnimator.setInterpolator(Interpolators.ALPHA_IN); mMenuContainerAnimator.setDuration(MENU_FADE_DURATION); @@ -394,9 +410,11 @@ public class PipMenuActivity extends Activity { ObjectAnimator menuAnim = ObjectAnimator.ofFloat(mMenuContainer, View.ALPHA, mMenuContainer.getAlpha(), 0f); menuAnim.addUpdateListener(mMenuBgUpdateListener); + ObjectAnimator settingsAnim = ObjectAnimator.ofFloat(mSettingsButton, View.ALPHA, + mSettingsButton.getAlpha(), 0f); ObjectAnimator dismissAnim = ObjectAnimator.ofFloat(mDismissButton, View.ALPHA, mDismissButton.getAlpha(), 0f); - mMenuContainerAnimator.playTogether(menuAnim, dismissAnim); + mMenuContainerAnimator.playTogether(menuAnim, settingsAnim, dismissAnim); mMenuContainerAnimator.setInterpolator(Interpolators.ALPHA_OUT); mMenuContainerAnimator.setDuration(MENU_FADE_DURATION); mMenuContainerAnimator.addListener(new AnimatorListenerAdapter() { @@ -526,12 +544,14 @@ public class PipMenuActivity extends Activity { final float menuAlpha = 1 - fraction; if (mMenuState == MENU_STATE_FULL) { mMenuContainer.setAlpha(menuAlpha); + mSettingsButton.setAlpha(menuAlpha); mDismissButton.setAlpha(menuAlpha); final float interpolatedAlpha = MENU_BACKGROUND_ALPHA * menuAlpha + DISMISS_BACKGROUND_ALPHA * fraction; alpha = (int) (interpolatedAlpha * 255); } else { if (mMenuState == MENU_STATE_CLOSE) { + mSettingsButton.setAlpha(menuAlpha); mDismissButton.setAlpha(menuAlpha); } alpha = (int) (fraction * DISMISS_BACKGROUND_ALPHA * 255); @@ -588,6 +608,19 @@ public class PipMenuActivity extends Activity { sendMessage(m, "Could not notify controller to show PIP menu"); } + private void showSettings() { + final Pair<ComponentName, Integer> topPipActivityInfo = + PipUtils.getTopPinnedActivity(this, ActivityManager.getService()); + if (topPipActivityInfo.first != null) { + final UserHandle user = UserHandle.of(topPipActivityInfo.second); + final Intent settingsIntent = new Intent(ACTION_PICTURE_IN_PICTURE_SETTINGS, + Uri.fromParts("package", topPipActivityInfo.first.getPackageName(), null)); + settingsIntent.putExtra(Intent.EXTRA_USER_HANDLE, user); + settingsIntent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK); + startActivity(settingsIntent); + } + } + private void notifyActivityCallback(Messenger callback) { Message m = Message.obtain(); m.what = PipMenuActivityController.MESSAGE_UPDATE_ACTIVITY_CALLBACK; diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipNotificationController.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipNotificationController.java deleted file mode 100644 index 6d083e9d601d..000000000000 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipNotificationController.java +++ /dev/null @@ -1,231 +0,0 @@ -/* - * Copyright (C) 2017 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.systemui.pip.phone; - -import static android.app.AppOpsManager.MODE_ALLOWED; -import static android.app.AppOpsManager.OP_PICTURE_IN_PICTURE; -import static android.app.PendingIntent.FLAG_CANCEL_CURRENT; -import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK; -import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; -import static android.provider.Settings.ACTION_PICTURE_IN_PICTURE_SETTINGS; - -import android.app.AppOpsManager; -import android.app.AppOpsManager.OnOpChangedListener; -import android.app.IActivityManager; -import android.app.Notification; -import android.app.NotificationManager; -import android.app.PendingIntent; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager; -import android.content.pm.PackageManager.NameNotFoundException; -import android.graphics.Bitmap; -import android.graphics.Bitmap.Config; -import android.graphics.Canvas; -import android.graphics.drawable.Drawable; -import android.net.Uri; -import android.os.UserHandle; -import android.util.IconDrawableFactory; -import android.util.Log; -import android.util.Pair; - -import com.android.systemui.R; -import com.android.systemui.SystemUI; -import com.android.systemui.util.NotificationChannels; - -/** - * Manages the BTW notification that shows whenever an activity enters or leaves picture-in-picture. - */ -public class PipNotificationController { - private static final String TAG = PipNotificationController.class.getSimpleName(); - - private static final String NOTIFICATION_TAG = PipNotificationController.class.getName(); - private static final int NOTIFICATION_ID = 0; - - private Context mContext; - private IActivityManager mActivityManager; - private AppOpsManager mAppOpsManager; - private NotificationManager mNotificationManager; - private IconDrawableFactory mIconDrawableFactory; - - private PipMotionHelper mMotionHelper; - - // Used when building a deferred notification - private String mDeferredNotificationPackageName; - private int mDeferredNotificationUserId; - - private AppOpsManager.OnOpChangedListener mAppOpsChangedListener = new OnOpChangedListener() { - @Override - public void onOpChanged(String op, String packageName) { - try { - // Dismiss the PiP once the user disables the app ops setting for that package - final Pair<ComponentName, Integer> topPipActivityInfo = - PipUtils.getTopPinnedActivity(mContext, mActivityManager); - if (topPipActivityInfo.first != null) { - final ApplicationInfo appInfo = mContext.getPackageManager() - .getApplicationInfoAsUser(packageName, 0, topPipActivityInfo.second); - if (appInfo.packageName.equals(topPipActivityInfo.first.getPackageName()) && - mAppOpsManager.checkOpNoThrow(OP_PICTURE_IN_PICTURE, appInfo.uid, - packageName) != MODE_ALLOWED) { - mMotionHelper.dismissPip(); - } - } - } catch (NameNotFoundException e) { - // Unregister the listener if the package can't be found - unregisterAppOpsListener(); - } - } - }; - - public PipNotificationController(Context context, IActivityManager activityManager, - PipMotionHelper motionHelper) { - mContext = context; - mActivityManager = activityManager; - mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); - mNotificationManager = NotificationManager.from(context); - mMotionHelper = motionHelper; - mIconDrawableFactory = IconDrawableFactory.newInstance(context); - } - - public void onActivityPinned(String packageName, int userId, boolean deferUntilAnimationEnds) { - // Clear any existing notification - mNotificationManager.cancel(NOTIFICATION_TAG, NOTIFICATION_ID); - - if (deferUntilAnimationEnds) { - mDeferredNotificationPackageName = packageName; - mDeferredNotificationUserId = userId; - } else { - showNotificationForApp(packageName, userId); - } - - // Register for changes to the app ops setting for this package while it is in PiP - registerAppOpsListener(packageName); - } - - public void onPinnedStackAnimationEnded() { - if (mDeferredNotificationPackageName != null) { - showNotificationForApp(mDeferredNotificationPackageName, mDeferredNotificationUserId); - mDeferredNotificationPackageName = null; - mDeferredNotificationUserId = 0; - } - } - - public void onActivityUnpinned(ComponentName topPipActivity, int userId) { - // Unregister for changes to the previously PiP'ed package - unregisterAppOpsListener(); - - // Reset the deferred notification package - mDeferredNotificationPackageName = null; - mDeferredNotificationUserId = 0; - - if (topPipActivity != null) { - // onActivityUnpinned() is only called after the transition is complete, so we don't - // need to defer until the animation ends to update the notification - onActivityPinned(topPipActivity.getPackageName(), userId, - false /* deferUntilAnimationEnds */); - } else { - mNotificationManager.cancel(NOTIFICATION_TAG, NOTIFICATION_ID); - } - } - - /** - * Builds and shows the notification for the given app. - */ - private void showNotificationForApp(String packageName, int userId) { - // Build a new notification - try { - final UserHandle user = UserHandle.of(userId); - final Context userContext = mContext.createPackageContextAsUser( - mContext.getPackageName(), 0, user); - final Notification.Builder builder = - new Notification.Builder(userContext, NotificationChannels.GENERAL) - .setLocalOnly(true) - .setOngoing(true) - .setSmallIcon(R.drawable.pip_notification_icon) - .setColor(mContext.getColor( - com.android.internal.R.color.system_notification_accent_color)); - if (updateNotificationForApp(builder, packageName, user)) { - SystemUI.overrideNotificationAppName(mContext, builder); - - // Show the new notification - mNotificationManager.notify(NOTIFICATION_TAG, NOTIFICATION_ID, builder.build()); - } - } catch (NameNotFoundException e) { - Log.e(TAG, "Could not show notification for application", e); - } - } - - /** - * Updates the notification builder with app-specific information, returning whether it was - * successful. - */ - private boolean updateNotificationForApp(Notification.Builder builder, String packageName, - UserHandle user) throws NameNotFoundException { - final PackageManager pm = mContext.getPackageManager(); - final ApplicationInfo appInfo; - try { - appInfo = pm.getApplicationInfoAsUser(packageName, 0, user.getIdentifier()); - } catch (NameNotFoundException e) { - Log.e(TAG, "Could not update notification for application", e); - return false; - } - - if (appInfo != null) { - final String appName = pm.getUserBadgedLabel(pm.getApplicationLabel(appInfo), user) - .toString(); - final String message = mContext.getString(R.string.pip_notification_message, appName); - final Intent settingsIntent = new Intent(ACTION_PICTURE_IN_PICTURE_SETTINGS, - Uri.fromParts("package", packageName, null)); - settingsIntent.putExtra(Intent.EXTRA_USER_HANDLE, user); - settingsIntent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK); - - final Drawable iconDrawable = mIconDrawableFactory.getBadgedIcon(appInfo); - builder.setContentTitle(mContext.getString(R.string.pip_notification_title, appName)) - .setContentText(message) - .setContentIntent(PendingIntent.getActivityAsUser(mContext, packageName.hashCode(), - settingsIntent, FLAG_CANCEL_CURRENT, null, user)) - .setStyle(new Notification.BigTextStyle().bigText(message)) - .setLargeIcon(createBitmap(iconDrawable).createAshmemBitmap()); - return true; - } - return false; - } - - private void registerAppOpsListener(String packageName) { - mAppOpsManager.startWatchingMode(OP_PICTURE_IN_PICTURE, packageName, - mAppOpsChangedListener); - } - - private void unregisterAppOpsListener() { - mAppOpsManager.stopWatchingMode(mAppOpsChangedListener); - } - - /** - * Bakes a drawable into a bitmap. - */ - private Bitmap createBitmap(Drawable d) { - Bitmap bitmap = Bitmap.createBitmap(d.getIntrinsicWidth(), d.getIntrinsicHeight(), - Config.ARGB_8888); - Canvas c = new Canvas(bitmap); - d.setBounds(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight()); - d.draw(c); - c.setBitmap(null); - return bitmap; - } -} diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java index 0b7b6d555fdf..927a49cb60f3 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java @@ -18,11 +18,6 @@ package com.android.systemui.qs; import static android.app.StatusBarManager.DISABLE2_QUICK_SETTINGS; -import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_QS_DATE; - -import android.app.ActivityManager; -import android.app.AlarmManager; -import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.content.res.Configuration; @@ -31,7 +26,6 @@ import android.graphics.PorterDuff.Mode; import android.graphics.drawable.Drawable; import android.graphics.drawable.RippleDrawable; import android.os.UserManager; -import android.provider.AlarmClock; import android.support.annotation.Nullable; import android.support.annotation.VisibleForTesting; import android.util.AttributeSet; @@ -39,24 +33,19 @@ import android.view.View; import android.view.View.OnClickListener; import android.widget.FrameLayout; import android.widget.ImageView; -import android.widget.TextView; import android.widget.Toast; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto; -import com.android.keyguard.KeyguardStatusView; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.settingslib.Utils; import com.android.settingslib.drawable.UserIconDrawable; import com.android.systemui.Dependency; -import com.android.systemui.FontSizeUtils; import com.android.systemui.R; import com.android.systemui.R.dimen; -import com.android.systemui.R.id; import com.android.systemui.SysUiServiceProvider; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.qs.TouchAnimator.Builder; -import com.android.systemui.qs.TouchAnimator.ListenerAdapter; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.phone.ExpandableIndicator; import com.android.systemui.statusbar.phone.MultiUserSwitch; @@ -65,8 +54,6 @@ import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.statusbar.policy.NetworkController; import com.android.systemui.statusbar.policy.NetworkController.EmergencyListener; import com.android.systemui.statusbar.policy.NetworkController.SignalCallback; -import com.android.systemui.statusbar.policy.NextAlarmController; -import com.android.systemui.statusbar.policy.NextAlarmController.NextAlarmChangeCallback; import com.android.systemui.statusbar.policy.UserInfoController; import com.android.systemui.statusbar.policy.UserInfoController.OnUserInfoChangedListener; import com.android.systemui.tuner.TunerService; diff --git a/packages/SystemUI/tests/Android.mk b/packages/SystemUI/tests/Android.mk index 9d44895dbe8d..066cfe5862ef 100644 --- a/packages/SystemUI/tests/Android.mk +++ b/packages/SystemUI/tests/Android.mk @@ -46,7 +46,12 @@ LOCAL_STATIC_ANDROID_LIBRARIES := \ android-support-v7-mediarouter \ android-support-v7-palette \ android-support-v14-preference \ - android-support-v17-leanback + android-support-v17-leanback \ + android-slices-core \ + android-slices-view \ + android-slices-builders \ + apptoolkit-arch-core-runtime \ + apptoolkit-lifecycle-extensions \ LOCAL_STATIC_JAVA_LIBRARIES := \ metrics-helper-lib \ diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java index 4eae3426f815..be28569ef629 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java @@ -16,11 +16,10 @@ package com.android.systemui.keyguard; -import android.app.slice.Slice; -import android.app.slice.SliceItem; -import android.app.slice.SliceQuery; +import androidx.app.slice.Slice; import android.content.Intent; import android.net.Uri; +import android.os.Debug; import android.os.Handler; import android.support.test.filters.SmallTest; import android.testing.AndroidTestingRunner; @@ -34,6 +33,9 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import androidx.app.slice.SliceItem; +import androidx.app.slice.core.SliceQuery; + @SmallTest @RunWith(AndroidTestingRunner.class) @RunWithLooper(setAsMainLooper = true) @@ -63,7 +65,8 @@ public class KeyguardSliceProviderTest extends SysuiTestCase { @Test public void returnsValidSlice() { Slice slice = mProvider.onBindSlice(Uri.parse(KeyguardSliceProvider.KEYGUARD_SLICE_URI)); - SliceItem text = SliceQuery.find(slice, SliceItem.FORMAT_TEXT, Slice.HINT_TITLE, + SliceItem text = SliceQuery.find(slice, android.app.slice.SliceItem.FORMAT_TEXT, + android.app.slice.Slice.HINT_TITLE, null /* nonHints */); Assert.assertNotNull("Slice must provide a title.", text); } @@ -78,9 +81,10 @@ public class KeyguardSliceProviderTest extends SysuiTestCase { @Test public void updatesClock() { + mProvider.mUpdateClockInvokations = 0; mProvider.mIntentReceiver.onReceive(getContext(), new Intent(Intent.ACTION_TIME_TICK)); TestableLooper.get(this).processAllMessages(); - Assert.assertEquals("Clock should have been updated.", 2 /* expected */, + Assert.assertEquals("Clock should have been updated.", 1 /* expected */, mProvider.mUpdateClockInvokations); } diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto index 935b787250fb..1aaa53837bf9 100644 --- a/proto/src/metrics_constants.proto +++ b/proto/src/metrics_constants.proto @@ -5094,6 +5094,36 @@ message MetricsEvent { // Tag used to report autofill field classification scores FIELD_AUTOFILL_MATCH_SCORE = 1274; + // ACTION: Usb config has been changed to charging + // CATEGORY: SETTINGS + // OS: P + ACTION_USB_CONFIG_CHARGING = 1275; + + // ACTION: Usb config has been changed to mtp (file transfer) + // CATEGORY: SETTINGS + // OS: P + ACTION_USB_CONFIG_MTP = 1276; + + // ACTION: Usb config has been changed to ptp (photo transfer) + // CATEGORY: SETTINGS + // OS: P + ACTION_USB_CONFIG_PTP = 1277; + + // ACTION: Usb config has been changed to rndis (usb tethering) + // CATEGORY: SETTINGS + // OS: P + ACTION_USB_CONFIG_RNDIS = 1278; + + // ACTION: Usb config has been changed to midi + // CATEGORY: SETTINGS + // OS: P + ACTION_USB_CONFIG_MIDI = 1279; + + // ACTION: Usb config has been changed to accessory + // CATEGORY: SETTINGS + // OS: P + ACTION_USB_CONFIG_ACCESSORY = 1280; + // ---- End P Constants, all P constants go above this line ---- // Add new aosp constants above this line. // END OF AOSP CONSTANTS diff --git a/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java index 64dc278d668d..74d2dddcdfb3 100644 --- a/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java +++ b/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java @@ -229,7 +229,7 @@ class MagnificationGestureHandler extends BaseEventStreamTransformation { } void clearAndTransitionToStateDetecting() { - mCurrentState = mDelegatingState; + mCurrentState = mDetectingState; mDetectingState.clear(); mViewportDraggingState.clear(); mPanningScalingState.clear(); @@ -649,14 +649,19 @@ class MagnificationGestureHandler extends BaseEventStreamTransformation { break; case ACTION_MOVE: { if (isFingerDown() - && distance(mLastDown, /* move */ event) > mSwipeMinDistance - // For convenience, viewport dragging on 3tap&hold takes precedence - // over insta-delegating on 3tap&swipe - // (which is a rare combo to be used aside from magnification) - && !isMultiTapTriggered(2 /* taps */)) { - - // Swipe detected - delegate skipping timeout - transitionToDelegatingStateAndClear(); + && distance(mLastDown, /* move */ event) > mSwipeMinDistance) { + + // Swipe detected - transition immediately + + // For convenience, viewport dragging takes precedence + // over insta-delegating on 3tap&swipe + // (which is a rare combo to be used aside from magnification) + if (isMultiTapTriggered(2 /* taps */)) { + transitionTo(mViewportDraggingState); + clear(); + } else { + transitionToDelegatingStateAndClear(); + } } } break; diff --git a/services/backup/java/com/android/server/backup/BackupManagerConstants.java b/services/backup/java/com/android/server/backup/BackupManagerConstants.java index 245241cb1f4c..537592e33d5c 100644 --- a/services/backup/java/com/android/server/backup/BackupManagerConstants.java +++ b/services/backup/java/com/android/server/backup/BackupManagerConstants.java @@ -24,6 +24,7 @@ import android.database.ContentObserver; import android.net.Uri; import android.os.Handler; import android.provider.Settings; +import android.text.TextUtils; import android.util.KeyValueListParser; import android.util.Slog; @@ -51,6 +52,8 @@ class BackupManagerConstants extends ContentObserver { "full_backup_require_charging"; private static final String FULL_BACKUP_REQUIRED_NETWORK_TYPE = "full_backup_required_network_type"; + private static final String BACKUP_FINISHED_NOTIFICATION_RECEIVERS = + "backup_finished_notification_receivers"; // Hard coded default values. private static final long DEFAULT_KEY_VALUE_BACKUP_INTERVAL_MILLISECONDS = @@ -63,6 +66,7 @@ class BackupManagerConstants extends ContentObserver { 24 * AlarmManager.INTERVAL_HOUR; private static final boolean DEFAULT_FULL_BACKUP_REQUIRE_CHARGING = true; private static final int DEFAULT_FULL_BACKUP_REQUIRED_NETWORK_TYPE = 2; + private static final String DEFAULT_BACKUP_FINISHED_NOTIFICATION_RECEIVERS = ""; // Backup manager constants. private long mKeyValueBackupIntervalMilliseconds; @@ -72,6 +76,7 @@ class BackupManagerConstants extends ContentObserver { private long mFullBackupIntervalMilliseconds; private boolean mFullBackupRequireCharging; private int mFullBackupRequiredNetworkType; + private String[] mBackupFinishedNotificationReceivers; private ContentResolver mResolver; private final KeyValueListParser mParser = new KeyValueListParser(','); @@ -116,6 +121,14 @@ class BackupManagerConstants extends ContentObserver { DEFAULT_FULL_BACKUP_REQUIRE_CHARGING); mFullBackupRequiredNetworkType = mParser.getInt(FULL_BACKUP_REQUIRED_NETWORK_TYPE, DEFAULT_FULL_BACKUP_REQUIRED_NETWORK_TYPE); + final String backupFinishedNotificationReceivers = mParser.getString( + BACKUP_FINISHED_NOTIFICATION_RECEIVERS, + DEFAULT_BACKUP_FINISHED_NOTIFICATION_RECEIVERS); + if (backupFinishedNotificationReceivers.isEmpty()) { + mBackupFinishedNotificationReceivers = new String[] {}; + } else { + mBackupFinishedNotificationReceivers = backupFinishedNotificationReceivers.split(":"); + } } // The following are access methods for the individual parameters. @@ -167,7 +180,6 @@ class BackupManagerConstants extends ContentObserver { Slog.v(TAG, "getFullBackupRequireCharging(...) returns " + mFullBackupRequireCharging); } return mFullBackupRequireCharging; - } public synchronized int getFullBackupRequiredNetworkType() { @@ -177,4 +189,12 @@ class BackupManagerConstants extends ContentObserver { } return mFullBackupRequiredNetworkType; } + + public synchronized String[] getBackupFinishedNotificationReceivers() { + if (RefactoredBackupManagerService.DEBUG_SCHEDULING) { + Slog.v(TAG, "getBackupFinishedNotificationReceivers(...) returns " + + TextUtils.join(", ", mBackupFinishedNotificationReceivers)); + } + return mBackupFinishedNotificationReceivers; + } } diff --git a/services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java b/services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java index 94b06b67ff87..51c44e103cc4 100644 --- a/services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java +++ b/services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java @@ -203,6 +203,8 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter public static final String RUN_BACKUP_ACTION = "android.app.backup.intent.RUN"; public static final String RUN_INITIALIZE_ACTION = "android.app.backup.intent.INIT"; + public static final String BACKUP_FINISHED_ACTION = "android.intent.action.BACKUP_FINISHED"; + public static final String BACKUP_FINISHED_PACKAGE_EXTRA = "packageName"; // Timeout interval for deciding that a bind or clear-data has taken too long private static final long TIMEOUT_INTERVAL = 10 * 1000; @@ -1084,34 +1086,34 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter } /** - * Maintain persistent state around whether need to do an initialize operation. - * Must be called with the queue lock held. + * Maintain persistent state around whether need to do an initialize operation. This will lock + * on {@link #getQueueLock()}. */ - @GuardedBy("mQueueLock") - public void recordInitPendingLocked( + public void recordInitPending( boolean isPending, String transportName, String transportDirName) { - if (MORE_DEBUG) { - Slog.i(TAG, "recordInitPendingLocked: " + isPending - + " on transport " + transportName); - } + synchronized (mQueueLock) { + if (MORE_DEBUG) { + Slog.i(TAG, "recordInitPending(" + isPending + ") on transport " + transportName); + } - File stateDir = new File(mBaseStateDir, transportDirName); - File initPendingFile = new File(stateDir, INIT_SENTINEL_FILE_NAME); + File stateDir = new File(mBaseStateDir, transportDirName); + File initPendingFile = new File(stateDir, INIT_SENTINEL_FILE_NAME); - if (isPending) { - // We need an init before we can proceed with sending backup data. - // Record that with an entry in our set of pending inits, as well as - // journaling it via creation of a sentinel file. - mPendingInits.add(transportName); - try { - (new FileOutputStream(initPendingFile)).close(); - } catch (IOException ioe) { - // Something is badly wrong with our permissions; just try to move on + if (isPending) { + // We need an init before we can proceed with sending backup data. + // Record that with an entry in our set of pending inits, as well as + // journaling it via creation of a sentinel file. + mPendingInits.add(transportName); + try { + (new FileOutputStream(initPendingFile)).close(); + } catch (IOException ioe) { + // Something is badly wrong with our permissions; just try to move on + } + } else { + // No more initialization needed; wipe the journal and reset our state. + initPendingFile.delete(); + mPendingInits.remove(transportName); } - } else { - // No more initialization needed; wipe the journal and reset our state. - initPendingFile.delete(); - mPendingInits.remove(transportName); } } @@ -1418,6 +1420,16 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter public void logBackupComplete(String packageName) { if (packageName.equals(PACKAGE_MANAGER_SENTINEL)) return; + for (String receiver : mConstants.getBackupFinishedNotificationReceivers()) { + final Intent notification = new Intent(); + notification.setAction(BACKUP_FINISHED_ACTION); + notification.setPackage(receiver); + notification.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES | + Intent.FLAG_RECEIVER_FOREGROUND); + notification.putExtra(BACKUP_FINISHED_PACKAGE_EXTRA, packageName); + mContext.sendBroadcastAsUser(notification, UserHandle.OWNER); + } + mProcessedPackagesJournal.addPackage(packageName); } @@ -2311,7 +2323,9 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter final long oldId = Binder.clearCallingIdentity(); try { mWakelock.acquire(); - mBackupHandler.post(new PerformInitializeTask(this, transportNames, observer)); + OnTaskFinishedListener listener = caller -> mWakelock.release(); + mBackupHandler.post( + new PerformInitializeTask(this, transportNames, observer, listener)); } finally { Binder.restoreCallingIdentity(oldId); } @@ -2802,7 +2816,7 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter // build the set of transports for which we are posting an init for (int i = 0; i < transportNames.size(); i++) { - recordInitPendingLocked( + recordInitPending( true, transportNames.get(i), transportDirNames.get(i)); diff --git a/services/backup/java/com/android/server/backup/internal/PerformInitializeTask.java b/services/backup/java/com/android/server/backup/internal/PerformInitializeTask.java index b21b0724acc2..c6246981ef44 100644 --- a/services/backup/java/com/android/server/backup/internal/PerformInitializeTask.java +++ b/services/backup/java/com/android/server/backup/internal/PerformInitializeTask.java @@ -18,6 +18,7 @@ package com.android.server.backup.internal; import static com.android.server.backup.RefactoredBackupManagerService.TAG; +import android.annotation.Nullable; import android.app.AlarmManager; import android.app.backup.BackupTransport; import android.app.backup.IBackupObserver; @@ -26,23 +27,63 @@ import android.os.SystemClock; import android.util.EventLog; import android.util.Slog; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.backup.IBackupTransport; import com.android.server.EventLogTags; import com.android.server.backup.RefactoredBackupManagerService; +import com.android.server.backup.TransportManager; +import com.android.server.backup.transport.TransportClient; import java.io.File; +import java.util.ArrayList; +import java.util.List; +/** + * Attempts to call {@link BackupTransport#initializeDevice()} followed by + * {@link BackupTransport#finishBackup()} for the transport names passed in with the intent of + * wiping backup data from the transport. + * + * If the transport returns error, it will record the operation as pending and schedule it to run in + * a future time according to {@link BackupTransport#requestBackupTime()}. The result status + * reported to observers will be the last unsuccessful status reported by the transports. If every + * operation was successful then it's {@link BackupTransport#TRANSPORT_OK}. + */ public class PerformInitializeTask implements Runnable { + private final RefactoredBackupManagerService mBackupManagerService; + private final TransportManager mTransportManager; + private final String[] mQueue; + private final File mBaseStateDir; + private final OnTaskFinishedListener mListener; + @Nullable private IBackupObserver mObserver; - private RefactoredBackupManagerService backupManagerService; - String[] mQueue; - IBackupObserver mObserver; + public PerformInitializeTask( + RefactoredBackupManagerService backupManagerService, + String[] transportNames, + @Nullable IBackupObserver observer, + OnTaskFinishedListener listener) { + this( + backupManagerService, + backupManagerService.getTransportManager(), + transportNames, + observer, + listener, + backupManagerService.getBaseStateDir()); + } - public PerformInitializeTask(RefactoredBackupManagerService backupManagerService, - String[] transportNames, IBackupObserver observer) { - this.backupManagerService = backupManagerService; + @VisibleForTesting + PerformInitializeTask( + RefactoredBackupManagerService backupManagerService, + TransportManager transportManager, + String[] transportNames, + @Nullable IBackupObserver observer, + OnTaskFinishedListener listener, + File baseStateDir) { + mBackupManagerService = backupManagerService; + mTransportManager = transportManager; mQueue = transportNames; mObserver = observer; + mListener = listener; + mBaseStateDir = baseStateDir; } private void notifyResult(String target, int status) { @@ -67,21 +108,25 @@ public class PerformInitializeTask implements Runnable { public void run() { // mWakelock is *acquired* when execution begins here + String callerLogString = "PerformInitializeTask.run()"; + List<TransportClient> transportClientsToDisposeOf = new ArrayList<>(mQueue.length); int result = BackupTransport.TRANSPORT_OK; try { for (String transportName : mQueue) { - IBackupTransport transport = - backupManagerService.getTransportManager().getTransportBinder( - transportName); - if (transport == null) { + TransportClient transportClient = + mTransportManager.getTransportClient(transportName, callerLogString); + if (transportClient == null) { Slog.e(TAG, "Requested init for " + transportName + " but not found"); continue; } + transportClientsToDisposeOf.add(transportClient); Slog.i(TAG, "Initializing (wiping) backup transport storage: " + transportName); - String transportDirName = transport.transportDirName(); + String transportDirName = transportClient.getTransportDirName(); EventLog.writeEvent(EventLogTags.BACKUP_START, transportDirName); long startRealtime = SystemClock.elapsedRealtime(); + + IBackupTransport transport = transportClient.connectOrThrow(callerLogString); int status = transport.initializeDevice(); if (status == BackupTransport.TRANSPORT_OK) { @@ -93,42 +138,38 @@ public class PerformInitializeTask implements Runnable { Slog.i(TAG, "Device init successful"); int millis = (int) (SystemClock.elapsedRealtime() - startRealtime); EventLog.writeEvent(EventLogTags.BACKUP_INITIALIZE); - backupManagerService - .resetBackupState(new File(backupManagerService.getBaseStateDir(), - transportDirName)); + File stateFileDir = new File(mBaseStateDir, transportDirName); + mBackupManagerService.resetBackupState(stateFileDir); EventLog.writeEvent(EventLogTags.BACKUP_SUCCESS, 0, millis); - synchronized (backupManagerService.getQueueLock()) { - backupManagerService.recordInitPendingLocked( - false, transportName, transportDirName); - } + mBackupManagerService.recordInitPending(false, transportName, transportDirName); notifyResult(transportName, BackupTransport.TRANSPORT_OK); } else { // If this didn't work, requeue this one and try again // after a suitable interval Slog.e(TAG, "Transport error in initializeDevice()"); EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_FAILURE, "(initialize)"); - synchronized (backupManagerService.getQueueLock()) { - backupManagerService.recordInitPendingLocked( - true, transportName, transportDirName); - } + mBackupManagerService.recordInitPending(true, transportName, transportDirName); notifyResult(transportName, status); result = status; // do this via another alarm to make sure of the wakelock states long delay = transport.requestBackupTime(); Slog.w(TAG, "Init failed on " + transportName + " resched in " + delay); - backupManagerService.getAlarmManager().set(AlarmManager.RTC_WAKEUP, + mBackupManagerService.getAlarmManager().set( + AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + delay, - backupManagerService.getRunInitIntent()); + mBackupManagerService.getRunInitIntent()); } } } catch (Exception e) { Slog.e(TAG, "Unexpected error performing init", e); result = BackupTransport.TRANSPORT_ERROR; } finally { - // Done; release the wakelock + for (TransportClient transportClient : transportClientsToDisposeOf) { + mTransportManager.disposeOfTransportClient(transportClient, callerLogString); + } notifyFinished(result); - backupManagerService.getWakelock().release(); + mListener.onFinished(callerLogString); } } } diff --git a/services/backup/java/com/android/server/backup/internal/RunInitializeReceiver.java b/services/backup/java/com/android/server/backup/internal/RunInitializeReceiver.java index 1df0bf0c6e08..6c160a332096 100644 --- a/services/backup/java/com/android/server/backup/internal/RunInitializeReceiver.java +++ b/services/backup/java/com/android/server/backup/internal/RunInitializeReceiver.java @@ -23,37 +23,41 @@ import static com.android.server.backup.RefactoredBackupManagerService.TAG; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; +import android.os.PowerManager; import android.util.ArraySet; import android.util.Slog; import com.android.server.backup.RefactoredBackupManagerService; public class RunInitializeReceiver extends BroadcastReceiver { - - private RefactoredBackupManagerService backupManagerService; + private final RefactoredBackupManagerService mBackupManagerService; public RunInitializeReceiver(RefactoredBackupManagerService backupManagerService) { - this.backupManagerService = backupManagerService; + mBackupManagerService = backupManagerService; } public void onReceive(Context context, Intent intent) { if (RUN_INITIALIZE_ACTION.equals(intent.getAction())) { - synchronized (backupManagerService.getQueueLock()) { - final ArraySet<String> pendingInits = backupManagerService.getPendingInits(); + synchronized (mBackupManagerService.getQueueLock()) { + final ArraySet<String> pendingInits = mBackupManagerService.getPendingInits(); if (DEBUG) { Slog.v(TAG, "Running a device init; " + pendingInits.size() + " pending"); } if (pendingInits.size() > 0) { - final String[] transports = pendingInits.toArray(new String[pendingInits.size()]); - PerformInitializeTask initTask = new PerformInitializeTask(backupManagerService, - transports, null); - - // Acquire the wakelock and pass it to the init thread. it will - // be released once init concludes. - backupManagerService.clearPendingInits(); - backupManagerService.getWakelock().acquire(); - backupManagerService.getBackupHandler().post(initTask); + final String[] transports = + pendingInits.toArray(new String[pendingInits.size()]); + + mBackupManagerService.clearPendingInits(); + + PowerManager.WakeLock wakelock = mBackupManagerService.getWakelock(); + wakelock.acquire(); + OnTaskFinishedListener listener = caller -> wakelock.release(); + + Runnable task = + new PerformInitializeTask( + mBackupManagerService, transports, null, listener); + mBackupManagerService.getBackupHandler().post(task); } } } diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java index 06cf9820ca5f..472723dfa909 100644 --- a/services/core/java/com/android/server/AlarmManagerService.java +++ b/services/core/java/com/android/server/AlarmManagerService.java @@ -3035,15 +3035,8 @@ class AlarmManagerService extends SystemService { Slog.v(TAG, "sending alarm " + alarm); } if (RECORD_ALARMS_IN_HISTORY) { - if (alarm.workSource != null && alarm.workSource.size() > 0) { - for (int wi=0; wi<alarm.workSource.size(); wi++) { - ActivityManager.noteAlarmStart( - alarm.operation, alarm.workSource.get(wi), alarm.statsTag); - } - } else { - ActivityManager.noteAlarmStart( - alarm.operation, alarm.uid, alarm.statsTag); - } + ActivityManager.noteAlarmStart(alarm.operation, alarm.workSource, alarm.uid, + alarm.statsTag); } mDeliveryTracker.deliverLocked(alarm, nowELAPSED, allowWhileIdle); } catch (RuntimeException e) { @@ -3553,15 +3546,8 @@ class AlarmManagerService extends SystemService { fs.aggregateTime += nowELAPSED - fs.startTime; } if (RECORD_ALARMS_IN_HISTORY) { - if (inflight.mWorkSource != null && inflight.mWorkSource.size() > 0) { - for (int wi=0; wi<inflight.mWorkSource.size(); wi++) { - ActivityManager.noteAlarmFinish( - inflight.mPendingIntent, inflight.mWorkSource.get(wi), inflight.mTag); - } - } else { - ActivityManager.noteAlarmFinish( - inflight.mPendingIntent, inflight.mUid, inflight.mTag); - } + ActivityManager.noteAlarmFinish(inflight.mPendingIntent, inflight.mWorkSource, + inflight.mUid, inflight.mTag); } } @@ -3771,18 +3757,9 @@ class AlarmManagerService extends SystemService { || alarm.type == RTC_WAKEUP) { bs.numWakeup++; fs.numWakeup++; - if (alarm.workSource != null && alarm.workSource.size() > 0) { - for (int wi=0; wi<alarm.workSource.size(); wi++) { - final String wsName = alarm.workSource.getName(wi); - ActivityManager.noteWakeupAlarm( - alarm.operation, alarm.workSource.get(wi), - (wsName != null) ? wsName : alarm.packageName, - alarm.statsTag); - } - } else { - ActivityManager.noteWakeupAlarm( - alarm.operation, alarm.uid, alarm.packageName, alarm.statsTag); - } + ActivityManager.noteWakeupAlarm( + alarm.operation, alarm.workSource, alarm.uid, alarm.packageName, + alarm.statsTag); } } } diff --git a/services/core/java/com/android/server/EntropyMixer.java b/services/core/java/com/android/server/EntropyMixer.java index 9877717943df..5e6e9d34dc25 100644 --- a/services/core/java/com/android/server/EntropyMixer.java +++ b/services/core/java/com/android/server/EntropyMixer.java @@ -196,11 +196,14 @@ public class EntropyMixer extends Binder { * Mixes in the output from HW RNG (if present) into the Linux RNG. */ private void addHwRandomEntropy() { + if (!new File(hwRandomDevice).exists()) { + // HW RNG not present/exposed -- ignore + return; + } + try { RandomBlock.fromFile(hwRandomDevice).toFile(randomDevice, false); Slog.i(TAG, "Added HW RNG output to entropy pool"); - } catch (FileNotFoundException ignored) { - // HW RNG not present/exposed -- ignore } catch (IOException e) { Slog.w(TAG, "Failed to add HW RNG output to entropy pool", e); } diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java index 31aea63875ea..46eea78c7f8b 100644 --- a/services/core/java/com/android/server/accounts/AccountManagerService.java +++ b/services/core/java/com/android/server/accounts/AccountManagerService.java @@ -5595,24 +5595,25 @@ public class AccountManagerService long ident = Binder.clearCallingIdentity(); try { packages = mPackageManager.getPackagesForUid(callingUid); - } finally { - Binder.restoreCallingIdentity(ident); - } - if (packages != null) { - for (String name : packages) { - try { - PackageInfo packageInfo = mPackageManager.getPackageInfo(name, 0 /* flags */); - if (packageInfo != null - && (packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) - != 0) { - return true; + if (packages != null) { + for (String name : packages) { + try { + PackageInfo packageInfo = + mPackageManager.getPackageInfo(name, 0 /* flags */); + if (packageInfo != null + && (packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) + != 0) { + return true; + } + } catch (NameNotFoundException e) { + Log.w(TAG, String.format("Could not find package [%s]", name), e); } - } catch (NameNotFoundException e) { - Log.w(TAG, String.format("Could not find package [%s]", name), e); } + } else { + Log.w(TAG, "No known packages with uid " + callingUid); } - } else { - Log.w(TAG, "No known packages with uid " + callingUid); + } finally { + Binder.restoreCallingIdentity(ident); } return false; } diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index d92b3b86d47a..5936ce1f65de 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -13858,68 +13858,100 @@ public class ActivityManagerService extends IActivityManager.Stub Context.WINDOW_SERVICE)).addView(v, lp); } - public void noteWakeupAlarm(IIntentSender sender, int sourceUid, String sourcePkg, String tag) { - if (sender != null && !(sender instanceof PendingIntentRecord)) { - return; + @Override + public void noteWakeupAlarm(IIntentSender sender, WorkSource workSource, int sourceUid, + String sourcePkg, String tag) { + if (workSource != null && workSource.isEmpty()) { + workSource = null; } - final PendingIntentRecord rec = (PendingIntentRecord)sender; - final BatteryStatsImpl stats = mBatteryStatsService.getActiveStatistics(); - synchronized (stats) { - if (mBatteryStatsService.isOnBattery()) { - mBatteryStatsService.enforceCallingPermission(); - int MY_UID = Binder.getCallingUid(); - final int uid; - if (sender == null) { - uid = sourceUid; - } else { - uid = rec.uid == MY_UID ? SYSTEM_UID : rec.uid; + + if (sourceUid <= 0 && workSource == null) { + // Try and derive a UID to attribute things to based on the caller. + if (sender != null) { + if (!(sender instanceof PendingIntentRecord)) { + return; } - BatteryStatsImpl.Uid.Pkg pkg = - stats.getPackageStatsLocked(sourceUid >= 0 ? sourceUid : uid, - sourcePkg != null ? sourcePkg : rec.key.packageName); - pkg.noteWakeupAlarmLocked(tag); - StatsLog.write(StatsLog.WAKEUP_ALARM_OCCURRED, sourceUid >= 0 ? sourceUid : uid, - tag); + + final PendingIntentRecord rec = (PendingIntentRecord) sender; + final int callerUid = Binder.getCallingUid(); + sourceUid = rec.uid == callerUid ? SYSTEM_UID : rec.uid; + } else { + // TODO(narayan): Should we throw an exception in this case ? It means that we + // haven't been able to derive a UID to attribute things to. + return; } } + + if (DEBUG_POWER) { + Slog.w(TAG, "noteWakupAlarm[ sourcePkg=" + sourcePkg + ", sourceUid=" + sourceUid + + ", workSource=" + workSource + ", tag=" + tag + "]"); + } + + mBatteryStatsService.noteWakupAlarm(sourcePkg, sourceUid, workSource, tag); } - public void noteAlarmStart(IIntentSender sender, int sourceUid, String tag) { - if (sender != null && !(sender instanceof PendingIntentRecord)) { - return; + @Override + public void noteAlarmStart(IIntentSender sender, WorkSource workSource, int sourceUid, + String tag) { + if (workSource != null && workSource.isEmpty()) { + workSource = null; } - final PendingIntentRecord rec = (PendingIntentRecord)sender; - final BatteryStatsImpl stats = mBatteryStatsService.getActiveStatistics(); - synchronized (stats) { - mBatteryStatsService.enforceCallingPermission(); - int MY_UID = Binder.getCallingUid(); - final int uid; - if (sender == null) { - uid = sourceUid; + + if (sourceUid <= 0 && workSource == null) { + // Try and derive a UID to attribute things to based on the caller. + if (sender != null) { + if (!(sender instanceof PendingIntentRecord)) { + return; + } + + final PendingIntentRecord rec = (PendingIntentRecord) sender; + final int callerUid = Binder.getCallingUid(); + sourceUid = rec.uid == callerUid ? SYSTEM_UID : rec.uid; } else { - uid = rec.uid == MY_UID ? SYSTEM_UID : rec.uid; + // TODO(narayan): Should we throw an exception in this case ? It means that we + // haven't been able to derive a UID to attribute things to. + return; } - mBatteryStatsService.noteAlarmStart(tag, sourceUid >= 0 ? sourceUid : uid); } + + if (DEBUG_POWER) { + Slog.w(TAG, "noteAlarmStart[sourceUid=" + sourceUid + ", workSource=" + workSource + + ", tag=" + tag + "]"); + } + + mBatteryStatsService.noteAlarmStart(tag, workSource, sourceUid); } - public void noteAlarmFinish(IIntentSender sender, int sourceUid, String tag) { - if (sender != null && !(sender instanceof PendingIntentRecord)) { - return; + @Override + public void noteAlarmFinish(IIntentSender sender, WorkSource workSource, int sourceUid, + String tag) { + if (workSource != null && workSource.isEmpty()) { + workSource = null; } - final PendingIntentRecord rec = (PendingIntentRecord)sender; - final BatteryStatsImpl stats = mBatteryStatsService.getActiveStatistics(); - synchronized (stats) { - mBatteryStatsService.enforceCallingPermission(); - int MY_UID = Binder.getCallingUid(); - final int uid; - if (sender == null) { - uid = sourceUid; + + if (sourceUid <= 0 && workSource == null) { + // Try and derive a UID to attribute things to based on the caller. + if (sender != null) { + if (!(sender instanceof PendingIntentRecord)) { + return; + } + + final PendingIntentRecord rec = (PendingIntentRecord) sender; + final int callerUid = Binder.getCallingUid(); + sourceUid = rec.uid == callerUid ? SYSTEM_UID : rec.uid; } else { - uid = rec.uid == MY_UID ? SYSTEM_UID : rec.uid; + // TODO(narayan): Should we throw an exception in this case ? It means that we + // haven't been able to derive a UID to attribute things to. + return; } - mBatteryStatsService.noteAlarmFinish(tag, sourceUid >= 0 ? sourceUid : uid); } + + if (DEBUG_POWER) { + Slog.w(TAG, "noteAlarmFinish[sourceUid=" + sourceUid + ", workSource=" + workSource + + ", tag=" + tag + "]"); + } + + mBatteryStatsService.noteAlarmFinish(tag, workSource, sourceUid); } public boolean killPids(int[] pids, String pReason, boolean secure) { diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java index 35318f655bc4..430320a58e9c 100644 --- a/services/core/java/com/android/server/am/BatteryStatsService.java +++ b/services/core/java/com/android/server/am/BatteryStatsService.java @@ -38,6 +38,7 @@ import android.os.SystemClock; import android.os.UserHandle; import android.os.UserManagerInternal; import android.os.WorkSource; +import android.os.WorkSource.WorkChain; import android.os.connectivity.CellularBatteryStats; import android.os.health.HealthStatsParceler; import android.os.health.HealthStatsWriter; @@ -66,6 +67,7 @@ import java.nio.CharBuffer; import java.nio.charset.CharsetDecoder; import java.nio.charset.CodingErrorAction; import java.nio.charset.StandardCharsets; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.concurrent.ExecutionException; @@ -446,17 +448,24 @@ public final class BatteryStatsService extends IBatteryStats.Stub } } - public void noteAlarmStart(String name, int uid) { + public void noteWakupAlarm(String name, int uid, WorkSource workSource, String tag) { enforceCallingPermission(); synchronized (mStats) { - mStats.noteAlarmStartLocked(name, uid); + mStats.noteWakupAlarmLocked(name, uid, workSource, tag); } } - public void noteAlarmFinish(String name, int uid) { + public void noteAlarmStart(String name, WorkSource workSource, int uid) { enforceCallingPermission(); synchronized (mStats) { - mStats.noteAlarmFinishLocked(name, uid); + mStats.noteAlarmStartLocked(name, workSource, uid); + } + } + + public void noteAlarmFinish(String name, WorkSource workSource, int uid) { + enforceCallingPermission(); + synchronized (mStats) { + mStats.noteAlarmFinishLocked(name, workSource, uid); } } diff --git a/services/core/java/com/android/server/am/ClientLifecycleManager.java b/services/core/java/com/android/server/am/ClientLifecycleManager.java index 1e7080980d9e..014f7086efa3 100644 --- a/services/core/java/com/android/server/am/ClientLifecycleManager.java +++ b/services/core/java/com/android/server/am/ClientLifecycleManager.java @@ -21,7 +21,6 @@ import android.app.IApplicationThread; import android.app.servertransaction.ClientTransaction; import android.app.servertransaction.ClientTransactionItem; import android.app.servertransaction.ActivityLifecycleItem; -import android.os.Binder; import android.os.IBinder; import android.os.RemoteException; @@ -44,12 +43,8 @@ class ClientLifecycleManager { */ void scheduleTransaction(ClientTransaction transaction) throws RemoteException { transaction.schedule(); - if (!(transaction.getClient() instanceof Binder)) { - // If client is not an instance of Binder - it's a remote call and at this point it is - // safe to recycle the object. All objects used for local calls will be recycled after - // the transaction is executed on client in ActivityThread. - transaction.recycle(); - } + // TODO: b/70616950 + //transaction.recycle(); } /** diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 6e7b43ef00f3..799f2a92bc33 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -2251,12 +2251,15 @@ public class AudioService extends IAudioService.Stub if (DEBUG_VOL) { Log.d(TAG, String.format("Mic mute %s, user=%d", on, userId)); } - // If mute is for current user actually mute, else just persist the setting - // which will be loaded on user switch. + // only mute for the current user if (getCurrentUserId() == userId) { + final boolean currentMute = AudioSystem.isMicrophoneMuted(); AudioSystem.muteMicrophone(on); + if (on != currentMute) { + mContext.sendBroadcast(new Intent(AudioManager.ACTION_MICROPHONE_MUTE_CHANGED) + .setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY)); + } } - // Post a persist microphone msg. } @Override diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java index 7715727f6d73..c7a43153c0aa 100644 --- a/services/core/java/com/android/server/connectivity/Vpn.java +++ b/services/core/java/com/android/server/connectivity/Vpn.java @@ -305,6 +305,7 @@ public class Vpn { } else { for (Network underlying : underlyingNetworks) { final NetworkCapabilities underlyingCaps = cm.getNetworkCapabilities(underlying); + if (underlyingCaps == null) continue; for (int underlyingType : underlyingCaps.getTransportTypes()) { transportTypes = ArrayUtils.appendInt(transportTypes, underlyingType); } diff --git a/services/core/java/com/android/server/location/ContextHubServiceUtil.java b/services/core/java/com/android/server/location/ContextHubServiceUtil.java index c356b639deba..033437a53891 100644 --- a/services/core/java/com/android/server/location/ContextHubServiceUtil.java +++ b/services/core/java/com/android/server/location/ContextHubServiceUtil.java @@ -221,7 +221,7 @@ import java.util.ArrayList; case Result.NOT_INIT: return ContextHubTransaction.RESULT_FAILED_UNINITIALIZED; case Result.TRANSACTION_PENDING: - return ContextHubTransaction.RESULT_FAILED_PENDING; + return ContextHubTransaction.RESULT_FAILED_BUSY; case Result.TRANSACTION_FAILED: case Result.UNKNOWN_FAILURE: default: /* fall through */ diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java index 92754970dcc2..02218ffc14ea 100644 --- a/services/core/java/com/android/server/locksettings/LockSettingsService.java +++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java @@ -1581,6 +1581,8 @@ public class LockSettingsService extends ILockSettings.Stub { userId, progressCallback); // The user employs synthetic password based credential. if (response != null) { + mRecoverableKeyStoreManager.lockScreenSecretAvailable(credentialType, credential, + userId); return response; } @@ -1705,6 +1707,9 @@ public class LockSettingsService extends ILockSettings.Stub { /* TODO(roosa): keep the same password quality */, userId); if (!hasChallenge) { notifyActivePasswordMetricsAvailable(credential, userId); + // Use credentials to create recoverable keystore snapshot. + mRecoverableKeyStoreManager.lockScreenSecretAvailable( + storedHash.type, credential, userId); return VerifyCredentialResponse.OK; } // Fall through to get the auth token. Technically this should never happen, @@ -2021,8 +2026,9 @@ public class LockSettingsService extends ILockSettings.Stub { } @Override - public Map<String, byte[]> recoverKeys(@NonNull String sessionId, @NonNull byte[] recoveryKeyBlob, - @NonNull List<KeyEntryRecoveryData> applicationKeys, @UserIdInt int userId) + public Map<String, byte[]> recoverKeys(@NonNull String sessionId, + @NonNull byte[] recoveryKeyBlob, @NonNull List<KeyEntryRecoveryData> applicationKeys, + @UserIdInt int userId) throws RemoteException { return mRecoverableKeyStoreManager.recoverKeys( sessionId, recoveryKeyBlob, applicationKeys, userId); diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java index 95f5cb7ae112..ef4dbf57430f 100644 --- a/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java @@ -88,7 +88,8 @@ public class PlatformKeyManager { * * @hide */ - public static PlatformKeyManager getInstance(Context context, RecoverableKeyStoreDb database, int userId) + public static PlatformKeyManager getInstance(Context context, RecoverableKeyStoreDb database, + int userId) throws KeyStoreException, NoSuchAlgorithmException, InsecureUserException { context = context.getApplicationContext(); PlatformKeyManager keyManager = new PlatformKeyManager( @@ -115,16 +116,12 @@ public class PlatformKeyManager { /** * Returns the current generation ID of the platform key. This increments whenever a platform * key has to be replaced. (e.g., because the user has removed and then re-added their lock - * screen). + * screen). Returns -1 if no key has been generated yet. * * @hide */ public int getGenerationId() { - int generationId = mDatabase.getPlatformKeyGenerationId(mUserId); - if (generationId == -1) { - return 1; - } - return generationId; + return mDatabase.getPlatformKeyGenerationId(mUserId); } /** @@ -207,14 +204,22 @@ public class PlatformKeyManager { Locale.US, "Platform key generation %d exists already.", generationId)); return; } - if (generationId == 1) { + if (generationId == -1) { Log.i(TAG, "Generating initial platform ID."); } else { Log.w(TAG, String.format(Locale.US, "Platform generation ID was %d but no " + "entry was present in AndroidKeyStore. Generating fresh key.", generationId)); } + if (generationId == -1) { + generationId = 1; + } else { + // Had to generate a fresh key, bump the generation id + generationId++; + } + generateAndLoadKey(generationId); + mDatabase.setPlatformKeyGenerationId(mUserId, generationId); } /** diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java index fe1cad4b18ac..eccf241dd47f 100644 --- a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java @@ -216,7 +216,6 @@ public class RecoverableKeyStoreManager { // Any application should be able to check status for its own keys. // If caller is a recovery agent it can check statuses for other packages, but // only for recoverable keys it manages. - checkRecoverKeyStorePermission(); return mDatabase.getStatusForAllKeys(Binder.getCallingUid()); } diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDb.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDb.java index 838311e185e8..5ca5da4ead56 100644 --- a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDb.java +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDb.java @@ -590,6 +590,7 @@ public class RecoverableKeyStoreDb { * * @hide */ + @Nullable public Long getServerParameters(int userId, int uid) { SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase(); diff --git a/services/core/java/com/android/server/net/watchlist/NetworkWatchlistService.java b/services/core/java/com/android/server/net/watchlist/NetworkWatchlistService.java index 171703ac8933..f35e6ec92dae 100644 --- a/services/core/java/com/android/server/net/watchlist/NetworkWatchlistService.java +++ b/services/core/java/com/android/server/net/watchlist/NetworkWatchlistService.java @@ -33,6 +33,7 @@ import android.text.TextUtils; import android.util.Slog; import com.android.internal.R; +import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.DumpUtils; import com.android.internal.net.INetworkWatchlistManager; @@ -92,6 +93,7 @@ public class NetworkWatchlistService extends INetworkWatchlistManager.Stub { } } + @GuardedBy("mLoggingSwitchLock") private volatile boolean mIsLoggingEnabled = false; private final Object mLoggingSwitchLock = new Object(); @@ -220,36 +222,11 @@ public class NetworkWatchlistService extends INetworkWatchlistManager.Stub { } } - /** - * Set a new network watchlist. - * This method should be called by ConfigUpdater only. - * - * @return True if network watchlist is updated. - */ - public boolean setNetworkSecurityWatchlist(List<byte[]> domainsCrc32Digests, - List<byte[]> domainsSha256Digests, - List<byte[]> ipAddressesCrc32Digests, - List<byte[]> ipAddressesSha256Digests) { - Slog.i(TAG, "Setting network watchlist"); - if (domainsCrc32Digests == null || domainsSha256Digests == null - || ipAddressesCrc32Digests == null || ipAddressesSha256Digests == null) { - Slog.e(TAG, "Parameters cannot be null"); - return false; - } - if (domainsCrc32Digests.size() != domainsSha256Digests.size() - || ipAddressesCrc32Digests.size() != ipAddressesSha256Digests.size()) { - Slog.e(TAG, "Must need to have the same number of CRC32 and SHA256 digests"); - return false; - } - if (domainsSha256Digests.size() + ipAddressesSha256Digests.size() - > MAX_NUM_OF_WATCHLIST_DIGESTS) { - Slog.e(TAG, "Total watchlist size cannot exceed " + MAX_NUM_OF_WATCHLIST_DIGESTS); - return false; - } - mSettings.writeSettingsToDisk(domainsCrc32Digests, domainsSha256Digests, - ipAddressesCrc32Digests, ipAddressesSha256Digests); - Slog.i(TAG, "Set network watchlist: Success"); - return true; + @Override + public void reloadWatchlist() throws RemoteException { + enforceWatchlistLoggingPermission(); + Slog.i(TAG, "Reloading watchlist"); + mSettings.reloadSettings(); } @Override diff --git a/services/core/java/com/android/server/net/watchlist/WatchlistReportDbHelper.java b/services/core/java/com/android/server/net/watchlist/WatchlistReportDbHelper.java index f48463f5ae63..838aa53938fa 100644 --- a/services/core/java/com/android/server/net/watchlist/WatchlistReportDbHelper.java +++ b/services/core/java/com/android/server/net/watchlist/WatchlistReportDbHelper.java @@ -21,10 +21,12 @@ import android.content.Context; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; +import android.os.Environment; import android.util.Pair; import com.android.internal.util.HexDump; +import java.io.File; import java.util.ArrayList; import java.util.GregorianCalendar; import java.util.HashMap; @@ -83,9 +85,12 @@ class WatchlistReportDbHelper extends SQLiteOpenHelper { HashMap<String, String> appDigestCNCList; } + static File getSystemWatchlistDbFile() { + return new File(Environment.getDataSystemDirectory(), NAME); + } + private WatchlistReportDbHelper(Context context) { - super(context, WatchlistSettings.getSystemWatchlistFile(NAME).getAbsolutePath(), - null, VERSION); + super(context, getSystemWatchlistDbFile().getAbsolutePath(), null, VERSION); // Memory optimization - close idle connections after 30s of inactivity setIdleConnectionTimeout(IDLE_CONNECTION_TIMEOUT_MS); } diff --git a/services/core/java/com/android/server/net/watchlist/WatchlistSettings.java b/services/core/java/com/android/server/net/watchlist/WatchlistSettings.java index c50f0d56c992..70002ea21aff 100644 --- a/services/core/java/com/android/server/net/watchlist/WatchlistSettings.java +++ b/services/core/java/com/android/server/net/watchlist/WatchlistSettings.java @@ -19,8 +19,10 @@ package com.android.server.net.watchlist; import android.os.Environment; import android.util.AtomicFile; import android.util.Log; +import android.util.Slog; import android.util.Xml; +import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.FastXmlSerializer; import com.android.internal.util.HexDump; @@ -51,10 +53,9 @@ import java.util.zip.CRC32; class WatchlistSettings { private static final String TAG = "WatchlistSettings"; - // Settings xml will be stored in /data/system/network_watchlist/watchlist_settings.xml - static final String SYSTEM_WATCHLIST_DIR = "network_watchlist"; - - private static final String WATCHLIST_XML_FILE = "watchlist_settings.xml"; + // Watchlist config that pushed by ConfigUpdater. + private static final String NETWORK_WATCHLIST_DB_PATH = + "/data/misc/network_watchlist/network_watchlist.xml"; private static class XmlTags { private static final String WATCHLIST_SETTINGS = "watchlist-settings"; @@ -65,86 +66,74 @@ class WatchlistSettings { private static final String HASH = "hash"; } - private static WatchlistSettings sInstance = new WatchlistSettings(); + private static class CrcShaDigests { + final HarmfulDigests crc32Digests; + final HarmfulDigests sha256Digests; + + public CrcShaDigests(HarmfulDigests crc32Digests, HarmfulDigests sha256Digests) { + this.crc32Digests = crc32Digests; + this.sha256Digests = sha256Digests; + } + } + + private final static WatchlistSettings sInstance = new WatchlistSettings(); private final AtomicFile mXmlFile; - private final Object mLock = new Object(); - private HarmfulDigests mCrc32DomainDigests = new HarmfulDigests(new ArrayList<>()); - private HarmfulDigests mSha256DomainDigests = new HarmfulDigests(new ArrayList<>()); - private HarmfulDigests mCrc32IpDigests = new HarmfulDigests(new ArrayList<>()); - private HarmfulDigests mSha256IpDigests = new HarmfulDigests(new ArrayList<>()); - public static synchronized WatchlistSettings getInstance() { + private volatile CrcShaDigests mDomainDigests; + private volatile CrcShaDigests mIpDigests; + + public static WatchlistSettings getInstance() { return sInstance; } private WatchlistSettings() { - this(getSystemWatchlistFile(WATCHLIST_XML_FILE)); + this(new File(NETWORK_WATCHLIST_DB_PATH)); } @VisibleForTesting protected WatchlistSettings(File xmlFile) { mXmlFile = new AtomicFile(xmlFile); - readSettingsLocked(); - } - - static File getSystemWatchlistFile(String filename) { - final File dataSystemDir = Environment.getDataSystemDirectory(); - final File systemWatchlistDir = new File(dataSystemDir, SYSTEM_WATCHLIST_DIR); - systemWatchlistDir.mkdirs(); - return new File(systemWatchlistDir, filename); + reloadSettings(); } - private void readSettingsLocked() { - synchronized (mLock) { - FileInputStream stream; - try { - stream = mXmlFile.openRead(); - } catch (FileNotFoundException e) { - Log.i(TAG, "No watchlist settings: " + mXmlFile.getBaseFile().getAbsolutePath()); - return; - } + public void reloadSettings() { + try (FileInputStream stream = mXmlFile.openRead()){ final List<byte[]> crc32DomainList = new ArrayList<>(); final List<byte[]> sha256DomainList = new ArrayList<>(); final List<byte[]> crc32IpList = new ArrayList<>(); final List<byte[]> sha256IpList = new ArrayList<>(); - try { - XmlPullParser parser = Xml.newPullParser(); - parser.setInput(stream, StandardCharsets.UTF_8.name()); - parser.nextTag(); - parser.require(XmlPullParser.START_TAG, null, XmlTags.WATCHLIST_SETTINGS); - while (parser.nextTag() == XmlPullParser.START_TAG) { - String tagName = parser.getName(); - switch (tagName) { - case XmlTags.CRC32_DOMAIN: - parseHash(parser, tagName, crc32DomainList); - break; - case XmlTags.CRC32_IP: - parseHash(parser, tagName, crc32IpList); - break; - case XmlTags.SHA256_DOMAIN: - parseHash(parser, tagName, sha256DomainList); - break; - case XmlTags.SHA256_IP: - parseHash(parser, tagName, sha256IpList); - break; - default: - Log.w(TAG, "Unknown element: " + parser.getName()); - XmlUtils.skipCurrentTag(parser); - } - } - parser.require(XmlPullParser.END_TAG, null, XmlTags.WATCHLIST_SETTINGS); - writeSettingsToMemory(crc32DomainList, sha256DomainList, crc32IpList, sha256IpList); - } catch (IllegalStateException | NullPointerException | NumberFormatException | - XmlPullParserException | IOException | IndexOutOfBoundsException e) { - Log.w(TAG, "Failed parsing " + e); - } finally { - try { - stream.close(); - } catch (IOException e) { + XmlPullParser parser = Xml.newPullParser(); + parser.setInput(stream, StandardCharsets.UTF_8.name()); + parser.nextTag(); + parser.require(XmlPullParser.START_TAG, null, XmlTags.WATCHLIST_SETTINGS); + while (parser.nextTag() == XmlPullParser.START_TAG) { + String tagName = parser.getName(); + switch (tagName) { + case XmlTags.CRC32_DOMAIN: + parseHash(parser, tagName, crc32DomainList); + break; + case XmlTags.CRC32_IP: + parseHash(parser, tagName, crc32IpList); + break; + case XmlTags.SHA256_DOMAIN: + parseHash(parser, tagName, sha256DomainList); + break; + case XmlTags.SHA256_IP: + parseHash(parser, tagName, sha256IpList); + break; + default: + Log.w(TAG, "Unknown element: " + parser.getName()); + XmlUtils.skipCurrentTag(parser); } } + parser.require(XmlPullParser.END_TAG, null, XmlTags.WATCHLIST_SETTINGS); + writeSettingsToMemory(crc32DomainList, sha256DomainList, crc32IpList, sha256IpList); + Log.i(TAG, "Reload watchlist done"); + } catch (IllegalStateException | NullPointerException | NumberFormatException | + XmlPullParserException | IOException | IndexOutOfBoundsException e) { + Slog.e(TAG, "Failed parsing xml", e); } } @@ -161,101 +150,61 @@ class WatchlistSettings { } /** - * Write network watchlist settings to disk. - * Adb should not use it, should use writeSettingsToMemory directly instead. - */ - public void writeSettingsToDisk(List<byte[]> newCrc32DomainList, - List<byte[]> newSha256DomainList, - List<byte[]> newCrc32IpList, - List<byte[]> newSha256IpList) { - synchronized (mLock) { - FileOutputStream stream; - try { - stream = mXmlFile.startWrite(); - } catch (IOException e) { - Log.w(TAG, "Failed to write display settings: " + e); - return; - } - - try { - XmlSerializer out = new FastXmlSerializer(); - out.setOutput(stream, StandardCharsets.UTF_8.name()); - out.startDocument(null, true); - out.startTag(null, XmlTags.WATCHLIST_SETTINGS); - - writeHashSetToXml(out, XmlTags.SHA256_DOMAIN, newSha256DomainList); - writeHashSetToXml(out, XmlTags.SHA256_IP, newSha256IpList); - writeHashSetToXml(out, XmlTags.CRC32_DOMAIN, newCrc32DomainList); - writeHashSetToXml(out, XmlTags.CRC32_IP, newCrc32IpList); - - out.endTag(null, XmlTags.WATCHLIST_SETTINGS); - out.endDocument(); - mXmlFile.finishWrite(stream); - writeSettingsToMemory(newCrc32DomainList, newSha256DomainList, newCrc32IpList, - newSha256IpList); - } catch (IOException e) { - Log.w(TAG, "Failed to write display settings, restoring backup.", e); - mXmlFile.failWrite(stream); - } - } - } - - /** * Write network watchlist settings to memory. */ public void writeSettingsToMemory(List<byte[]> newCrc32DomainList, List<byte[]> newSha256DomainList, List<byte[]> newCrc32IpList, List<byte[]> newSha256IpList) { - synchronized (mLock) { - mCrc32DomainDigests = new HarmfulDigests(newCrc32DomainList); - mCrc32IpDigests = new HarmfulDigests(newCrc32IpList); - mSha256DomainDigests = new HarmfulDigests(newSha256DomainList); - mSha256IpDigests = new HarmfulDigests(newSha256IpList); - } - } - - private static void writeHashSetToXml(XmlSerializer out, String tagName, List<byte[]> hashSet) - throws IOException { - out.startTag(null, tagName); - for (byte[] hash : hashSet) { - out.startTag(null, XmlTags.HASH); - out.text(HexDump.toHexString(hash)); - out.endTag(null, XmlTags.HASH); - } - out.endTag(null, tagName); + mDomainDigests = new CrcShaDigests(new HarmfulDigests(newCrc32DomainList), + new HarmfulDigests(newSha256DomainList)); + mIpDigests = new CrcShaDigests(new HarmfulDigests(newCrc32IpList), + new HarmfulDigests(newSha256IpList)); } public boolean containsDomain(String domain) { + final CrcShaDigests domainDigests = mDomainDigests; + if (domainDigests == null) { + Slog.wtf(TAG, "domainDigests should not be null"); + return false; + } // First it does a quick CRC32 check. final byte[] crc32 = getCrc32(domain); - if (!mCrc32DomainDigests.contains(crc32)) { + if (!domainDigests.crc32Digests.contains(crc32)) { return false; } // Now we do a slow SHA256 check. final byte[] sha256 = getSha256(domain); - return mSha256DomainDigests.contains(sha256); + return domainDigests.sha256Digests.contains(sha256); } public boolean containsIp(String ip) { + final CrcShaDigests ipDigests = mIpDigests; + if (ipDigests == null) { + Slog.wtf(TAG, "ipDigests should not be null"); + return false; + } // First it does a quick CRC32 check. final byte[] crc32 = getCrc32(ip); - if (!mCrc32IpDigests.contains(crc32)) { + if (!ipDigests.crc32Digests.contains(crc32)) { return false; } // Now we do a slow SHA256 check. final byte[] sha256 = getSha256(ip); - return mSha256IpDigests.contains(sha256); + return ipDigests.sha256Digests.contains(sha256); } - /** Get CRC32 of a string */ + /** Get CRC32 of a string + * + * TODO: Review if we should use CRC32 or other algorithms + */ private byte[] getCrc32(String str) { final CRC32 crc = new CRC32(); crc.update(str.getBytes()); final long tmp = crc.getValue(); - return new byte[]{(byte)(tmp >> 24 & 255), (byte)(tmp >> 16 & 255), - (byte)(tmp >> 8 & 255), (byte)(tmp & 255)}; + return new byte[]{(byte) (tmp >> 24 & 255), (byte) (tmp >> 16 & 255), + (byte) (tmp >> 8 & 255), (byte) (tmp & 255)}; } /** Get SHA256 of a string */ @@ -273,12 +222,12 @@ class WatchlistSettings { public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { pw.println("Domain CRC32 digest list:"); - mCrc32DomainDigests.dump(fd, pw, args); + mDomainDigests.crc32Digests.dump(fd, pw, args); pw.println("Domain SHA256 digest list:"); - mSha256DomainDigests.dump(fd, pw, args); + mDomainDigests.sha256Digests.dump(fd, pw, args); pw.println("Ip CRC32 digest list:"); - mCrc32IpDigests.dump(fd, pw, args); + mIpDigests.crc32Digests.dump(fd, pw, args); pw.println("Ip SHA256 digest list:"); - mSha256IpDigests.dump(fd, pw, args); + mIpDigests.sha256Digests.dump(fd, pw, args); } } diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 696d89575e2d..1157af41ccec 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -163,10 +163,12 @@ import android.content.pm.PackageCleanItem; import android.content.pm.PackageInfo; import android.content.pm.PackageInfoLite; import android.content.pm.PackageInstaller; +import android.content.pm.PackageList; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.content.pm.PackageManager.LegacyPackageDeleteObserver; import android.content.pm.PackageManager.PackageInfoFlags; +import android.content.pm.PackageManagerInternal.PackageListObserver; import android.content.pm.PackageParser; import android.content.pm.PackageParser.ActivityIntentInfo; import android.content.pm.PackageParser.Package; @@ -757,6 +759,9 @@ public class PackageManagerService extends IPackageManager.Stub @GuardedBy("mPackages") final SparseArray<Map<String, Integer>> mChangedPackagesSequenceNumbers = new SparseArray<>(); + @GuardedBy("mPackages") + final private ArraySet<PackageListObserver> mPackageListObservers = new ArraySet<>(); + class PackageParserCallback implements PackageParser.Callback { @Override public final boolean hasFeature(String feature) { return PackageManagerService.this.hasSystemFeature(feature, 0); @@ -2095,6 +2100,10 @@ public class PackageManagerService extends IPackageManager.Stub } } + if (allNewUsers && !update) { + notifyPackageAdded(packageName); + } + // Log current value of "unknown sources" setting EventLog.writeEvent(EventLogTags.UNKNOWN_SOURCES_ENABLED, getUnknownSourcesSettings()); @@ -12983,6 +12992,34 @@ public class PackageManagerService extends IPackageManager.Stub }); } + @Override + public void notifyPackageAdded(String packageName) { + final PackageListObserver[] observers; + synchronized (mPackages) { + if (mPackageListObservers.size() == 0) { + return; + } + observers = (PackageListObserver[]) mPackageListObservers.toArray(); + } + for (int i = observers.length - 1; i >= 0; --i) { + observers[i].onPackageAdded(packageName); + } + } + + @Override + public void notifyPackageRemoved(String packageName) { + final PackageListObserver[] observers; + synchronized (mPackages) { + if (mPackageListObservers.size() == 0) { + return; + } + observers = (PackageListObserver[]) mPackageListObservers.toArray(); + } + for (int i = observers.length - 1; i >= 0; --i) { + observers[i].onPackageRemoved(packageName); + } + } + /** * Sends a broadcast for the given action. * <p>If {@code isInstantApp} is {@code true}, then the broadcast is protected with @@ -17640,6 +17677,7 @@ public class PackageManagerService extends IPackageManager.Stub removedPackage, extras, Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND, null, null, broadcastUsers, instantUserIds); + packageSender.notifyPackageRemoved(removedPackage); } } if (removedAppId >= 0) { @@ -20395,10 +20433,9 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName()); } } sUserManager.systemReady(); - // If we upgraded grant all default permissions before kicking off. for (int userId : grantPermissionsUserIds) { - mDefaultPermissionPolicy.grantDefaultPermissions(mPackages.values(), userId); + mDefaultPermissionPolicy.grantDefaultPermissions(userId); } if (grantPermissionsUserIds == EMPTY_INT_ARRAY) { @@ -22445,8 +22482,8 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName()); } void onNewUserCreated(final int userId) { + mDefaultPermissionPolicy.grantDefaultPermissions(userId); synchronized(mPackages) { - mDefaultPermissionPolicy.grantDefaultPermissions(mPackages.values(), userId); // If permission review for legacy apps is required, we represent // dagerous permissions for such apps as always granted runtime // permissions to keep per user flag state whether review is needed. @@ -22933,6 +22970,29 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName()); } @Override + public PackageList getPackageList(PackageListObserver observer) { + synchronized (mPackages) { + final int N = mPackages.size(); + final ArrayList<String> list = new ArrayList<>(N); + for (int i = 0; i < N; i++) { + list.add(mPackages.keyAt(i)); + } + final PackageList packageList = new PackageList(list, observer); + if (observer != null) { + mPackageListObservers.add(packageList); + } + return packageList; + } + } + + @Override + public void removePackageListObserver(PackageListObserver observer) { + synchronized (mPackages) { + mPackageListObservers.remove(observer); + } + } + + @Override public PackageParser.Package getDisabledPackage(String packageName) { synchronized (mPackages) { final PackageSetting ps = mSettings.getDisabledSystemPkgLPr(packageName); @@ -23594,4 +23654,6 @@ interface PackageSender { final IIntentReceiver finishedReceiver, final int[] userIds, int[] instantUserIds); void sendPackageAddedForNewUsers(String packageName, boolean sendBootCompleted, boolean includeStopped, int appId, int[] userIds, int[] instantUserIds); + void notifyPackageAdded(String packageName); + void notifyPackageRemoved(String packageName); } diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java index 01f3c576f72b..d38dc9ae55f0 100644 --- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java +++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java @@ -30,6 +30,7 @@ import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.IPackageManager; +import android.content.pm.PackageList; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.content.pm.PackageParser; @@ -252,11 +253,11 @@ public final class DefaultPermissionGrantPolicy { } } - public void grantDefaultPermissions(Collection<PackageParser.Package> packages, int userId) { + public void grantDefaultPermissions(int userId) { if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_EMBEDDED, 0)) { - grantAllRuntimePermissions(packages, userId); + grantAllRuntimePermissions(userId); } else { - grantPermissionsToSysComponentsAndPrivApps(packages, userId); + grantPermissionsToSysComponentsAndPrivApps(userId); grantDefaultSystemHandlerPermissions(userId); grantDefaultPermissionExceptions(userId); } @@ -278,10 +279,14 @@ public final class DefaultPermissionGrantPolicy { } } - private void grantAllRuntimePermissions( - Collection<PackageParser.Package> packages, int userId) { + private void grantAllRuntimePermissions(int userId) { Log.i(TAG, "Granting all runtime permissions for user " + userId); - for (PackageParser.Package pkg : packages) { + final PackageList packageList = mServiceInternal.getPackageList(); + for (String packageName : packageList.getPackageNames()) { + final PackageParser.Package pkg = mServiceInternal.getPackage(packageName); + if (pkg == null) { + continue; + } grantRuntimePermissionsForPackage(userId, pkg); } } @@ -290,10 +295,14 @@ public final class DefaultPermissionGrantPolicy { mHandler.sendEmptyMessage(MSG_READ_DEFAULT_PERMISSION_EXCEPTIONS); } - private void grantPermissionsToSysComponentsAndPrivApps( - Collection<PackageParser.Package> packages, int userId) { + private void grantPermissionsToSysComponentsAndPrivApps(int userId) { Log.i(TAG, "Granting permissions to platform components for user " + userId); - for (PackageParser.Package pkg : packages) { + final PackageList packageList = mServiceInternal.getPackageList(); + for (String packageName : packageList.getPackageNames()) { + final PackageParser.Package pkg = mServiceInternal.getPackage(packageName); + if (pkg == null) { + continue; + } if (!isSysComponentOrPersistentPlatformSignedPrivApp(pkg) || !doesPackageSupportRuntimePermissions(pkg) || pkg.requestedPermissions.isEmpty()) { diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java index 02c8f681e7c4..bc7f2e691e86 100644 --- a/services/core/java/com/android/server/power/PowerManagerService.java +++ b/services/core/java/com/android/server/power/PowerManagerService.java @@ -57,6 +57,7 @@ import android.os.Trace; import android.os.UserHandle; import android.os.UserManager; import android.os.WorkSource; +import android.os.WorkSource.WorkChain; import android.provider.Settings; import android.provider.Settings.SettingNotFoundException; import android.service.dreams.DreamManagerInternal; @@ -1976,6 +1977,16 @@ public final class PowerManagerService extends SystemService return true; } } + + final ArrayList<WorkChain> workChains = wakeLock.mWorkSource.getWorkChains(); + if (workChains != null) { + for (int k = 0; k < workChains.size(); k++) { + final int uid = workChains.get(k).getAttributionUid(); + if (userId == UserHandle.getUserId(uid)) { + return true; + } + } + } } return userId == UserHandle.getUserId(wakeLock.mOwnerUid); } diff --git a/services/core/java/com/android/server/updates/NetworkWatchlistInstallReceiver.java b/services/core/java/com/android/server/updates/NetworkWatchlistInstallReceiver.java new file mode 100644 index 000000000000..3b7ddc2e803f --- /dev/null +++ b/services/core/java/com/android/server/updates/NetworkWatchlistInstallReceiver.java @@ -0,0 +1,40 @@ +/* + * Copyright 2017 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.updates; + +import android.content.Context; +import android.content.Intent; +import android.net.NetworkWatchlistManager; +import android.os.RemoteException; +import android.util.Slog; + +public class NetworkWatchlistInstallReceiver extends ConfigUpdateInstallReceiver { + + public NetworkWatchlistInstallReceiver() { + super("/data/misc/network_watchlist/", "network_watchlist.xml", "metadata/", "version"); + } + + @Override + protected void postInstall(Context context, Intent intent) { + try { + context.getSystemService(NetworkWatchlistManager.class).reloadWatchlist(); + } catch (Exception e) { + // Network Watchlist is not available + Slog.wtf("NetworkWatchlistInstallReceiver", "Unable to reload watchlist"); + } + } +} diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java index b24a8cac3cef..44d7948b12b6 100644 --- a/services/core/java/com/android/server/wm/AppWindowToken.java +++ b/services/core/java/com/android/server/wm/AppWindowToken.java @@ -211,7 +211,7 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree private int mTransitFlags; /** Whether our surface was set to be showing in the last call to {@link #prepareSurfaces} */ - private boolean mLastSurfaceShowing; + private boolean mLastSurfaceShowing = true; private AppWindowThumbnail mThumbnail; diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index dfb385bddcdf..863922f60aae 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -2222,12 +2222,13 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP mWinAnimator + ": " + mPolicyVisibilityAfterAnim); } mPolicyVisibility = mPolicyVisibilityAfterAnim; - setDisplayLayoutNeeded(); if (!mPolicyVisibility) { + mWinAnimator.hide("checkPolicyVisibilityChange"); if (mService.mCurrentFocus == this) { if (DEBUG_FOCUS_LIGHT) Slog.i(TAG, "setAnimationLocked: setting mFocusMayChange true"); mService.mFocusMayChange = true; + setDisplayLayoutNeeded(); } // Window is no longer visible -- make sure if we were waiting // for it to be displayed before enabling the display, that @@ -4291,8 +4292,22 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP float9[Matrix.MSKEW_Y] = mWinAnimator.mDtDx; float9[Matrix.MSKEW_X] = mWinAnimator.mDtDy; float9[Matrix.MSCALE_Y] = mWinAnimator.mDsDy; - float9[Matrix.MTRANS_X] = mSurfacePosition.x + mShownPosition.x; - float9[Matrix.MTRANS_Y] = mSurfacePosition.y + mShownPosition.y; + int x = mSurfacePosition.x + mShownPosition.x; + int y = mSurfacePosition.y + mShownPosition.y; + + // If changed, also adjust transformFrameToSurfacePosition + final WindowContainer parent = getParent(); + if (isChildWindow()) { + final WindowState parentWindow = getParentWindow(); + x += parentWindow.mFrame.left - parentWindow.mAttrs.surfaceInsets.left; + y += parentWindow.mFrame.top - parentWindow.mAttrs.surfaceInsets.top; + } else if (parent != null) { + final Rect parentBounds = parent.getBounds(); + x += parentBounds.left; + y += parentBounds.top; + } + float9[Matrix.MTRANS_X] = x; + float9[Matrix.MTRANS_Y] = y; float9[Matrix.MPERSP_0] = 0; float9[Matrix.MPERSP_1] = 0; float9[Matrix.MPERSP_2] = 1; @@ -4439,6 +4454,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP private void transformFrameToSurfacePosition(int left, int top, Point outPoint) { outPoint.set(left, top); + + // If changed, also adjust getTransformationMatrix final WindowContainer parentWindowContainer = getParent(); if (isChildWindow()) { // TODO: This probably falls apart at some point and we should diff --git a/services/robotests/src/com/android/server/backup/BackupManagerConstantsTest.java b/services/robotests/src/com/android/server/backup/BackupManagerConstantsTest.java new file mode 100644 index 000000000000..c20c37678b24 --- /dev/null +++ b/services/robotests/src/com/android/server/backup/BackupManagerConstantsTest.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2017 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.backup; + +import android.app.AlarmManager; +import android.content.Context; +import android.os.Handler; +import android.platform.test.annotations.Presubmit; +import android.provider.Settings; + +import com.android.server.backup.testing.ShadowBackupTransportStub; +import com.android.server.backup.testing.ShadowContextImplForBackup; +import com.android.server.backup.testing.ShadowPackageManagerForBackup; +import com.android.server.testing.FrameworkRobolectricTestRunner; +import com.android.server.testing.SystemLoaderClasses; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.MockitoAnnotations; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; + +import static com.google.common.truth.Truth.assertThat; + +@RunWith(FrameworkRobolectricTestRunner.class) +@Config( + manifest = Config.NONE, + sdk = 26, + shadows = { + ShadowContextImplForBackup.class, + ShadowBackupTransportStub.class, + ShadowPackageManagerForBackup.class + } +) +@SystemLoaderClasses({TransportManager.class}) +@Presubmit +public class BackupManagerConstantsTest { + private static final String PACKAGE_NAME = "some.package.name"; + private static final String ANOTHER_PACKAGE_NAME = "another.package.name"; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + } + + @After + public void tearDown() throws Exception { + } + + @Test + public void testDefaultValues() throws Exception { + final Context context = RuntimeEnvironment.application.getApplicationContext(); + final Handler handler = new Handler(); + + Settings.Secure.putString(context.getContentResolver(), + Settings.Secure.BACKUP_MANAGER_CONSTANTS, null); + + final BackupManagerConstants constants = + new BackupManagerConstants(handler, context.getContentResolver()); + constants.start(); + + assertThat(constants.getKeyValueBackupIntervalMilliseconds()) + .isEqualTo(4 * AlarmManager.INTERVAL_HOUR); + assertThat(constants.getKeyValueBackupFuzzMilliseconds()).isEqualTo(10 * 60 * 1000); + assertThat(constants.getKeyValueBackupRequireCharging()).isEqualTo(true); + assertThat(constants.getKeyValueBackupRequiredNetworkType()).isEqualTo(1); + + assertThat(constants.getFullBackupIntervalMilliseconds()) + .isEqualTo(24 * AlarmManager.INTERVAL_HOUR); + assertThat(constants.getFullBackupRequireCharging()).isEqualTo(true); + assertThat(constants.getFullBackupRequiredNetworkType()).isEqualTo(2); + assertThat(constants.getBackupFinishedNotificationReceivers()).isEqualTo(new String[0]); + } + + @Test + public void testParseNotificationReceivers() throws Exception { + final Context context = RuntimeEnvironment.application.getApplicationContext(); + final Handler handler = new Handler(); + + final String recievers_setting = "backup_finished_notification_receivers=" + + PACKAGE_NAME + ':' + ANOTHER_PACKAGE_NAME; + Settings.Secure.putString(context.getContentResolver(), + Settings.Secure.BACKUP_MANAGER_CONSTANTS, recievers_setting); + + final BackupManagerConstants constants = + new BackupManagerConstants(handler, context.getContentResolver()); + constants.start(); + + assertThat(constants.getBackupFinishedNotificationReceivers()).isEqualTo(new String[] { + PACKAGE_NAME, + ANOTHER_PACKAGE_NAME}); + } +} diff --git a/services/robotests/src/com/android/server/backup/TransportManagerTest.java b/services/robotests/src/com/android/server/backup/TransportManagerTest.java index ced9b1ec8042..82830fe5c479 100644 --- a/services/robotests/src/com/android/server/backup/TransportManagerTest.java +++ b/services/robotests/src/com/android/server/backup/TransportManagerTest.java @@ -18,8 +18,6 @@ package com.android.server.backup; import static com.google.common.truth.Truth.assertThat; -import static junit.framework.Assert.fail; - import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import static org.robolectric.shadow.api.Shadow.extract; @@ -58,7 +56,6 @@ import org.robolectric.annotation.Config; import org.robolectric.shadows.ShadowLog; import org.robolectric.shadows.ShadowLooper; import org.robolectric.shadows.ShadowPackageManager; -import org.testng.Assert.ThrowingRunnable; import java.util.ArrayList; import java.util.Arrays; diff --git a/services/robotests/src/com/android/server/backup/internal/PerformInitializeTaskTest.java b/services/robotests/src/com/android/server/backup/internal/PerformInitializeTaskTest.java new file mode 100644 index 000000000000..73f1c2fbda0a --- /dev/null +++ b/services/robotests/src/com/android/server/backup/internal/PerformInitializeTaskTest.java @@ -0,0 +1,411 @@ +/* + * Copyright (C) 2017 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.backup.internal; + +import static android.app.backup.BackupTransport.TRANSPORT_ERROR; +import static android.app.backup.BackupTransport.TRANSPORT_OK; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.annotation.Nullable; +import android.app.AlarmManager; +import android.app.Application; +import android.app.PendingIntent; +import android.app.backup.IBackupObserver; +import android.os.DeadObjectException; +import android.platform.test.annotations.Presubmit; + +import com.android.internal.backup.IBackupTransport; +import com.android.server.backup.RefactoredBackupManagerService; +import com.android.server.backup.TransportManager; +import com.android.server.backup.transport.TransportClient; +import com.android.server.backup.transport.TransportNotAvailableException; +import com.android.server.testing.FrameworkRobolectricTestRunner; +import com.android.server.testing.SystemLoaderClasses; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; + +import java.io.File; +import java.util.Arrays; +import java.util.List; + +@RunWith(FrameworkRobolectricTestRunner.class) +@Config(manifest = Config.NONE, sdk = 26) +@SystemLoaderClasses({PerformInitializeTaskTest.class, TransportManager.class}) +@Presubmit +public class PerformInitializeTaskTest { + private static final String[] TRANSPORT_NAMES = { + "android/com.android.internal.backup.LocalTransport", + "com.google.android.gms/.backup.migrate.service.D2dTransport", + "com.google.android.gms/.backup.BackupTransportService" + }; + + private static final String TRANSPORT_NAME = TRANSPORT_NAMES[0]; + + @Mock private RefactoredBackupManagerService mBackupManagerService; + @Mock private TransportManager mTransportManager; + @Mock private OnTaskFinishedListener mListener; + @Mock private IBackupTransport mTransport; + @Mock private IBackupObserver mObserver; + @Mock private AlarmManager mAlarmManager; + @Mock private PendingIntent mRunInitIntent; + private File mBaseStateDir; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + + Application context = RuntimeEnvironment.application; + mBaseStateDir = new File(context.getCacheDir(), "base_state_dir"); + assertThat(mBaseStateDir.mkdir()).isTrue(); + + when(mBackupManagerService.getAlarmManager()).thenReturn(mAlarmManager); + when(mBackupManagerService.getRunInitIntent()).thenReturn(mRunInitIntent); + } + + @Test + public void testRun_callsTransportCorrectly() throws Exception { + setUpTransport(TRANSPORT_NAME); + configureTransport(mTransport, TRANSPORT_OK, TRANSPORT_OK); + PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME); + + performInitializeTask.run(); + + verify(mTransport).initializeDevice(); + verify(mTransport).finishBackup(); + } + + @Test + public void testRun_callsBackupManagerCorrectly() throws Exception { + setUpTransport(TRANSPORT_NAME); + configureTransport(mTransport, TRANSPORT_OK, TRANSPORT_OK); + PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME); + + performInitializeTask.run(); + + verify(mBackupManagerService) + .recordInitPending(false, TRANSPORT_NAME, dirName(TRANSPORT_NAME)); + verify(mBackupManagerService) + .resetBackupState(eq(new File(mBaseStateDir, dirName(TRANSPORT_NAME)))); + } + + @Test + public void testRun_callsObserverAndListenerCorrectly() throws Exception { + setUpTransport(TRANSPORT_NAME); + configureTransport(mTransport, TRANSPORT_OK, TRANSPORT_OK); + PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME); + + performInitializeTask.run(); + + verify(mObserver).onResult(eq(TRANSPORT_NAME), eq(TRANSPORT_OK)); + verify(mObserver).backupFinished(eq(TRANSPORT_OK)); + verify(mListener).onFinished(any()); + } + + @Test + public void testRun_whenInitializeDeviceFails() throws Exception { + setUpTransport(TRANSPORT_NAME); + configureTransport(mTransport, TRANSPORT_ERROR, 0); + PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME); + + performInitializeTask.run(); + + verify(mTransport).initializeDevice(); + verify(mTransport, never()).finishBackup(); + verify(mBackupManagerService) + .recordInitPending(true, TRANSPORT_NAME, dirName(TRANSPORT_NAME)); + } + + @Test + public void testRun_whenInitializeDeviceFails_callsObserverAndListenerCorrectly() + throws Exception { + setUpTransport(TRANSPORT_NAME); + configureTransport(mTransport, TRANSPORT_ERROR, 0); + PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME); + + performInitializeTask.run(); + + verify(mObserver).onResult(eq(TRANSPORT_NAME), eq(TRANSPORT_ERROR)); + verify(mObserver).backupFinished(eq(TRANSPORT_ERROR)); + verify(mListener).onFinished(any()); + } + + @Test + public void testRun_whenInitializeDeviceFails_schedulesAlarm() throws Exception { + setUpTransport(TRANSPORT_NAME); + configureTransport(mTransport, TRANSPORT_ERROR, 0); + PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME); + + performInitializeTask.run(); + + verify(mAlarmManager).set(anyInt(), anyLong(), eq(mRunInitIntent)); + } + + @Test + public void testRun_whenFinishBackupFails() throws Exception { + setUpTransport(TRANSPORT_NAME); + configureTransport(mTransport, TRANSPORT_OK, TRANSPORT_ERROR); + PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME); + + performInitializeTask.run(); + + verify(mTransport).initializeDevice(); + verify(mTransport).finishBackup(); + verify(mBackupManagerService) + .recordInitPending(true, TRANSPORT_NAME, dirName(TRANSPORT_NAME)); + } + + @Test + public void testRun_whenFinishBackupFails_callsObserverAndListenerCorrectly() throws Exception { + setUpTransport(TRANSPORT_NAME); + configureTransport(mTransport, TRANSPORT_OK, TRANSPORT_ERROR); + PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME); + + performInitializeTask.run(); + + verify(mObserver).onResult(eq(TRANSPORT_NAME), eq(TRANSPORT_ERROR)); + verify(mObserver).backupFinished(eq(TRANSPORT_ERROR)); + verify(mListener).onFinished(any()); + } + + @Test + public void testRun_whenFinishBackupFails_schedulesAlarm() throws Exception { + setUpTransport(TRANSPORT_NAME); + configureTransport(mTransport, TRANSPORT_OK, TRANSPORT_ERROR); + PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME); + + performInitializeTask.run(); + + verify(mAlarmManager).set(anyInt(), anyLong(), eq(mRunInitIntent)); + } + + @Test + public void testRun_whenOnlyOneTransportFails() throws Exception { + List<TransportData> transports = setUpTransports(TRANSPORT_NAMES[0], TRANSPORT_NAMES[1]); + configureTransport(transports.get(0).transportMock, TRANSPORT_ERROR, 0); + configureTransport(transports.get(1).transportMock, TRANSPORT_OK, TRANSPORT_OK); + PerformInitializeTask performInitializeTask = + createPerformInitializeTask(TRANSPORT_NAMES[0], TRANSPORT_NAMES[1]); + + performInitializeTask.run(); + + verify(transports.get(1).transportMock).initializeDevice(); + verify(mObserver).onResult(eq(TRANSPORT_NAMES[0]), eq(TRANSPORT_ERROR)); + verify(mObserver).onResult(eq(TRANSPORT_NAMES[1]), eq(TRANSPORT_OK)); + verify(mObserver).backupFinished(eq(TRANSPORT_ERROR)); + } + + @Test + public void testRun_withMultipleTransports() throws Exception { + List<TransportData> transports = setUpTransports(TRANSPORT_NAMES); + configureTransport(transports.get(0).transportMock, TRANSPORT_OK, TRANSPORT_OK); + configureTransport(transports.get(1).transportMock, TRANSPORT_OK, TRANSPORT_OK); + configureTransport(transports.get(2).transportMock, TRANSPORT_OK, TRANSPORT_OK); + PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAMES); + + performInitializeTask.run(); + + for (TransportData transport : transports) { + verify(mTransportManager).getTransportClient(eq(transport.transportName), any()); + verify(mTransportManager) + .disposeOfTransportClient(eq(transport.transportClientMock), any()); + } + } + + @Test + public void testRun_whenOnlyOneTransportFails_disposesAllTransports() throws Exception { + List<TransportData> transports = setUpTransports(TRANSPORT_NAMES[0], TRANSPORT_NAMES[1]); + configureTransport(transports.get(0).transportMock, TRANSPORT_ERROR, 0); + configureTransport(transports.get(1).transportMock, TRANSPORT_OK, TRANSPORT_OK); + PerformInitializeTask performInitializeTask = + createPerformInitializeTask(TRANSPORT_NAMES[0], TRANSPORT_NAMES[1]); + + performInitializeTask.run(); + + verify(mTransportManager) + .disposeOfTransportClient(eq(transports.get(0).transportClientMock), any()); + verify(mTransportManager) + .disposeOfTransportClient(eq(transports.get(1).transportClientMock), any()); + } + + @Test + public void testRun_whenTransportNotRegistered() throws Exception { + setUpTransport(new TransportData(TRANSPORT_NAME, null, null)); + PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME); + + performInitializeTask.run(); + + verify(mTransportManager, never()).disposeOfTransportClient(any(), any()); + verify(mObserver, never()).onResult(any(), anyInt()); + verify(mObserver).backupFinished(eq(TRANSPORT_OK)); + } + + @Test + public void testRun_whenOnlyOneTransportNotRegistered() throws Exception { + List<TransportData> transports = + setUpTransports( + new TransportData(TRANSPORT_NAMES[0], null, null), + new TransportData(TRANSPORT_NAMES[1])); + String registeredTransportName = transports.get(1).transportName; + IBackupTransport registeredTransport = transports.get(1).transportMock; + TransportClient registeredTransportClient = transports.get(1).transportClientMock; + PerformInitializeTask performInitializeTask = + createPerformInitializeTask(TRANSPORT_NAMES[0], TRANSPORT_NAMES[1]); + + performInitializeTask.run(); + + verify(registeredTransport).initializeDevice(); + verify(mTransportManager).disposeOfTransportClient(eq(registeredTransportClient), any()); + verify(mObserver).onResult(eq(registeredTransportName), eq(TRANSPORT_OK)); + } + + @Test + public void testRun_whenTransportNotAvailable() throws Exception { + TransportClient transportClient = mock(TransportClient.class); + setUpTransport(new TransportData(TRANSPORT_NAME, null, transportClient)); + PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME); + + performInitializeTask.run(); + + verify(mTransportManager).disposeOfTransportClient(eq(transportClient), any()); + verify(mObserver).backupFinished(eq(TRANSPORT_ERROR)); + verify(mListener).onFinished(any()); + } + + @Test + public void testRun_whenTransportThrowsDeadObjectException() throws Exception { + TransportClient transportClient = mock(TransportClient.class); + setUpTransport(new TransportData(TRANSPORT_NAME, mTransport, transportClient)); + when(mTransport.initializeDevice()).thenThrow(DeadObjectException.class); + PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME); + + performInitializeTask.run(); + + verify(mTransportManager).disposeOfTransportClient(eq(transportClient), any()); + verify(mObserver).backupFinished(eq(TRANSPORT_ERROR)); + verify(mListener).onFinished(any()); + } + + private PerformInitializeTask createPerformInitializeTask(String... transportNames) { + return new PerformInitializeTask( + mBackupManagerService, + mTransportManager, + transportNames, + mObserver, + mListener, + mBaseStateDir); + } + + private void configureTransport( + IBackupTransport transportMock, int initializeDeviceStatus, int finishBackupStatus) + throws Exception { + when(transportMock.initializeDevice()).thenReturn(initializeDeviceStatus); + when(transportMock.finishBackup()).thenReturn(finishBackupStatus); + } + + private List<TransportData> setUpTransports(String... transportNames) throws Exception { + return setUpTransports( + Arrays.stream(transportNames) + .map(TransportData::new) + .toArray(TransportData[]::new)); + } + + /** @see #setUpTransport(TransportData) */ + private List<TransportData> setUpTransports(TransportData... transports) throws Exception { + for (TransportData transport : transports) { + setUpTransport(transport); + } + return Arrays.asList(transports); + } + + private void setUpTransport(String transportName) throws Exception { + setUpTransport(new TransportData(transportName, mTransport, mock(TransportClient.class))); + } + + /** + * Configures transport according to {@link TransportData}: + * + * <ul> + * <li>{@link TransportData#transportMock} {@code null} means {@link + * TransportClient#connectOrThrow(String)} throws {@link TransportNotAvailableException}. + * <li>{@link TransportData#transportClientMock} {@code null} means {@link + * TransportManager#getTransportClient(String, String)} returns {@code null}. + * </ul> + */ + private void setUpTransport(TransportData transport) throws Exception { + String transportName = transport.transportName; + String transportDirName = dirName(transportName); + IBackupTransport transportMock = transport.transportMock; + TransportClient transportClientMock = transport.transportClientMock; + + if (transportMock != null) { + when(transportMock.name()).thenReturn(transportName); + when(transportMock.transportDirName()).thenReturn(transportDirName); + } + + if (transportClientMock != null) { + when(transportClientMock.getTransportDirName()).thenReturn(transportDirName); + if (transportMock != null) { + when(transportClientMock.connectOrThrow(any())).thenReturn(transportMock); + } else { + when(transportClientMock.connectOrThrow(any())) + .thenThrow(TransportNotAvailableException.class); + } + } + + when(mTransportManager.getTransportClient(eq(transportName), any())) + .thenReturn(transportClientMock); + } + + private String dirName(String transportName) { + return transportName + "_dir_name"; + } + + private static class TransportData { + private final String transportName; + @Nullable private final IBackupTransport transportMock; + @Nullable private final TransportClient transportClientMock; + + private TransportData( + String transportName, + @Nullable IBackupTransport transportMock, + @Nullable TransportClient transportClientMock) { + this.transportName = transportName; + this.transportMock = transportMock; + this.transportClientMock = transportClientMock; + } + + private TransportData(String transportName) { + this(transportName, mock(IBackupTransport.class), mock(TransportClient.class)); + } + } +} diff --git a/services/robotests/src/com/android/server/testing/FrameworkRobolectricTestRunner.java b/services/robotests/src/com/android/server/testing/FrameworkRobolectricTestRunner.java index 78ac4ed92788..6c7313ba639e 100644 --- a/services/robotests/src/com/android/server/testing/FrameworkRobolectricTestRunner.java +++ b/services/robotests/src/com/android/server/testing/FrameworkRobolectricTestRunner.java @@ -27,14 +27,9 @@ import org.robolectric.internal.bytecode.InstrumentationConfiguration; import org.robolectric.internal.bytecode.SandboxClassLoader; import org.robolectric.util.Util; -import java.io.File; -import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; -import java.io.OutputStreamWriter; -import java.io.PrintWriter; import java.net.URL; -import java.util.Arrays; import java.util.Set; import javax.annotation.Nonnull; diff --git a/services/tests/servicestests/src/com/android/server/accessibility/MagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/MagnificationGestureHandlerTest.java index 8a54c4e7f9c0..8d5556eac447 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/MagnificationGestureHandlerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/MagnificationGestureHandlerTest.java @@ -32,6 +32,7 @@ import static org.mockito.Mockito.verify; import android.annotation.NonNull; import android.content.Context; +import android.os.Message; import android.support.test.InstrumentationRegistry; import android.support.test.runner.AndroidJUnit4; import android.util.DebugUtils; @@ -48,6 +49,34 @@ import org.junit.runner.RunWith; import java.util.function.IntConsumer; +/** + * Tests the state transitions of {@link MagnificationGestureHandler} + * + * Here's a dot graph describing the transitions being tested: + * {@code + * digraph { + * IDLE -> SHORTCUT_TRIGGERED [label="a11y\nbtn"] + * SHORTCUT_TRIGGERED -> IDLE [label="a11y\nbtn"] + * IDLE -> DOUBLE_TAP [label="2tap"] + * DOUBLE_TAP -> IDLE [label="timeout"] + * DOUBLE_TAP -> TRIPLE_TAP_AND_HOLD [label="down"] + * SHORTCUT_TRIGGERED -> TRIPLE_TAP_AND_HOLD [label="down"] + * TRIPLE_TAP_AND_HOLD -> ZOOMED [label="up"] + * TRIPLE_TAP_AND_HOLD -> DRAGGING_TMP [label="hold/\nswipe"] + * DRAGGING_TMP -> IDLE [label="release"] + * ZOOMED -> ZOOMED_DOUBLE_TAP [label="2tap"] + * ZOOMED_DOUBLE_TAP -> ZOOMED [label="timeout"] + * ZOOMED_DOUBLE_TAP -> DRAGGING [label="hold"] + * ZOOMED_DOUBLE_TAP -> IDLE [label="tap"] + * DRAGGING -> ZOOMED [label="release"] + * ZOOMED -> IDLE [label="a11y\nbtn"] + * ZOOMED -> PANNING [label="2hold"] + * PANNING -> PANNING_SCALING [label="pinch"] + * PANNING_SCALING -> ZOOMED [label="release"] + * PANNING -> ZOOMED [label="release"] + * } + * } + */ @RunWith(AndroidJUnit4.class) public class MagnificationGestureHandlerTest { @@ -76,6 +105,8 @@ public class MagnificationGestureHandlerTest { private MagnificationGestureHandler mMgh; private TestHandler mHandler; + private long mLastDownTime = Integer.MIN_VALUE; + @Before public void setUp() { mContext = InstrumentationRegistry.getContext(); @@ -104,7 +135,13 @@ public class MagnificationGestureHandlerTest { MagnificationGestureHandler h = new MagnificationGestureHandler( mContext, mMagnificationController, detectTripleTap, detectShortcutTrigger); - mHandler = new TestHandler(h.mDetectingState, mClock); + mHandler = new TestHandler(h.mDetectingState, mClock) { + @Override + protected String messageToString(Message m) { + return DebugUtils.valueToString( + MagnificationGestureHandler.DetectingState.class, "MESSAGE_", m.what); + } + }; h.mDetectingState.mHandler = mHandler; h.setNext(strictMock(EventStreamTransformation.class)); return h; @@ -184,11 +221,11 @@ public class MagnificationGestureHandlerTest { fastForward1sec(); }, STATE_ZOOMED); - // tap+tap+swipe gets delegated - assertTransition(STATE_2TAPS, () -> { - allowEventDelegation(); - swipe(); - }, STATE_IDLE); + // tap+tap+swipe doesn't get delegated + assertTransition(STATE_2TAPS, () -> swipe(), STATE_IDLE); + + // tap+tap+swipe initiates viewport dragging immediately + assertTransition(STATE_2TAPS, () -> swipeAndHold(), STATE_DRAGGING_TMP); } @Test @@ -439,23 +476,24 @@ public class MagnificationGestureHandlerTest { } private void tap() { - MotionEvent downEvent = downEvent(); - send(downEvent); - send(upEvent(downEvent.getDownTime())); + send(downEvent()); + send(upEvent()); } private void swipe() { - MotionEvent downEvent = downEvent(); - send(downEvent); + swipeAndHold(); + send(upEvent()); + } + + private void swipeAndHold() { + send(downEvent()); send(moveEvent(DEFAULT_X * 2, DEFAULT_Y * 2)); - send(upEvent(downEvent.getDownTime())); } private void longTap() { - MotionEvent downEvent = downEvent(); - send(downEvent); + send(downEvent()); fastForward(2000); - send(upEvent(downEvent.getDownTime())); + send(upEvent()); } private void triggerShortcut() { @@ -473,16 +511,17 @@ public class MagnificationGestureHandlerTest { } private MotionEvent moveEvent(float x, float y) { - return MotionEvent.obtain(defaultDownTime(), mClock.now(), ACTION_MOVE, x, y, 0); + return MotionEvent.obtain(mLastDownTime, mClock.now(), ACTION_MOVE, x, y, 0); } private MotionEvent downEvent() { - return MotionEvent.obtain(mClock.now(), mClock.now(), + mLastDownTime = mClock.now(); + return MotionEvent.obtain(mLastDownTime, mLastDownTime, ACTION_DOWN, DEFAULT_X, DEFAULT_Y, 0); } private MotionEvent upEvent() { - return upEvent(defaultDownTime()); + return upEvent(mLastDownTime); } private MotionEvent upEvent(long downTime) { @@ -490,11 +529,6 @@ public class MagnificationGestureHandlerTest { MotionEvent.ACTION_UP, DEFAULT_X, DEFAULT_Y, 0); } - private long defaultDownTime() { - MotionEvent lastDown = mMgh.mDetectingState.mLastDown; - return lastDown == null ? mClock.now() - 1 : lastDown.getDownTime(); - } - private MotionEvent pointerEvent(int action, float x, float y) { MotionEvent.PointerProperties defPointerProperties = new MotionEvent.PointerProperties(); defPointerProperties.id = 0; diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/OverlayPackagesProviderTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/OverlayPackagesProviderTest.java index 4447fe91e34e..939a2725fb07 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/OverlayPackagesProviderTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/OverlayPackagesProviderTest.java @@ -261,7 +261,7 @@ public class OverlayPackagesProviderTest extends AndroidTestCase { } private void verifyAppsAreNonRequired(String action, String... appArray) { - assertEquals(listFromArray(appArray), + assertEquals(setFromArray(appArray), mHelper.getNonRequiredApps(TEST_MDM_COMPONENT_NAME, TEST_USER_ID, action)); } @@ -347,14 +347,7 @@ public class OverlayPackagesProviderTest extends AndroidTestCase { if (array == null) { return null; } - return new HashSet<T>(Arrays.asList(array)); - } - - private <T> List<T> listFromArray(T... array) { - if (array == null) { - return null; - } - return Arrays.asList(array); + return new HashSet<>(Arrays.asList(array)); } class FakePackageManager extends MockPackageManager { diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java index 6f13a9873909..97fbca2403bf 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java @@ -205,6 +205,14 @@ public class PlatformKeyManagerTest { } @Test + public void init_savesGenerationIdToDatabase() throws Exception { + mPlatformKeyManager.init(); + + assertEquals(1, + mRecoverableKeyStoreDb.getPlatformKeyGenerationId(USER_ID_FIXTURE)); + } + + @Test public void init_setsGenerationIdTo1() throws Exception { mPlatformKeyManager.init(); @@ -212,7 +220,38 @@ public class PlatformKeyManagerTest { } @Test + public void init_incrementsGenerationIdIfKeyIsUnavailable() throws Exception { + mPlatformKeyManager.init(); + + mPlatformKeyManager.init(); + + assertEquals(2, mPlatformKeyManager.getGenerationId()); + } + + @Test + public void init_doesNotIncrementGenerationIdIfKeyAvailable() throws Exception { + mPlatformKeyManager.init(); + when(mKeyStoreProxy + .containsAlias("com.android.server.locksettings.recoverablekeystore/" + + "platform/42/1/decrypt")).thenReturn(true); + when(mKeyStoreProxy + .containsAlias("com.android.server.locksettings.recoverablekeystore/" + + "platform/42/1/encrypt")).thenReturn(true); + + mPlatformKeyManager.init(); + + assertEquals(1, mPlatformKeyManager.getGenerationId()); + } + + @Test + public void getGenerationId_returnsMinusOneIfNotInitialized() throws Exception { + assertEquals(-1, mPlatformKeyManager.getGenerationId()); + } + + @Test public void getDecryptKey_getsDecryptKeyWithCorrectAlias() throws Exception { + mPlatformKeyManager.init(); + mPlatformKeyManager.getDecryptKey(); verify(mKeyStoreProxy).getKey( @@ -222,6 +261,8 @@ public class PlatformKeyManagerTest { @Test public void getEncryptKey_getsDecryptKeyWithCorrectAlias() throws Exception { + mPlatformKeyManager.init(); + mPlatformKeyManager.getEncryptKey(); verify(mKeyStoreProxy).getKey( diff --git a/services/tests/servicestests/src/com/android/server/net/watchlist/WatchlistSettingsTests.java b/services/tests/servicestests/src/com/android/server/net/watchlist/WatchlistSettingsTests.java index f3cb98078602..212d25d42420 100644 --- a/services/tests/servicestests/src/com/android/server/net/watchlist/WatchlistSettingsTests.java +++ b/services/tests/servicestests/src/com/android/server/net/watchlist/WatchlistSettingsTests.java @@ -95,41 +95,6 @@ public class WatchlistSettingsTests { } @Test - public void testWatchlistSettings_writeSettingsToDisk() throws Exception { - copyWatchlistSettingsXml(mContext, TEST_XML_1, mTestXmlFile); - WatchlistSettings settings = new WatchlistSettings(mTestXmlFile); - settings.writeSettingsToDisk(Arrays.asList(TEST_NEW_CC_DOMAIN_CRC32), - Arrays.asList(TEST_NEW_CC_DOMAIN_SHA256), Arrays.asList(TEST_NEW_CC_IP_CRC32), - Arrays.asList(TEST_NEW_CC_IP_SHA256)); - // Ensure old watchlist is not in memory - assertFalse(settings.containsDomain(TEST_CC_DOMAIN)); - assertFalse(settings.containsIp(TEST_CC_IP)); - assertFalse(settings.containsDomain(TEST_NOT_EXIST_CC_DOMAIN)); - assertFalse(settings.containsIp(TEST_NOT_EXIST_CC_IP)); - assertFalse(settings.containsDomain(TEST_SHA256_ONLY_DOMAIN)); - assertFalse(settings.containsIp(TEST_SHA256_ONLY_IP)); - assertFalse(settings.containsDomain(TEST_CRC32_ONLY_DOMAIN)); - assertFalse(settings.containsIp(TEST_CRC32_ONLY_IP)); - // Ensure new watchlist is in memory - assertTrue(settings.containsDomain(TEST_NEW_CC_DOMAIN)); - assertTrue(settings.containsIp(TEST_NEW_CC_IP)); - // Reload settings from disk and test again - settings = new WatchlistSettings(mTestXmlFile); - // Ensure old watchlist is not in memory - assertFalse(settings.containsDomain(TEST_CC_DOMAIN)); - assertFalse(settings.containsIp(TEST_CC_IP)); - assertFalse(settings.containsDomain(TEST_NOT_EXIST_CC_DOMAIN)); - assertFalse(settings.containsIp(TEST_NOT_EXIST_CC_IP)); - assertFalse(settings.containsDomain(TEST_SHA256_ONLY_DOMAIN)); - assertFalse(settings.containsIp(TEST_SHA256_ONLY_IP)); - assertFalse(settings.containsDomain(TEST_CRC32_ONLY_DOMAIN)); - assertFalse(settings.containsIp(TEST_CRC32_ONLY_IP)); - // Ensure new watchlist is in memory - assertTrue(settings.containsDomain(TEST_NEW_CC_DOMAIN)); - assertTrue(settings.containsIp(TEST_NEW_CC_IP)); - } - - @Test public void testWatchlistSettings_writeSettingsToMemory() throws Exception { copyWatchlistSettingsXml(mContext, TEST_XML_1, mTestXmlFile); WatchlistSettings settings = new WatchlistSettings(mTestXmlFile); diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageManagerServiceTest.java index e12a8da805f6..cdac516c9577 100644 --- a/services/tests/servicestests/src/com/android/server/pm/PackageManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/PackageManagerServiceTest.java @@ -17,75 +17,93 @@ package com.android.server.pm; import android.content.IIntentReceiver; - import android.os.Bundle; +import android.support.test.runner.AndroidJUnit4; -import android.test.AndroidTestCase; -import android.test.suitebuilder.annotation.SmallTest; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; import java.io.File; // runtest -c com.android.server.pm.PackageManagerServiceTest frameworks-services - -@SmallTest -public class PackageManagerServiceTest extends AndroidTestCase { - @Override - protected void setUp() throws Exception { - super.setUp(); +// bit FrameworksServicesTests:com.android.server.pm.PackageManagerServiceTest +@RunWith(AndroidJUnit4.class) +public class PackageManagerServiceTest { + @Before + public void setUp() throws Exception { } - @Override - protected void tearDown() throws Exception { - super.tearDown(); + @After + public void tearDown() throws Exception { } + @Test public void testPackageRemoval() throws Exception { - class PackageSenderImpl implements PackageSender { - public void sendPackageBroadcast(final String action, final String pkg, - final Bundle extras, final int flags, final String targetPkg, - final IIntentReceiver finishedReceiver, final int[] userIds, - int[] instantUserIds) { + class PackageSenderImpl implements PackageSender { + public void sendPackageBroadcast(final String action, final String pkg, + final Bundle extras, final int flags, final String targetPkg, + final IIntentReceiver finishedReceiver, final int[] userIds, + int[] instantUserIds) { + } + + public void sendPackageAddedForNewUsers(String packageName, + boolean sendBootComplete, boolean includeStopped, int appId, + int[] userIds, int[] instantUserIds) { + } + + @Override + public void notifyPackageAdded(String packageName) { + } + + @Override + public void notifyPackageRemoved(String packageName) { + } } - public void sendPackageAddedForNewUsers(String packageName, - boolean sendBootComplete, boolean includeStopped, int appId, - int[] userIds, int[] instantUserIds) { - } - } - - PackageSenderImpl sender = new PackageSenderImpl(); - PackageSetting setting = null; - PackageManagerService.PackageRemovedInfo pri = - new PackageManagerService.PackageRemovedInfo(sender); - - // Initial conditions: nothing there - assertNull(pri.removedUsers); - assertNull(pri.broadcastUsers); - - // populateUsers with nothing leaves nothing - pri.populateUsers(null, setting); - assertNull(pri.broadcastUsers); - - // Create a real (non-null) PackageSetting and confirm that the removed - // users are copied properly - setting = new PackageSetting("name", "realName", new File("codePath"), - new File("resourcePath"), "legacyNativeLibraryPathString", - "primaryCpuAbiString", "secondaryCpuAbiString", - "cpuAbiOverrideString", 0, 0, 0, "parentPackageName", null, 0, - null, null); - pri.populateUsers(new int[] {1, 2, 3, 4, 5}, setting); - assertNotNull(pri.broadcastUsers); - assertEquals(5, pri.broadcastUsers.length); - - // Exclude a user - pri.broadcastUsers = null; - final int EXCLUDED_USER_ID = 4; - setting.setInstantApp(true, EXCLUDED_USER_ID); - pri.populateUsers(new int[] {1, 2, 3, EXCLUDED_USER_ID, 5}, setting); - assertNotNull(pri.broadcastUsers); - assertEquals(5 - 1, pri.broadcastUsers.length); - - // TODO: test that sendApplicationHiddenForUser() actually fills in - // broadcastUsers + PackageSenderImpl sender = new PackageSenderImpl(); + PackageSetting setting = null; + PackageManagerService.PackageRemovedInfo pri = + new PackageManagerService.PackageRemovedInfo(sender); + + // Initial conditions: nothing there + Assert.assertNull(pri.removedUsers); + Assert.assertNull(pri.broadcastUsers); + + // populateUsers with nothing leaves nothing + pri.populateUsers(null, setting); + Assert.assertNull(pri.broadcastUsers); + + // Create a real (non-null) PackageSetting and confirm that the removed + // users are copied properly + setting = new PackageSetting("name", "realName", new File("codePath"), + new File("resourcePath"), "legacyNativeLibraryPathString", + "primaryCpuAbiString", "secondaryCpuAbiString", + "cpuAbiOverrideString", 0, 0, 0, "parentPackageName", null, 0, + null, null); + pri.populateUsers(new int[] { + 1, 2, 3, 4, 5 + }, setting); + Assert.assertNotNull(pri.broadcastUsers); + Assert.assertEquals(5, pri.broadcastUsers.length); + Assert.assertNotNull(pri.instantUserIds); + Assert.assertEquals(0, pri.instantUserIds.length); + + // Exclude a user + pri.broadcastUsers = null; + final int EXCLUDED_USER_ID = 4; + setting.setInstantApp(true, EXCLUDED_USER_ID); + pri.populateUsers(new int[] { + 1, 2, 3, EXCLUDED_USER_ID, 5 + }, setting); + Assert.assertNotNull(pri.broadcastUsers); + Assert.assertEquals(4, pri.broadcastUsers.length); + Assert.assertNotNull(pri.instantUserIds); + Assert.assertEquals(1, pri.instantUserIds.length); + + // TODO: test that sendApplicationHiddenForUser() actually fills in + // broadcastUsers } } diff --git a/services/tests/servicestests/src/com/android/server/testutils/TestHandler.java b/services/tests/servicestests/src/com/android/server/testutils/TestHandler.java index 2d4bc0f8b7d0..029d9f1a8262 100644 --- a/services/tests/servicestests/src/com/android/server/testutils/TestHandler.java +++ b/services/tests/servicestests/src/com/android/server/testutils/TestHandler.java @@ -104,6 +104,15 @@ public class TestHandler extends Handler { return new PriorityQueue<>(mMessages); } + /** + * Optionally-overridable to allow deciphering message types + * + * @see android.util.DebugUtils#valueToString - a handy utility to use when overriding this + */ + protected String messageToString(Message message) { + return message.toString(); + } + private void dispatch(MsgInfo msg) { int msgId = msg.message.what; @@ -148,7 +157,7 @@ public class TestHandler extends Handler { @Override public String toString() { return "MsgInfo{" + - "message=" + message + + "message=" + messageToString(message) + ", sendTime=" + sendTime + '}'; } diff --git a/telephony/java/android/telephony/euicc/EuiccManager.java b/telephony/java/android/telephony/euicc/EuiccManager.java index 973df316d280..176057ddc23e 100644 --- a/telephony/java/android/telephony/euicc/EuiccManager.java +++ b/telephony/java/android/telephony/euicc/EuiccManager.java @@ -15,8 +15,10 @@ */ package android.telephony.euicc; +import android.annotation.IntDef; import android.annotation.Nullable; import android.annotation.SdkConstant; +import android.annotation.SystemApi; import android.app.Activity; import android.app.PendingIntent; import android.content.Context; @@ -29,6 +31,9 @@ import android.os.ServiceManager; import com.android.internal.telephony.euicc.IEuiccController; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + /** * EuiccManager is the application interface to eUICCs, or eSIMs/embedded SIMs. * @@ -167,6 +172,35 @@ public class EuiccManager { */ public static final String META_DATA_CARRIER_ICON = "android.telephony.euicc.carriericon"; + /** + * Euicc OTA update status which can be got by {@link #getOtaStatus} + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = {"EUICC_OTA_"}, value = { + EUICC_OTA_IN_PROGRESS, + EUICC_OTA_FAILED, + EUICC_OTA_SUCCEEDED, + EUICC_OTA_NOT_NEEDED, + EUICC_OTA_STATUS_UNAVAILABLE + + }) + public @interface OtaStatus{} + + /** + * An OTA is in progress. During this time, the eUICC is not available and the user may lose + * network access. + */ + public static final int EUICC_OTA_IN_PROGRESS = 1; + /** The OTA update failed. */ + public static final int EUICC_OTA_FAILED = 2; + /** The OTA update finished successfully. */ + public static final int EUICC_OTA_SUCCEEDED = 3; + /** The OTA update not needed since current eUICC OS is latest. */ + public static final int EUICC_OTA_NOT_NEEDED = 4; + /** The OTA status is unavailable since eUICC service is unavailable. */ + public static final int EUICC_OTA_STATUS_UNAVAILABLE = 5; + private final Context mContext; /** @hide */ @@ -211,6 +245,26 @@ public class EuiccManager { } /** + * Returns the current status of eUICC OTA. + * + * <p>Requires the {@link android.Manifest.permission#WRITE_EMBEDDED_SUBSCRIPTIONS} permission. + * + * @return the status of eUICC OTA. If {@link #isEnabled()} is false or the eUICC is not ready, + * {@link OtaStatus#EUICC_OTA_STATUS_UNAVAILABLE} will be returned. + */ + @SystemApi + public int getOtaStatus() { + if (!isEnabled()) { + return EUICC_OTA_STATUS_UNAVAILABLE; + } + try { + return getIEuiccController().getOtaStatus(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Attempt to download the given {@link DownloadableSubscription}. * * <p>Requires the {@link android.Manifest.permission#WRITE_EMBEDDED_SUBSCRIPTIONS} permission, diff --git a/telephony/java/com/android/internal/telephony/RILConstants.java b/telephony/java/com/android/internal/telephony/RILConstants.java index e2d25b8e352c..f804cb068b31 100644 --- a/telephony/java/com/android/internal/telephony/RILConstants.java +++ b/telephony/java/com/android/internal/telephony/RILConstants.java @@ -417,6 +417,8 @@ cat include/telephony/ril.h | \ int RIL_REQUEST_SET_CARRIER_INFO_IMSI_ENCRYPTION = 141; int RIL_REQUEST_START_NETWORK_SCAN = 142; int RIL_REQUEST_STOP_NETWORK_SCAN = 143; + int RIL_REQUEST_GET_SLOT_STATUS = 144; + int RIL_REQUEST_SET_LOGICAL_TO_PHYSICAL_SLOT_MAPPING = 145; int RIL_RESPONSE_ACKNOWLEDGEMENT = 800; @@ -471,4 +473,5 @@ cat include/telephony/ril.h | \ int RIL_UNSOL_MODEM_RESTART = 1047; int RIL_UNSOL_CARRIER_INFO_IMSI_ENCRYPTION = 1048; int RIL_UNSOL_NETWORK_SCAN_RESULT = 1049; + int RIL_UNSOL_ICC_SLOT_STATUS = 1050; } diff --git a/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl b/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl index b3fc90db75d3..0a0ad90b5954 100644 --- a/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl +++ b/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl @@ -30,6 +30,7 @@ interface IEuiccController { oneway void getDefaultDownloadableSubscriptionList( String callingPackage, in PendingIntent callbackIntent); String getEid(); + int getOtaStatus(); oneway void downloadSubscription(in DownloadableSubscription subscription, boolean switchAfterDownload, String callingPackage, in PendingIntent callbackIntent); EuiccInfo getEuiccInfo(); diff --git a/tests/DexLoggerIntegrationTests/Android.mk b/tests/DexLoggerIntegrationTests/Android.mk new file mode 100644 index 000000000000..7187a3795433 --- /dev/null +++ b/tests/DexLoggerIntegrationTests/Android.mk @@ -0,0 +1,49 @@ +# +# Copyright 2017 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. +# + +LOCAL_PATH:= $(call my-dir) + +# Build a tiny library that the test app can dynamically load + +include $(CLEAR_VARS) + +LOCAL_MODULE_TAGS := tests +LOCAL_MODULE := DexLoggerTestLibrary +LOCAL_SRC_FILES := $(call all-java-files-under, src/com/android/dcl) + +include $(BUILD_JAVA_LIBRARY) + +dexloggertest_jar := $(LOCAL_BUILT_MODULE) + + +# Build the test app itself + +include $(CLEAR_VARS) + +LOCAL_MODULE_TAGS := tests +LOCAL_PACKAGE_NAME := DexLoggerIntegrationTests +LOCAL_COMPATIBILITY_SUITE := device-tests +LOCAL_CERTIFICATE := platform +LOCAL_SRC_FILES := $(call all-java-files-under, src/com/android/server/pm) + +LOCAL_STATIC_JAVA_LIBRARIES := \ + android-support-test \ + truth-prebuilt \ + +# This gets us the javalib.jar built by DexLoggerTestLibrary above. +LOCAL_JAVA_RESOURCE_FILES := $(dexloggertest_jar) + +include $(BUILD_PACKAGE) diff --git a/tests/DexLoggerIntegrationTests/AndroidManifest.xml b/tests/DexLoggerIntegrationTests/AndroidManifest.xml new file mode 100644 index 000000000000..a847e8f3b921 --- /dev/null +++ b/tests/DexLoggerIntegrationTests/AndroidManifest.xml @@ -0,0 +1,35 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright 2017 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. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.frameworks.dexloggertest"> + + <!-- Tests feature introduced in P (27) --> + <uses-sdk + android:minSdkVersion="27" + android:targetSdkVersion="27" /> + + <uses-permission android:name="android.permission.READ_LOGS" /> + + <application> + <uses-library android:name="android.test.runner" /> + </application> + + <instrumentation + android:name="android.support.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.frameworks.dexloggertest" + android:label="Integration test for DexLogger" /> +</manifest> diff --git a/tests/DexLoggerIntegrationTests/AndroidTest.xml b/tests/DexLoggerIntegrationTests/AndroidTest.xml new file mode 100644 index 000000000000..8ed19f893476 --- /dev/null +++ b/tests/DexLoggerIntegrationTests/AndroidTest.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright 2017 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. +--> +<configuration description="Runs DexLogger Integration Tests"> + <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup"> + <option name="test-file-name" value="DexLoggerIntegrationTests.apk"/> + <option name="cleanup-apks" value="true"/> + </target_preparer> + + <option name="test-suite-tag" value="apct"/> + <option name="test-tag" value="DexLoggerIntegrationTests"/> + + <test class="com.android.tradefed.testtype.AndroidJUnitTest"> + <option name="package" value="com.android.frameworks.dexloggertest"/> + <option name="runner" value="android.support.test.runner.AndroidJUnitRunner"/> + </test> +</configuration> diff --git a/tests/DexLoggerIntegrationTests/src/com/android/dcl/Simple.java b/tests/DexLoggerIntegrationTests/src/com/android/dcl/Simple.java new file mode 100644 index 000000000000..e995a26ea5c9 --- /dev/null +++ b/tests/DexLoggerIntegrationTests/src/com/android/dcl/Simple.java @@ -0,0 +1,22 @@ +/* + * Copyright 2017 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.dcl; + +/** Dummy class which is built into a jar purely so we can pass it to DexClassLoader. */ +public final class Simple { + public Simple() {} +} diff --git a/tests/DexLoggerIntegrationTests/src/com/android/server/pm/DexLoggerIntegrationTests.java b/tests/DexLoggerIntegrationTests/src/com/android/server/pm/DexLoggerIntegrationTests.java new file mode 100644 index 000000000000..d9f34d589c41 --- /dev/null +++ b/tests/DexLoggerIntegrationTests/src/com/android/server/pm/DexLoggerIntegrationTests.java @@ -0,0 +1,151 @@ +/* + * Copyright 2017 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.pm; + +import static com.google.common.truth.Truth.assertThat; + +import android.content.Context; +import android.support.test.InstrumentationRegistry; +import android.util.EventLog; + +import dalvik.system.DexClassLoader; + +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.io.OutputStream; +import java.security.MessageDigest; +import java.util.ArrayList; +import java.util.Formatter; +import java.util.List; + +/** + * Integration tests for {@link com.android.server.pm.dex.DexLogger}. + * + * The setup for the test dynamically loads code in a jar extracted + * from our assets (a secondary dex file). + * + * We then use adb to trigger secondary dex file reconcilation (and + * wait for it to complete). As a side-effect of this DexLogger should + * be notified of the file and should log the hash of the file's name + * and content. We verify that this message appears in the event log. + * + * Run with "atest DexLoggerIntegrationTests". + */ +@RunWith(JUnit4.class) +public final class DexLoggerIntegrationTests { + + private static final String TAG = DexLoggerIntegrationTests.class.getSimpleName(); + + private static final String PACKAGE_NAME = "com.android.frameworks.dexloggertest"; + + private static final int SNET_TAG = 0x534e4554; + private static final String DCL_SUBTAG = "dcl"; + + // Obtained via "echo -n copied.jar | sha256sum" + private static final String EXPECTED_NAME_HASH = + "1B6C71DB26F36582867432CCA12FB6A517470C9F9AABE9198DD4C5C030D6DC0C"; + + private static String expectedContentHash; + + @BeforeClass + public static void setUpAll() throws Exception { + Context context = InstrumentationRegistry.getTargetContext(); + MessageDigest hasher = MessageDigest.getInstance("SHA-256"); + + // Copy the jar from our Java resources to a private data directory + File privateCopy = new File(context.getDir("jars", Context.MODE_PRIVATE), "copied.jar"); + try (InputStream input = DexLoggerIntegrationTests.class.getResourceAsStream("/javalib.jar"); + OutputStream output = new FileOutputStream(privateCopy)) { + byte[] buffer = new byte[1024]; + while (true) { + int numRead = input.read(buffer); + if (numRead < 0) { + break; + } + output.write(buffer, 0, numRead); + hasher.update(buffer, 0, numRead); + } + } + + // Remember the SHA-256 of the file content to check that it is the same as + // the value we see logged. + Formatter formatter = new Formatter(); + for (byte b : hasher.digest()) { + formatter.format("%02X", b); + } + expectedContentHash = formatter.toString(); + + // Feed the jar to a class loader and make sure it contains what we expect. + ClassLoader loader = + new DexClassLoader( + privateCopy.toString(), null, null, context.getClass().getClassLoader()); + loader.loadClass("com.android.dcl.Simple"); + } + + @Test + public void testDexLoggerReconcileGeneratesEvents() throws Exception { + int[] tagList = new int[] { SNET_TAG }; + List<EventLog.Event> events = new ArrayList<>(); + + // There may already be events in the event log - figure out the most recent one + EventLog.readEvents(tagList, events); + long previousEventNanos = events.isEmpty() ? 0 : events.get(events.size() - 1).getTimeNanos(); + events.clear(); + + Process process = Runtime.getRuntime().exec( + "cmd package reconcile-secondary-dex-files " + PACKAGE_NAME); + int exitCode = process.waitFor(); + assertThat(exitCode).isEqualTo(0); + + int myUid = android.os.Process.myUid(); + String expectedMessage = EXPECTED_NAME_HASH + " " + expectedContentHash; + + EventLog.readEvents(tagList, events); + boolean found = false; + for (EventLog.Event event : events) { + if (event.getTimeNanos() <= previousEventNanos) { + continue; + } + Object[] data = (Object[]) event.getData(); + + // We only care about DCL events that we generated. + String subTag = (String) data[0]; + if (!DCL_SUBTAG.equals(subTag)) { + continue; + } + int uid = (int) data[1]; + if (uid != myUid) { + continue; + } + + String message = (String) data[2]; + assertThat(message).isEqualTo(expectedMessage); + found = true; + } + + assertThat(found).isTrue(); + } +} diff --git a/tools/sdkparcelables/Android.bp b/tools/sdkparcelables/Android.bp new file mode 100644 index 000000000000..00fb8aa20b18 --- /dev/null +++ b/tools/sdkparcelables/Android.bp @@ -0,0 +1,22 @@ +java_binary_host { + name: "sdkparcelables", + manifest: "manifest.txt", + srcs: [ + "src/**/*.kt", + ], + static_libs: [ + "asm-6.0", + ], +} + +java_library_host { + name: "sdkparcelables_test", + manifest: "manifest.txt", + srcs: [ + "tests/**/*.kt", + ], + static_libs: [ + "sdkparcelables", + "junit", + ], +} diff --git a/tools/sdkparcelables/manifest.txt b/tools/sdkparcelables/manifest.txt new file mode 100644 index 000000000000..cd5420ce0bf4 --- /dev/null +++ b/tools/sdkparcelables/manifest.txt @@ -0,0 +1 @@ +Main-class: com.android.sdkparcelables.MainKt diff --git a/tools/sdkparcelables/src/com/android/sdkparcelables/AncestorCollector.kt b/tools/sdkparcelables/src/com/android/sdkparcelables/AncestorCollector.kt new file mode 100644 index 000000000000..f278aec8eb6f --- /dev/null +++ b/tools/sdkparcelables/src/com/android/sdkparcelables/AncestorCollector.kt @@ -0,0 +1,28 @@ +package com.android.sdkparcelables + +import org.objectweb.asm.ClassVisitor +import java.util.* + +data class Ancestors(val superName: String?, val interfaces: List<String>?) + +/** A class that implements an ASM ClassVisitor that collects super class and + * implemented interfaces for each class that it visits. + */ +class AncestorCollector(api: Int, dest: ClassVisitor?) : ClassVisitor(api, dest) { + private val _ancestors = LinkedHashMap<String, Ancestors>() + + val ancestors: Map<String, Ancestors> + get() = _ancestors + + override fun visit(version: Int, access: Int, name: String?, signature: String?, + superName: String?, interfaces: Array<out String>?) { + name!! + + val old = _ancestors.put(name, Ancestors(superName, interfaces?.toList())) + if (old != null) { + throw RuntimeException("class $name already found") + } + + super.visit(version, access, name, signature, superName, interfaces) + } +}
\ No newline at end of file diff --git a/tools/sdkparcelables/src/com/android/sdkparcelables/Main.kt b/tools/sdkparcelables/src/com/android/sdkparcelables/Main.kt new file mode 100644 index 000000000000..3e9d92cd978f --- /dev/null +++ b/tools/sdkparcelables/src/com/android/sdkparcelables/Main.kt @@ -0,0 +1,56 @@ +package com.android.sdkparcelables + +import org.objectweb.asm.ClassReader +import org.objectweb.asm.Opcodes +import java.io.File +import java.io.IOException +import java.util.zip.ZipFile + +fun main(args: Array<String>) { + if (args.size != 2) { + usage() + } + + val zipFileName = args[0] + val aidlFileName = args[1] + + val zipFile: ZipFile + + try { + zipFile = ZipFile(zipFileName) + } catch (e: IOException) { + System.err.println("error reading input jar: ${e.message}") + kotlin.system.exitProcess(2) + } + + val ancestorCollector = AncestorCollector(Opcodes.ASM6, null) + + for (entry in zipFile.entries()) { + if (entry.name.endsWith(".class")) { + val reader = ClassReader(zipFile.getInputStream(entry)) + reader.accept(ancestorCollector, + ClassReader.SKIP_CODE or ClassReader.SKIP_DEBUG or ClassReader.SKIP_FRAMES) + } + } + + val parcelables = ParcelableDetector.ancestorsToParcelables(ancestorCollector.ancestors) + + try { + val outFile = File(aidlFileName) + val outWriter = outFile.bufferedWriter() + for (parcelable in parcelables) { + outWriter.write("parcelable ") + outWriter.write(parcelable.replace('/', '.').replace('$', '.')) + outWriter.write(";\n") + } + outWriter.flush() + } catch (e: IOException) { + System.err.println("error writing output aidl: ${e.message}") + kotlin.system.exitProcess(2) + } +} + +fun usage() { + System.err.println("Usage: <input jar> <output aidl>") + kotlin.system.exitProcess(1) +}
\ No newline at end of file diff --git a/tools/sdkparcelables/src/com/android/sdkparcelables/ParcelableDetector.kt b/tools/sdkparcelables/src/com/android/sdkparcelables/ParcelableDetector.kt new file mode 100644 index 000000000000..620f798daf48 --- /dev/null +++ b/tools/sdkparcelables/src/com/android/sdkparcelables/ParcelableDetector.kt @@ -0,0 +1,52 @@ +package com.android.sdkparcelables + +/** A class that uses an ancestor map to find all classes that + * implement android.os.Parcelable, including indirectly through + * super classes or super interfaces. + */ +class ParcelableDetector { + companion object { + fun ancestorsToParcelables(ancestors: Map<String, Ancestors>): List<String> { + val impl = Impl(ancestors) + impl.build() + return impl.parcelables + } + } + + private class Impl(val ancestors: Map<String, Ancestors>) { + val isParcelableCache = HashMap<String, Boolean>() + val parcelables = ArrayList<String>() + + fun build() { + val classList = ancestors.keys + classList.filterTo(parcelables, this::isParcelable) + parcelables.sort() + } + + private fun isParcelable(c: String?): Boolean { + if (c == null) { + return false + } + + if (c == "android/os/Parcelable") { + return true + } + + val old = isParcelableCache[c] + if (old != null) { + return old + } + + val cAncestors = ancestors[c] ?: + throw RuntimeException("class $c missing ancestor information") + + val seq = (cAncestors.interfaces?.asSequence() ?: emptySequence()) + + cAncestors.superName + + val ancestorIsParcelable = seq.any(this::isParcelable) + + isParcelableCache[c] = ancestorIsParcelable + return ancestorIsParcelable + } + } +} diff --git a/tools/sdkparcelables/tests/com/android/sdkparcelables/ParcelableDetectorTest.kt b/tools/sdkparcelables/tests/com/android/sdkparcelables/ParcelableDetectorTest.kt new file mode 100644 index 000000000000..edfc8259a738 --- /dev/null +++ b/tools/sdkparcelables/tests/com/android/sdkparcelables/ParcelableDetectorTest.kt @@ -0,0 +1,57 @@ +package com.android.sdkparcelables + +import junit.framework.TestCase.assertEquals +import org.junit.Test + +class ParcelableDetectorTest { + @Test + fun `detect implements`() { + val ancestorMap = mapOf( + testAncestors("android/test/Parcelable",null, "android/os/Parcelable"), + testAncestors("android/os/Parcelable", null)) + + val parcelables = ParcelableDetector.ancestorsToParcelables(ancestorMap) + + assertEquals(parcelables, listOf("android/os/Parcelable", "android/test/Parcelable")) + } + + @Test + fun `detect implements in reverse order`() { + val ancestorMap = mapOf( + testAncestors("android/os/Parcelable", null), + testAncestors("android/test/Parcelable",null, "android/os/Parcelable")) + + val parcelables = ParcelableDetector.ancestorsToParcelables(ancestorMap) + + assertEquals(parcelables, listOf("android/os/Parcelable", "android/test/Parcelable")) + } + + @Test + fun `detect super implements`() { + val ancestorMap = mapOf( + testAncestors("android/test/SuperParcelable",null, "android/os/Parcelable"), + testAncestors("android/test/Parcelable","android/test/SuperParcelable"), + testAncestors("android/os/Parcelable", null)) + + val parcelables = ParcelableDetector.ancestorsToParcelables(ancestorMap) + + assertEquals(parcelables, listOf("android/os/Parcelable", "android/test/Parcelable", "android/test/SuperParcelable")) + } + + @Test + fun `detect super interface`() { + val ancestorMap = mapOf( + testAncestors("android/test/IParcelable",null, "android/os/Parcelable"), + testAncestors("android/test/Parcelable",null, "android/test/IParcelable"), + testAncestors("android/os/Parcelable", null)) + + val parcelables = ParcelableDetector.ancestorsToParcelables(ancestorMap) + + assertEquals(parcelables, listOf("android/os/Parcelable", "android/test/IParcelable", "android/test/Parcelable")) + } + +} + +private fun testAncestors(name: String, superName: String?, vararg interfaces: String): Pair<String, Ancestors> { + return Pair(name, Ancestors(superName, interfaces.toList())) +} diff --git a/tools/stats_log_api_gen/Collation.cpp b/tools/stats_log_api_gen/Collation.cpp index a25784debf12..80853b13a7cc 100644 --- a/tools/stats_log_api_gen/Collation.cpp +++ b/tools/stats_log_api_gen/Collation.cpp @@ -112,7 +112,7 @@ java_type(const FieldDescriptor* field) case FieldDescriptor::TYPE_MESSAGE: // TODO: not the final package name if (field->message_type()->full_name() == - "android.os.statsd.AttributionChain") { + "android.os.statsd.AttributionNode") { return JAVA_TYPE_ATTRIBUTION_CHAIN; } else { return JAVA_TYPE_OBJECT; diff --git a/tools/stats_log_api_gen/main.cpp b/tools/stats_log_api_gen/main.cpp index 5e93c08e88b8..89749fb52bb4 100644 --- a/tools/stats_log_api_gen/main.cpp +++ b/tools/stats_log_api_gen/main.cpp @@ -769,7 +769,7 @@ run(int argc, char const*const* argv) AtomDecl attributionDecl; vector<java_type_t> attributionSignature; - collate_atom(android::os::statsd::Attribution::descriptor(), + collate_atom(android::os::statsd::AttributionNode::descriptor(), &attributionDecl, &attributionSignature); // Write the .cpp file diff --git a/tools/stats_log_api_gen/test.proto b/tools/stats_log_api_gen/test.proto index 84b22cdaf522..66cbee8f219b 100644 --- a/tools/stats_log_api_gen/test.proto +++ b/tools/stats_log_api_gen/test.proto @@ -39,7 +39,7 @@ enum AnEnum { } message AllTypesAtom { - optional android.os.statsd.AttributionChain attribution_chain = 1; + repeated android.os.statsd.AttributionNode attribution_chain = 1; optional double double_field = 2; optional float float_field = 3; optional int64 int64_field = 4; @@ -99,12 +99,12 @@ message BadSkippedFieldMultiple { } } -message BadAttributionChainPositionAtom { +message BadAttributionNodePositionAtom { optional int32 field1 = 1; - optional android.os.statsd.AttributionChain attribution = 2; + repeated android.os.statsd.AttributionNode attribution = 2; } -message BadAttributionChainPosition { - oneof event { BadAttributionChainPositionAtom bad = 1; } +message BadAttributionNodePosition { + oneof event { BadAttributionNodePositionAtom bad = 1; } } diff --git a/tools/stats_log_api_gen/test_collation.cpp b/tools/stats_log_api_gen/test_collation.cpp index b2b7298d86c7..9e22cd92fd53 100644 --- a/tools/stats_log_api_gen/test_collation.cpp +++ b/tools/stats_log_api_gen/test_collation.cpp @@ -191,10 +191,10 @@ TEST(CollationTest, FailOnSkippedFieldsMultiple) { * Test that atoms that have an attribution chain not in the first position are * rejected. */ -TEST(CollationTest, FailBadAttributionChainPosition) { +TEST(CollationTest, FailBadAttributionNodePosition) { Atoms atoms; int errorCount = - collate_atoms(BadAttributionChainPosition::descriptor(), &atoms); + collate_atoms(BadAttributionNodePosition::descriptor(), &atoms); EXPECT_EQ(1, errorCount); } diff --git a/wifi/java/android/net/wifi/WifiScanner.java b/wifi/java/android/net/wifi/WifiScanner.java index e3752ac77a5e..928a1da8b51c 100644 --- a/wifi/java/android/net/wifi/WifiScanner.java +++ b/wifi/java/android/net/wifi/WifiScanner.java @@ -160,6 +160,24 @@ public class WifiScanner { */ public static final int REPORT_EVENT_NO_BATCH = (1 << 2); + /** + * This is used to indicate the purpose of the scan to the wifi chip in + * {@link ScanSettings#type}. + * On devices with multiple hardware radio chains (and hence different modes of scan), + * this type serves as an indication to the hardware on what mode of scan to perform. + * Only apps holding android.Manifest.permission.NETWORK_STACK permission can set this value. + * + * Note: This serves as an intent and not as a stipulation, the wifi chip + * might honor or ignore the indication based on the current radio conditions. Always + * use the {@link ScanResult#radioChainInfos} to figure out the radio chain configuration used + * to receive the corresponding scan result. + */ + /** {@hide} */ + public static final int TYPE_LOW_LATENCY = 0; + /** {@hide} */ + public static final int TYPE_LOW_POWER = 1; + /** {@hide} */ + public static final int TYPE_HIGH_ACCURACY = 2; /** {@hide} */ public static final String SCAN_PARAMS_SCAN_SETTINGS_KEY = "ScanSettings"; @@ -193,7 +211,8 @@ public class WifiScanner { * list of hidden networks to scan for. Explicit probe requests are sent out for such * networks during scan. Only valid for single scan requests. * {@hide} - * */ + */ + @RequiresPermission(android.Manifest.permission.NETWORK_STACK) public HiddenNetwork[] hiddenNetworks; /** period of background scan; in millisecond, 0 => single shot scan */ public int periodInMs; @@ -223,6 +242,13 @@ public class WifiScanner { * {@hide} */ public boolean isPnoScan; + /** + * Indicate the type of scan to be performed by the wifi chip. + * Default value: {@link #TYPE_LOW_LATENCY}. + * {@hide} + */ + @RequiresPermission(android.Manifest.permission.NETWORK_STACK) + public int type = TYPE_LOW_LATENCY; /** Implement the Parcelable interface {@hide} */ public int describeContents() { @@ -239,6 +265,7 @@ public class WifiScanner { dest.writeInt(maxPeriodInMs); dest.writeInt(stepCount); dest.writeInt(isPnoScan ? 1 : 0); + dest.writeInt(type); if (channels != null) { dest.writeInt(channels.length); for (int i = 0; i < channels.length; i++) { @@ -272,6 +299,7 @@ public class WifiScanner { settings.maxPeriodInMs = in.readInt(); settings.stepCount = in.readInt(); settings.isPnoScan = in.readInt() == 1; + settings.type = in.readInt(); int num_channels = in.readInt(); settings.channels = new ChannelSpec[num_channels]; for (int i = 0; i < num_channels; i++) { diff --git a/wifi/java/android/net/wifi/hotspot2/ProvisioningCallback.java b/wifi/java/android/net/wifi/hotspot2/ProvisioningCallback.java index 8b86cdde4a9e..2ea6e797ec93 100644 --- a/wifi/java/android/net/wifi/hotspot2/ProvisioningCallback.java +++ b/wifi/java/android/net/wifi/hotspot2/ProvisioningCallback.java @@ -26,13 +26,49 @@ import android.os.Handler; */ public abstract class ProvisioningCallback { - /** + /** * The reason code for Provisioning Failure due to connection failure to OSU AP. * @hide */ public static final int OSU_FAILURE_AP_CONNECTION = 1; /** + * The reason code for Provisioning Failure due to connection failure to OSU AP. + * @hide + */ + public static final int OSU_FAILURE_SERVER_URL_INVALID = 2; + + /** + * The reason code for Provisioning Failure due to connection failure to OSU AP. + * @hide + */ + public static final int OSU_FAILURE_SERVER_CONNECTION = 3; + + /** + * The reason code for Provisioning Failure due to connection failure to OSU AP. + * @hide + */ + public static final int OSU_FAILURE_SERVER_VALIDATION = 4; + + /** + * The reason code for Provisioning Failure due to connection failure to OSU AP. + * @hide + */ + public static final int OSU_FAILURE_PROVIDER_VERIFICATION = 5; + + /** + * The reason code for Provisioning Failure when a provisioning flow is aborted. + * @hide + */ + public static final int OSU_FAILURE_PROVISIONING_ABORTED = 6; + + /** + * The reason code for Provisioning Failure when a provisioning flow is aborted. + * @hide + */ + public static final int OSU_FAILURE_PROVISIONING_NOT_AVAILABLE = 7; + + /** * The status code for Provisioning flow to indicate connecting to OSU AP * @hide */ @@ -45,6 +81,24 @@ public abstract class ProvisioningCallback { public static final int OSU_STATUS_AP_CONNECTED = 2; /** + * The status code for Provisioning flow to indicate connecting to OSU AP + * @hide + */ + public static final int OSU_STATUS_SERVER_CONNECTED = 3; + + /** + * The status code for Provisioning flow to indicate connecting to OSU AP + * @hide + */ + public static final int OSU_STATUS_SERVER_VALIDATED = 4; + + /** + * The status code for Provisioning flow to indicate connecting to OSU AP + * @hide + */ + public static final int OSU_STATUS_PROVIDER_VERIFIED = 5; + + /** * Provisioning status for OSU failure * @param status indicates error condition */ diff --git a/wifi/java/android/net/wifi/rtt/ResponderConfig.java b/wifi/java/android/net/wifi/rtt/ResponderConfig.java index 8be7782d5664..c3e10074c56c 100644 --- a/wifi/java/android/net/wifi/rtt/ResponderConfig.java +++ b/wifi/java/android/net/wifi/rtt/ResponderConfig.java @@ -37,7 +37,7 @@ import java.util.Objects; * * @hide (@SystemApi) */ -public class ResponderConfig implements Parcelable { +public final class ResponderConfig implements Parcelable { private static final int AWARE_BAND_2_DISCOVERY_CHANNEL = 2437; /** @hide */ @@ -122,15 +122,14 @@ public class ResponderConfig implements Parcelable { /** * The MAC address of the Responder. Will be null if a Wi-Fi Aware peer identifier (the * peerHandle field) ise used to identify the Responder. - * TODO: convert to MacAddress */ - public MacAddress macAddress; + public final MacAddress macAddress; /** * The peer identifier of a Wi-Fi Aware Responder. Will be null if a MAC Address (the macAddress * field) is used to identify the Responder. */ - public PeerHandle peerHandle; + public final PeerHandle peerHandle; /** * The device type of the Responder. @@ -171,7 +170,7 @@ public class ResponderConfig implements Parcelable { public final int preamble; /** - * Constructs Responder configuration. + * Constructs Responder configuration, using a MAC address to identify the Responder. * * @param macAddress The MAC address of the Responder. * @param responderType The type of the responder device, specified using @@ -210,7 +209,7 @@ public class ResponderConfig implements Parcelable { } /** - * Constructs Responder configuration. + * Constructs Responder configuration, using a Wi-Fi Aware PeerHandle to identify the Responder. * * @param peerHandle The Wi-Fi Aware peer identifier of the Responder. * @param responderType The type of the responder device, specified using @@ -245,6 +244,45 @@ public class ResponderConfig implements Parcelable { } /** + * Constructs Responder configuration. This is an internal-only constructor which specifies both + * a MAC address and a Wi-Fi PeerHandle to identify the Responder. + * + * @param macAddress The MAC address of the Responder. + * @param peerHandle The Wi-Fi Aware peer identifier of the Responder. + * @param responderType The type of the responder device, specified using + * {@link ResponderType}. + * @param supports80211mc Indicates whether the responder supports IEEE 802.11mc. + * @param channelWidth Responder channel bandwidth, specified using {@link ChannelWidth}. + * @param frequency The primary 20 MHz frequency (in MHz) of the channel of the Responder. + * @param centerFreq0 Not used if the {@code channelWidth} is 20 MHz. If the Responder uses + * 40, 80 or 160 MHz, this is the center frequency (in MHz), if the + * Responder uses 80 + 80 MHz, this is the center frequency of the first + * segment (in MHz). + * @param centerFreq1 Only used if the {@code channelWidth} is 80 + 80 MHz. If the + * Responder + * uses 80 + 80 MHz, this is the center frequency of the second segment + * (in + * MHz). + * @param preamble The preamble used by the Responder, specified using + * {@link PreambleType}. + * @hide + */ + public ResponderConfig(@NonNull MacAddress macAddress, @NonNull PeerHandle peerHandle, + @ResponderType int responderType, boolean supports80211mc, + @ChannelWidth int channelWidth, int frequency, int centerFreq0, int centerFreq1, + @PreambleType int preamble) { + this.macAddress = macAddress; + this.peerHandle = peerHandle; + this.responderType = responderType; + this.supports80211mc = supports80211mc; + this.channelWidth = channelWidth; + this.frequency = frequency; + this.centerFreq0 = centerFreq0; + this.centerFreq1 = centerFreq1; + this.preamble = preamble; + } + + /** * Creates a Responder configuration from a {@link ScanResult} corresponding to an Access * Point (AP), which can be obtained from {@link android.net.wifi.WifiManager#getScanResults()}. */ diff --git a/wifi/tests/src/android/net/wifi/WifiScannerTest.java b/wifi/tests/src/android/net/wifi/WifiScannerTest.java index e542789e01e3..a4366d454a67 100644 --- a/wifi/tests/src/android/net/wifi/WifiScannerTest.java +++ b/wifi/tests/src/android/net/wifi/WifiScannerTest.java @@ -16,19 +16,23 @@ package android.net.wifi; +import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.validateMockitoUsage; import static org.mockito.Mockito.when; import android.content.Context; import android.os.Handler; +import android.os.Parcel; import android.os.test.TestLooper; import android.test.suitebuilder.annotation.SmallTest; +import android.net.wifi.WifiScanner.ScanSettings; import com.android.internal.util.test.BidirectionalAsyncChannelServer; import org.junit.After; import org.junit.Before; +import org.junit.Test; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @@ -70,4 +74,50 @@ public class WifiScannerTest { validateMockitoUsage(); } + /** + * Verify parcel read/write for ScanSettings. + */ + @Test + public void verifyScanSettingsParcelWithBand() throws Exception { + ScanSettings writeSettings = new ScanSettings(); + writeSettings.type = WifiScanner.TYPE_LOW_POWER; + writeSettings.band = WifiScanner.WIFI_BAND_BOTH_WITH_DFS; + + ScanSettings readSettings = parcelWriteRead(writeSettings); + assertEquals(readSettings.type, writeSettings.type); + assertEquals(readSettings.band, writeSettings.band); + assertEquals(0, readSettings.channels.length); + } + + /** + * Verify parcel read/write for ScanSettings. + */ + @Test + public void verifyScanSettingsParcelWithChannels() throws Exception { + ScanSettings writeSettings = new ScanSettings(); + writeSettings.type = WifiScanner.TYPE_HIGH_ACCURACY; + writeSettings.band = WifiScanner.WIFI_BAND_UNSPECIFIED; + writeSettings.channels = new WifiScanner.ChannelSpec[] { + new WifiScanner.ChannelSpec(5), + new WifiScanner.ChannelSpec(7) + }; + + ScanSettings readSettings = parcelWriteRead(writeSettings); + assertEquals(writeSettings.type, readSettings.type); + assertEquals(writeSettings.band, readSettings.band); + assertEquals(2, readSettings.channels.length); + assertEquals(5, readSettings.channels[0].frequency); + assertEquals(7, readSettings.channels[1].frequency); + } + + /** + * Write the provided {@link ScanSettings} to a parcel and deserialize it. + */ + private static ScanSettings parcelWriteRead(ScanSettings writeSettings) throws Exception { + Parcel parcel = Parcel.obtain(); + writeSettings.writeToParcel(parcel, 0); + parcel.setDataPosition(0); // Rewind data position back to the beginning for read. + return ScanSettings.CREATOR.createFromParcel(parcel); + } + } |