diff options
80 files changed, 2468 insertions, 712 deletions
diff --git a/Android.bp b/Android.bp index 69ee848cadad..03a79f61def7 100644 --- a/Android.bp +++ b/Android.bp @@ -730,10 +730,18 @@ gensrcs { "core/proto/android/os/procrank.proto", "core/proto/android/os/ps.proto", "core/proto/android/os/system_properties.proto", + "core/proto/android/util/event_log_tags.proto", ], // Append protoc-gen-cppstream tool's PATH otherwise aprotoc can't find the plugin tool - cmd: "PATH=$$PATH:$$(dirname $(location protoc-gen-cppstream)) $(location aprotoc) --plugin=protoc-gen-cpp-stream=$(location protoc-gen-cppstream) --dependency_out=$(depfile) --cppstream_out=$(genDir)/ -Iexternal/protobuf/src -I . $(in)", + cmd: "mkdir -p $(genDir) " + + "&& $(location aprotoc) " + + " --plugin=$(location protoc-gen-cppstream) " + + " --dependency_out=$(depfile) " + + " --cppstream_out=$(genDir) " + + " -Iexternal/protobuf/src " + + " -I . " + + " $(in)", output_extension = "proto.h", } diff --git a/cmds/incident_helper/src/ih_util.cpp b/cmds/incident_helper/src/ih_util.cpp index e23e80ae21e8..847b26a39ffe 100644 --- a/cmds/incident_helper/src/ih_util.cpp +++ b/cmds/incident_helper/src/ih_util.cpp @@ -208,6 +208,19 @@ bool stripSuffix(std::string* line, const char* key, bool endAtDelimiter) { return true; } +std::string behead(std::string* line, const char cut) { + auto found = line->find_first_of(cut); + if (found == std::string::npos) { + std::string head = line->substr(0); + line->assign(""); + return head; + } + std::string head = line->substr(0, found); + while(line->at(found) == cut) found++; // trim more cut of the rest + line->assign(line->substr(found)); + return head; +} + int toInt(const std::string& s) { return atoi(s.c_str()); } diff --git a/cmds/incident_helper/src/ih_util.h b/cmds/incident_helper/src/ih_util.h index b063b2fe0bba..53f443873e4d 100644 --- a/cmds/incident_helper/src/ih_util.h +++ b/cmds/incident_helper/src/ih_util.h @@ -34,6 +34,8 @@ const std::string DEFAULT_WHITESPACE = " \t"; const std::string DEFAULT_NEWLINE = "\r\n"; const std::string TAB_DELIMITER = "\t"; const std::string COMMA_DELIMITER = ","; +const std::string PIPE_DELIMITER = "|"; +const std::string PARENTHESES_DELIMITER = "()"; // returns true if c is a-zA-Z0-9 or underscore bool isValidChar(char c); @@ -89,6 +91,11 @@ bool stripPrefix(std::string* line, const char* key, bool endAtDelimiter = false bool stripSuffix(std::string* line, const char* key, bool endAtDelimiter = false); /** + * behead the given line by the cut, return the head and reassign the line to be the rest. + */ +std::string behead(std::string* line, const char cut); + +/** * Converts string to the desired type */ int toInt(const std::string& s); diff --git a/cmds/incident_helper/src/main.cpp b/cmds/incident_helper/src/main.cpp index 8c6cd78d3bf2..418dc3fad761 100644 --- a/cmds/incident_helper/src/main.cpp +++ b/cmds/incident_helper/src/main.cpp @@ -19,6 +19,7 @@ #include "parsers/BatteryTypeParser.h" #include "parsers/CpuFreqParser.h" #include "parsers/CpuInfoParser.h" +#include "parsers/EventLogTagsParser.h" #include "parsers/KernelWakesParser.h" #include "parsers/PageTypeInfoParser.h" #include "parsers/ProcrankParser.h" @@ -55,6 +56,8 @@ static TextParserBase* selectParser(int section) { // IDs larger than 1 are section ids reserved in incident.proto case 1000: return new SystemPropertiesParser(); + case 1100: + return new EventLogTagsParser(); case 2000: return new ProcrankParser(); case 2001: diff --git a/cmds/incident_helper/src/parsers/EventLogTagsParser.cpp b/cmds/incident_helper/src/parsers/EventLogTagsParser.cpp new file mode 100644 index 000000000000..73e37bd166cd --- /dev/null +++ b/cmds/incident_helper/src/parsers/EventLogTagsParser.cpp @@ -0,0 +1,84 @@ +/* + * 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/util/event_log_tags.proto.h" +#include "ih_util.h" +#include "EventLogTagsParser.h" + +status_t +EventLogTagsParser::Parse(const int in, const int out) const +{ + Reader reader(in); + string line; + + ProtoOutputStream proto; + + // parse line by line + while (reader.readLine(&line)) { + if (line.empty()) continue; + string debug = line; + string tagNumber = behead(&line, ' '); + string tagName = behead(&line, ' '); + if (tagNumber == "" || tagName == "") { + fprintf(stderr, "Bad line, expect at least two parts: %s[%s, %s]\n", + debug.c_str(), tagNumber.c_str(), tagName.c_str()); + continue; + } + + long long token = proto.start(EventLogTagMapProto::EVENT_LOG_TAGS); + proto.write(EventLogTag::TAG_NUMBER, toInt(tagNumber)); + proto.write(EventLogTag::TAG_NAME, tagName); + + record_t valueDescriptors = parseRecord(line, PARENTHESES_DELIMITER); + for (size_t i = 0; i < valueDescriptors.size(); i++) { + record_t valueDescriptor = parseRecord(valueDescriptors[i], PIPE_DELIMITER); + if (valueDescriptor.size() != 2 && valueDescriptor.size() != 3) { + // If the parts doesn't contains pipe, then skips it. + continue; + } + long long descriptorToken = proto.start(EventLogTag::VALUE_DESCRIPTORS); + proto.write(EventLogTag::ValueDescriptor::NAME, valueDescriptor[0]); + proto.write(EventLogTag::ValueDescriptor::TYPE, toInt(valueDescriptor[1])); + if (valueDescriptor.size() == 3) { + char c = valueDescriptor[2][0]; + int unit = 0; + if (c < '0' || c > '9') { + unit = (int) c; + } else { + unit = toInt(valueDescriptor[2]); + } + proto.write(EventLogTag::ValueDescriptor::UNIT, unit); + } + proto.end(descriptorToken); + } + 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/EventLogTagsParser.h b/cmds/incident_helper/src/parsers/EventLogTagsParser.h new file mode 100644 index 000000000000..79057ce0b3ca --- /dev/null +++ b/cmds/incident_helper/src/parsers/EventLogTagsParser.h @@ -0,0 +1,35 @@ +/* + * 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 EVENT_LOG_TAGS_PARSER_H +#define EVENT_LOG_TAGS_PARSER_H + +#include "TextParserBase.h" + +using namespace android; + +/** + * event.logtags parser, parse file in /system/etc/event-log-tags + */ +class EventLogTagsParser : public TextParserBase { +public: + EventLogTagsParser() : TextParserBase(String8("EventLogTagsParser")) {}; + ~EventLogTagsParser() {}; + + virtual status_t Parse(const int in, const int out) const; +}; + +#endif // EVENT_LOG_TAGS_PARSER_H diff --git a/cmds/incident_helper/testdata/event-log-tags.txt b/cmds/incident_helper/testdata/event-log-tags.txt new file mode 100644 index 000000000000..35396bfb4250 --- /dev/null +++ b/cmds/incident_helper/testdata/event-log-tags.txt @@ -0,0 +1,6 @@ +42 answer (to life the universe etc|3) +314 pi +1004 chatty (dropped|3) +1005 tag_def (tag|1),(name|3),(format|3) +2747 contacts_aggregation (aggregation time|2|3), (count|1|1) +1397638484 snet_event_log (subtag|3) (uid|1) (message|3|s)
\ No newline at end of file diff --git a/cmds/incident_helper/tests/EventLogTagsParser_test.cpp b/cmds/incident_helper/tests/EventLogTagsParser_test.cpp new file mode 100644 index 000000000000..d0d1f1e023a8 --- /dev/null +++ b/cmds/incident_helper/tests/EventLogTagsParser_test.cpp @@ -0,0 +1,125 @@ +/* + * 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 "EventLogTagsParser.h" + +#include "frameworks/base/core/proto/android/util/event_log_tags.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::util; +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 EventLogTagsParserTest : 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(EventLogTagsParserTest, Success) { + const string testFile = kTestDataPath + "event-log-tags.txt"; + + EventLogTagsParser parser; + EventLogTagMapProto expected; + + EventLogTag* eventLogTag; + EventLogTag::ValueDescriptor* desp; + + eventLogTag = expected.add_event_log_tags(); + eventLogTag->set_tag_number(42); + eventLogTag->set_tag_name("answer"); + desp = eventLogTag->add_value_descriptors(); + desp->set_name("to life the universe etc"); + desp->set_type(EventLogTag_ValueDescriptor_DataType_STRING); + + eventLogTag = expected.add_event_log_tags(); + eventLogTag->set_tag_number(314); + eventLogTag->set_tag_name("pi"); + + eventLogTag = expected.add_event_log_tags(); + eventLogTag->set_tag_number(1004); + eventLogTag->set_tag_name("chatty"); + desp = eventLogTag->add_value_descriptors(); + desp->set_name("dropped"); + desp->set_type(EventLogTag_ValueDescriptor_DataType_STRING); + + eventLogTag = expected.add_event_log_tags(); + eventLogTag->set_tag_number(1005); + eventLogTag->set_tag_name("tag_def"); + desp = eventLogTag->add_value_descriptors(); + desp->set_name("tag"); + desp->set_type(EventLogTag_ValueDescriptor_DataType_INT); + desp = eventLogTag->add_value_descriptors(); + desp->set_name("name"); + desp->set_type(EventLogTag_ValueDescriptor_DataType_STRING); + desp = eventLogTag->add_value_descriptors(); + desp->set_name("format"); + desp->set_type(EventLogTag_ValueDescriptor_DataType_STRING); + + eventLogTag = expected.add_event_log_tags(); + eventLogTag->set_tag_number(2747); + eventLogTag->set_tag_name("contacts_aggregation"); + desp = eventLogTag->add_value_descriptors(); + desp->set_name("aggregation time"); + desp->set_type(EventLogTag_ValueDescriptor_DataType_LONG); + desp->set_unit(EventLogTag_ValueDescriptor_DataUnit_MILLISECONDS); + desp = eventLogTag->add_value_descriptors(); + desp->set_name("count"); + desp->set_type(EventLogTag_ValueDescriptor_DataType_INT); + desp->set_unit(EventLogTag_ValueDescriptor_DataUnit_OBJECTS); + + eventLogTag = expected.add_event_log_tags(); + eventLogTag->set_tag_number(1397638484); + eventLogTag->set_tag_name("snet_event_log"); + desp = eventLogTag->add_value_descriptors(); + desp->set_name("subtag"); + desp->set_type(EventLogTag_ValueDescriptor_DataType_STRING); + desp = eventLogTag->add_value_descriptors(); + desp->set_name("uid"); + desp->set_type(EventLogTag_ValueDescriptor_DataType_INT); + desp = eventLogTag->add_value_descriptors(); + desp->set_name("message"); + desp->set_type(EventLogTag_ValueDescriptor_DataType_STRING); + desp->set_unit(EventLogTag_ValueDescriptor_DataUnit_SECONDS); + + 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/ih_util_test.cpp b/cmds/incident_helper/tests/ih_util_test.cpp index 7b8cf52c8bee..efe714d98b18 100644 --- a/cmds/incident_helper/tests/ih_util_test.cpp +++ b/cmds/incident_helper/tests/ih_util_test.cpp @@ -60,6 +60,9 @@ TEST(IhUtilTest, ParseRecord) { result = parseRecord("123,456,78_9", ","); expected = { "123", "456", "78_9" }; EXPECT_EQ(expected, result); + + result = parseRecord("", " "); + EXPECT_TRUE(result.empty()); } TEST(IhUtilTest, ParseRecordByColumns) { @@ -133,6 +136,22 @@ TEST(IhUtilTest, stripSuffix) { EXPECT_THAT(data4, StrEq(" 243%abc")); } +TEST(IhUtilTest, behead) { + string testcase1 = "81002 dropbox_file_copy (a)(b)"; + EXPECT_THAT(behead(&testcase1, ' '), StrEq("81002")); + EXPECT_THAT(behead(&testcase1, ' '), StrEq("dropbox_file_copy")); + EXPECT_THAT(testcase1, "(a)(b)"); + + string testcase2 = "adbce,erwqr"; + EXPECT_THAT(behead(&testcase2, ' '), StrEq("adbce,erwqr")); + EXPECT_THAT(testcase2, ""); + + string testcase3 = "first second"; + EXPECT_THAT(behead(&testcase3, ' '), StrEq("first")); + EXPECT_THAT(behead(&testcase3, ' '), StrEq("second")); + EXPECT_THAT(testcase3, ""); +} + TEST(IhUtilTest, Reader) { TemporaryFile tf; ASSERT_NE(tf.fd, -1); diff --git a/cmds/incidentd/Android.mk b/cmds/incidentd/Android.mk index 11d3e4911761..1cf02d35636c 100644 --- a/cmds/incidentd/Android.mk +++ b/cmds/incidentd/Android.mk @@ -14,6 +14,9 @@ LOCAL_PATH:= $(call my-dir) +# proto files used in incidentd to generate cppstream proto headers. +PROTO_FILES:= frameworks/base/core/proto/android/util/log.proto + # ========= # # incidentd # # ========= # @@ -59,18 +62,34 @@ LOCAL_SHARED_LIBRARIES := \ libutils LOCAL_MODULE_CLASS := EXECUTABLES + gen_src_dir := $(local-generated-sources-dir) -GEN := $(gen_src_dir)/src/section_list.cpp -$(GEN): $(HOST_OUT_EXECUTABLES)/incident-section-gen -$(GEN): PRIVATE_CUSTOM_TOOL = \ +# generate section_list.cpp +GEN_LIST := $(gen_src_dir)/src/section_list.cpp +$(GEN_LIST): $(HOST_OUT_EXECUTABLES)/incident-section-gen +$(GEN_LIST): PRIVATE_CUSTOM_TOOL = \ $(HOST_OUT_EXECUTABLES)/incident-section-gen incidentd > $@ -$(GEN): $(HOST_OUT_EXECUTABLES)/incident-section-gen +$(GEN_LIST): $(HOST_OUT_EXECUTABLES)/incident-section-gen $(transform-generated-source) -LOCAL_GENERATED_SOURCES += $(GEN) +LOCAL_GENERATED_SOURCES += $(GEN_LIST) +GEN_LIST:= + +# generate cppstream proto, add proto files to PROTO_FILES +GEN_PROTO := $(gen_src_dir)/proto.timestamp +$(GEN_PROTO): $(HOST_OUT_EXECUTABLES)/aprotoc $(HOST_OUT_EXECUTABLES)/protoc-gen-cppstream $(PROTO_FILES) +$(GEN_PROTO): PRIVATE_GEN_SRC_DIR := $(gen_src_dir) +$(GEN_PROTO): PRIVATE_CUSTOM_TOOL = \ + $(HOST_OUT_EXECUTABLES)/aprotoc --plugin=protoc-gen-cppstream=$(HOST_OUT_EXECUTABLES)/protoc-gen-cppstream \ + --cppstream_out=$(PRIVATE_GEN_SRC_DIR) -Iexternal/protobuf/src -I . \ + $(PROTO_FILES) \ + && touch $@ +$(GEN_PROTO): $(HOST_OUT_EXECUTABLES)/aprotoc + $(transform-generated-source) +LOCAL_GENERATED_SOURCES += $(GEN_PROTO) +GEN_PROTO:= gen_src_dir:= -GEN:= LOCAL_INIT_RC := incidentd.rc @@ -120,4 +139,22 @@ LOCAL_SHARED_LIBRARIES := \ LOCAL_TEST_DATA := $(call find-test-data-in-subdirs, $(LOCAL_PATH), *, testdata) +LOCAL_MODULE_CLASS := NATIVE_TESTS +gen_src_dir := $(local-generated-sources-dir) +# generate cppstream proto for testing +GEN_PROTO := $(gen_src_dir)/log.proto.timestamp +$(GEN_PROTO): $(HOST_OUT_EXECUTABLES)/aprotoc $(HOST_OUT_EXECUTABLES)/protoc-gen-cppstream $(PROTO_FILES) +$(GEN_PROTO): PRIVATE_GEN_SRC_DIR := $(gen_src_dir) +$(GEN_PROTO): PRIVATE_CUSTOM_TOOL = \ + $(HOST_OUT_EXECUTABLES)/aprotoc --plugin=protoc-gen-cppstream=$(HOST_OUT_EXECUTABLES)/protoc-gen-cppstream \ + --cppstream_out=$(PRIVATE_GEN_SRC_DIR) -Iexternal/protobuf/src -I . \ + $(PROTO_FILES) \ + && touch $@ +$(GEN_PROTO): $(HOST_OUT_EXECUTABLES)/aprotoc + $(transform-generated-source) +LOCAL_GENERATED_SOURCES += $(GEN_PROTO) +GEN_PROTO:= + +gen_src_dir:= + include $(BUILD_NATIVE_TEST) diff --git a/cmds/incidentd/src/Section.cpp b/cmds/incidentd/src/Section.cpp index 22053ef3c53a..61d16f815e65 100644 --- a/cmds/incidentd/src/Section.cpp +++ b/cmds/incidentd/src/Section.cpp @@ -16,21 +16,29 @@ #define LOG_TAG "incidentd" -#include "FdBuffer.h" -#include "Privacy.h" -#include "PrivacyBuffer.h" #include "Section.h" -#include "io_util.h" -#include "section_list.h" +#include <errno.h> +#include <unistd.h> +#include <wait.h> + +#include <memory> +#include <mutex> #include <android/util/protobuf.h> -#include <private/android_filesystem_config.h> #include <binder/IServiceManager.h> -#include <map> -#include <mutex> -#include <wait.h> -#include <unistd.h> +#include <log/log_event_list.h> +#include <log/logprint.h> +#include <log/log_read.h> +#include <private/android_filesystem_config.h> // for AID_NOBODY +#include <private/android_logger.h> + +#include "FdBuffer.h" +#include "frameworks/base/core/proto/android/util/log.proto.h" +#include "io_util.h" +#include "Privacy.h" +#include "PrivacyBuffer.h" +#include "section_list.h" using namespace android::util; using namespace std; @@ -41,7 +49,7 @@ const int FIELD_ID_INCIDENT_HEADER = 1; // incident section parameters const int WAIT_MAX = 5; const struct timespec WAIT_INTERVAL_NS = {0, 200 * 1000 * 1000}; -const char* INCIDENT_HELPER = "/system/bin/incident_helper"; +const char INCIDENT_HELPER[] = "/system/bin/incident_helper"; static pid_t fork_execute_incident_helper(const int id, const char* name, Fpipe& p2cPipe, Fpipe& c2pPipe) @@ -609,3 +617,160 @@ DumpsysSection::BlockingCall(int pipeWriteFd) const return NO_ERROR; } + +// ================================================================================ +// initialization only once in Section.cpp. +map<log_id_t, log_time> LogSection::gLastLogsRetrieved; + +LogSection::LogSection(int id, log_id_t logID) + :WorkerThreadSection(id), + mLogID(logID) +{ + name += "logcat "; + name += android_log_id_to_name(logID); + switch (logID) { + case LOG_ID_EVENTS: + case LOG_ID_STATS: + case LOG_ID_SECURITY: + mBinary = true; + break; + default: + mBinary = false; + } +} + +LogSection::~LogSection() +{ +} + +static size_t +trimTail(char const* buf, size_t len) +{ + while (len > 0) { + char c = buf[len - 1]; + if (c == '\0' || c == ' ' || c == '\n' || c == '\r' || c == ':') { + len--; + } else { + break; + } + } + return len; +} + +static inline int32_t get4LE(uint8_t const* src) { + return src[0] | (src[1] << 8) | (src[2] << 16) | (src[3] << 24); +} + +status_t +LogSection::BlockingCall(int pipeWriteFd) const +{ + status_t err = NO_ERROR; + // Open log buffer and getting logs since last retrieved time if any. + unique_ptr<logger_list, void (*)(logger_list*)> loggers( + gLastLogsRetrieved.find(mLogID) == gLastLogsRetrieved.end() ? + android_logger_list_alloc(ANDROID_LOG_RDONLY | ANDROID_LOG_NONBLOCK, 0, 0) : + android_logger_list_alloc_time(ANDROID_LOG_RDONLY | ANDROID_LOG_NONBLOCK, + gLastLogsRetrieved[mLogID], 0), + android_logger_list_free); + + if (android_logger_open(loggers.get(), mLogID) == NULL) { + ALOGW("LogSection %s: Can't get logger.", this->name.string()); + return err; + } + + log_msg msg; + log_time lastTimestamp(0); + + ProtoOutputStream proto; + while (true) { // keeps reading until logd buffer is fully read. + status_t err = android_logger_list_read(loggers.get(), &msg); + // err = 0 - no content, unexpected connection drop or EOF. + // err = +ive number - size of retrieved data from logger + // err = -ive number, OS supplied error _except_ for -EAGAIN + // err = -EAGAIN, graceful indication for ANDRODI_LOG_NONBLOCK that this is the end of data. + if (err <= 0) { + if (err != -EAGAIN) { + ALOGE("LogSection %s: fails to read a log_msg.\n", this->name.string()); + } + break; + } + if (mBinary) { + // remove the first uint32 which is tag's index in event log tags + android_log_context context = create_android_log_parser(msg.msg() + sizeof(uint32_t), + msg.len() - sizeof(uint32_t));; + android_log_list_element elem; + + lastTimestamp.tv_sec = msg.entry_v1.sec; + lastTimestamp.tv_nsec = msg.entry_v1.nsec; + + // format a BinaryLogEntry + long long token = proto.start(LogProto::BINARY_LOGS); + proto.write(BinaryLogEntry::SEC, msg.entry_v1.sec); + proto.write(BinaryLogEntry::NANOSEC, msg.entry_v1.nsec); + proto.write(BinaryLogEntry::UID, (int) msg.entry_v4.uid); + proto.write(BinaryLogEntry::PID, msg.entry_v1.pid); + proto.write(BinaryLogEntry::TID, msg.entry_v1.tid); + proto.write(BinaryLogEntry::TAG_INDEX, get4LE(reinterpret_cast<uint8_t const*>(msg.msg()))); + do { + elem = android_log_read_next(context); + long long elemToken = proto.start(BinaryLogEntry::ELEMS); + switch (elem.type) { + case EVENT_TYPE_INT: + proto.write(BinaryLogEntry::Elem::TYPE, BinaryLogEntry::Elem::EVENT_TYPE_INT); + proto.write(BinaryLogEntry::Elem::VAL_INT32, (int) elem.data.int32); + break; + case EVENT_TYPE_LONG: + proto.write(BinaryLogEntry::Elem::TYPE, BinaryLogEntry::Elem::EVENT_TYPE_LONG); + proto.write(BinaryLogEntry::Elem::VAL_INT64, (long long) elem.data.int64); + break; + case EVENT_TYPE_STRING: + proto.write(BinaryLogEntry::Elem::TYPE, BinaryLogEntry::Elem::EVENT_TYPE_STRING); + proto.write(BinaryLogEntry::Elem::VAL_STRING, elem.data.string, elem.len); + break; + case EVENT_TYPE_FLOAT: + proto.write(BinaryLogEntry::Elem::TYPE, BinaryLogEntry::Elem::EVENT_TYPE_FLOAT); + proto.write(BinaryLogEntry::Elem::VAL_FLOAT, elem.data.float32); + break; + case EVENT_TYPE_LIST: + proto.write(BinaryLogEntry::Elem::TYPE, BinaryLogEntry::Elem::EVENT_TYPE_LIST); + break; + case EVENT_TYPE_LIST_STOP: + proto.write(BinaryLogEntry::Elem::TYPE, BinaryLogEntry::Elem::EVENT_TYPE_LIST_STOP); + break; + case EVENT_TYPE_UNKNOWN: + proto.write(BinaryLogEntry::Elem::TYPE, BinaryLogEntry::Elem::EVENT_TYPE_UNKNOWN); + break; + } + proto.end(elemToken); + } while ((elem.type != EVENT_TYPE_UNKNOWN) && !elem.complete); + proto.end(token); + if (context) { + android_log_destroy(&context); + } + } else { + AndroidLogEntry entry; + err = android_log_processLogBuffer(&msg.entry_v1, &entry); + if (err != NO_ERROR) { + ALOGE("LogSection %s: fails to process to an entry.\n", this->name.string()); + break; + } + lastTimestamp.tv_sec = entry.tv_sec; + lastTimestamp.tv_nsec = entry.tv_nsec; + + // format a TextLogEntry + long long token = proto.start(LogProto::TEXT_LOGS); + proto.write(TextLogEntry::SEC, (long long)entry.tv_sec); + proto.write(TextLogEntry::NANOSEC, (long long)entry.tv_nsec); + proto.write(TextLogEntry::PRIORITY, (int)entry.priority); + proto.write(TextLogEntry::UID, entry.uid); + proto.write(TextLogEntry::PID, entry.pid); + proto.write(TextLogEntry::TID, entry.tid); + proto.write(TextLogEntry::TAG, entry.tag, trimTail(entry.tag, entry.tagLen)); + proto.write(TextLogEntry::LOG, entry.message, trimTail(entry.message, entry.messageLen)); + proto.end(token); + } + } + gLastLogsRetrieved[mLogID] = lastTimestamp; + proto.flush(pipeWriteFd); + return err; +} diff --git a/cmds/incidentd/src/Section.h b/cmds/incidentd/src/Section.h index 64558a6b732b..d440ee92601c 100644 --- a/cmds/incidentd/src/Section.h +++ b/cmds/incidentd/src/Section.h @@ -19,7 +19,9 @@ #include "Reporter.h" +#include <map> #include <stdarg.h> + #include <utils/String8.h> #include <utils/String16.h> #include <utils/Vector.h> @@ -122,5 +124,24 @@ private: Vector<String16> mArgs; }; +/** + * Section that reads from logd. + */ +class LogSection : public WorkerThreadSection +{ + // global last log retrieved timestamp for each log_id_t. + static map<log_id_t, log_time> gLastLogsRetrieved; + +public: + LogSection(int id, log_id_t logID); + virtual ~LogSection(); + + virtual status_t BlockingCall(int pipeWriteFd) const; + +private: + log_id_t mLogID; + bool mBinary; +}; + #endif // SECTIONS_H diff --git a/cmds/incidentd/src/section_list.h b/cmds/incidentd/src/section_list.h index dfd2312df668..ddc0505df331 100644 --- a/cmds/incidentd/src/section_list.h +++ b/cmds/incidentd/src/section_list.h @@ -17,6 +17,8 @@ #ifndef SECTION_LIST_H #define SECTION_LIST_H +#include <log/log_event_list.h> // include log_id_t enums. + #include "Privacy.h" #include "Section.h" diff --git a/cmds/incidentd/tests/Section_test.cpp b/cmds/incidentd/tests/Section_test.cpp index 0c7876c1ecfb..cbfb89685de0 100644 --- a/cmds/incidentd/tests/Section_test.cpp +++ b/cmds/incidentd/tests/Section_test.cpp @@ -155,7 +155,7 @@ TEST(SectionTest, CommandSectionIncidentHelperTimeout) { } TEST(SectionTest, CommandSectionBadCommand) { - CommandSection cs(NOOP_PARSER, "echo", "about", NULL); + CommandSection cs(NOOP_PARSER, "echoo", "about", NULL); ReportRequestSet requests; ASSERT_EQ(NAME_NOT_FOUND, cs.Execute(&requests)); } @@ -167,6 +167,26 @@ TEST(SectionTest, CommandSectionBadCommandAndTimeout) { ASSERT_EQ(NO_ERROR, cs.Execute(&requests)); } +TEST(SectionTest, LogSectionBinary) { + LogSection ls(1, LOG_ID_EVENTS); + ReportRequestSet requests; + requests.setMainFd(STDOUT_FILENO); + CaptureStdout(); + ASSERT_EQ(NO_ERROR, ls.Execute(&requests)); + string results = GetCapturedStdout(); + EXPECT_FALSE(results.empty()); +} + +TEST(SectionTest, LogSectionSystem) { + LogSection ls(1, LOG_ID_SYSTEM); + ReportRequestSet requests; + requests.setMainFd(STDOUT_FILENO); + CaptureStdout(); + ASSERT_EQ(NO_ERROR, ls.Execute(&requests)); + string results = GetCapturedStdout(); + EXPECT_FALSE(results.empty()); +} + TEST(SectionTest, TestFilterPiiTaggedFields) { TemporaryFile tf; FileSection fs(NOOP_PARSER, tf.path); diff --git a/core/java/android/bluetooth/BluetoothA2dp.java b/core/java/android/bluetooth/BluetoothA2dp.java index 7841b83cf92c..35a21a4eaf9f 100644 --- a/core/java/android/bluetooth/BluetoothA2dp.java +++ b/core/java/android/bluetooth/BluetoothA2dp.java @@ -17,6 +17,7 @@ package android.bluetooth; import android.Manifest; +import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; @@ -103,6 +104,24 @@ public final class BluetoothA2dp implements BluetoothProfile { "android.bluetooth.a2dp.profile.action.AVRCP_CONNECTION_STATE_CHANGED"; /** + * Intent used to broadcast the selection of a connected device as active. + * + * <p>This intent will have one extra: + * <ul> + * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. It can + * be null if no device is active. </li> + * </ul> + * + * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to + * receive. + * + * @hide + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_ACTIVE_DEVICE_CHANGED = + "android.bluetooth.a2dp.profile.action.ACTIVE_DEVICE_CHANGED"; + + /** * Intent used to broadcast the change in the Audio Codec state of the * A2DP Source profile. * @@ -425,6 +444,75 @@ public final class BluetoothA2dp implements BluetoothProfile { } /** + * Select a connected device as active. + * + * The active device selection is per profile. An active device's + * purpose is profile-specific. For example, A2DP audio streaming + * is to the active A2DP Sink device. If a remote device is not + * connected, it cannot be selected as active. + * + * <p> This API returns false in scenarios like the profile on the + * device is not connected or Bluetooth is not turned on. + * When this API returns true, it is guaranteed that the + * {@link #ACTION_ACTIVE_DEVICE_CHANGED} intent will be broadcasted + * with the active device. + * + * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} + * permission. + * + * @param device the remote Bluetooth device. Could be null to clear + * the active device and stop streaming audio to a Bluetooth device. + * @return false on immediate error, true otherwise + * @hide + */ + public boolean setActiveDevice(@Nullable BluetoothDevice device) { + if (DBG) log("setActiveDevice(" + device + ")"); + try { + mServiceLock.readLock().lock(); + if (mService != null && isEnabled() + && ((device == null) || isValidDevice(device))) { + return mService.setActiveDevice(device); + } + if (mService == null) Log.w(TAG, "Proxy not attached to service"); + return false; + } catch (RemoteException e) { + Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); + return false; + } finally { + mServiceLock.readLock().unlock(); + } + } + + /** + * Get the connected device that is active. + * + * <p>Requires {@link android.Manifest.permission#BLUETOOTH} + * permission. + * + * @return the connected device that is active or null if no device + * is active + * @hide + */ + @RequiresPermission(Manifest.permission.BLUETOOTH) + @Nullable + public BluetoothDevice getActiveDevice() { + if (VDBG) log("getActiveDevice()"); + try { + mServiceLock.readLock().lock(); + if (mService != null && isEnabled()) { + return mService.getActiveDevice(); + } + if (mService == null) Log.w(TAG, "Proxy not attached to service"); + return null; + } catch (RemoteException e) { + Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); + return null; + } finally { + mServiceLock.readLock().unlock(); + } + } + + /** * Set priority of the profile * * <p> The device should already be paired. diff --git a/core/java/android/content/pm/PackageBackwardCompatibility.java b/core/java/android/content/pm/PackageBackwardCompatibility.java index cee25994a271..8014c94d198d 100644 --- a/core/java/android/content/pm/PackageBackwardCompatibility.java +++ b/core/java/android/content/pm/PackageBackwardCompatibility.java @@ -16,6 +16,8 @@ package android.content.pm; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.content.pm.PackageParser.Package; import android.os.Build; @@ -56,7 +58,7 @@ public class PackageBackwardCompatibility { boolean apacheHttpLegacyPresent = isLibraryPresent( usesLibraries, usesOptionalLibraries, APACHE_HTTP_LEGACY); if (!apacheHttpLegacyPresent) { - usesLibraries = ArrayUtils.add(usesLibraries, APACHE_HTTP_LEGACY); + usesLibraries = prefix(usesLibraries, APACHE_HTTP_LEGACY); } } @@ -86,4 +88,12 @@ public class PackageBackwardCompatibility { return ArrayUtils.contains(usesLibraries, apacheHttpLegacy) || ArrayUtils.contains(usesOptionalLibraries, apacheHttpLegacy); } + + private static @NonNull <T> ArrayList<T> prefix(@Nullable ArrayList<T> cur, T val) { + if (cur == null) { + cur = new ArrayList<>(); + } + cur.add(0, val); + return cur; + } } diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index 77eb57f25613..367c39dfd8ff 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -85,7 +85,6 @@ import android.util.Pair; import android.util.Slog; import android.util.SparseArray; import android.util.TypedValue; -import android.util.apk.ApkSignatureSchemeV2Verifier; import android.util.apk.ApkSignatureVerifier; import android.view.Gravity; @@ -112,8 +111,7 @@ import java.lang.reflect.Constructor; import java.security.KeyFactory; import java.security.NoSuchAlgorithmException; import java.security.PublicKey; -import java.security.cert.Certificate; -import java.security.cert.CertificateEncodingException; +import java.security.cert.CertificateException; import java.security.spec.EncodedKeySpec; import java.security.spec.InvalidKeySpecException; import java.security.spec.X509EncodedKeySpec; @@ -158,10 +156,6 @@ public class PackageParser { private static final boolean MULTI_PACKAGE_APK_ENABLED = Build.IS_DEBUGGABLE && SystemProperties.getBoolean(PROPERTY_CHILD_PACKAGES_ENABLED, false); - public static final int APK_SIGNING_UNKNOWN = 0; - public static final int APK_SIGNING_V1 = 1; - public static final int APK_SIGNING_V2 = 2; - private static final float DEFAULT_PRE_O_MAX_ASPECT_RATIO = 1.86f; // TODO: switch outError users to PackageParserException @@ -477,8 +471,7 @@ public class PackageParser { public final int revisionCode; public final int installLocation; public final VerifierInfo[] verifiers; - public final Signature[] signatures; - public final Certificate[][] certificates; + public final SigningDetails signingDetails; public final boolean coreApp; public final boolean debuggable; public final boolean multiArch; @@ -486,10 +479,11 @@ public class PackageParser { public final boolean extractNativeLibs; public final boolean isolatedSplits; - public ApkLite(String codePath, String packageName, String splitName, boolean isFeatureSplit, + public ApkLite(String codePath, String packageName, String splitName, + boolean isFeatureSplit, String configForSplit, String usesSplitName, int versionCode, int versionCodeMajor, int revisionCode, int installLocation, List<VerifierInfo> verifiers, - Signature[] signatures, Certificate[][] certificates, boolean coreApp, + SigningDetails signingDetails, boolean coreApp, boolean debuggable, boolean multiArch, boolean use32bitAbi, boolean extractNativeLibs, boolean isolatedSplits) { this.codePath = codePath; @@ -502,9 +496,8 @@ public class PackageParser { this.versionCodeMajor = versionCodeMajor; this.revisionCode = revisionCode; this.installLocation = installLocation; + this.signingDetails = signingDetails; this.verifiers = verifiers.toArray(new VerifierInfo[verifiers.size()]); - this.signatures = signatures; - this.certificates = certificates; this.coreApp = coreApp; this.debuggable = debuggable; this.multiArch = multiArch; @@ -807,10 +800,10 @@ public class PackageParser { } } if ((flags&PackageManager.GET_SIGNATURES) != 0) { - int N = (p.mSignatures != null) ? p.mSignatures.length : 0; - if (N > 0) { - pi.signatures = new Signature[N]; - System.arraycopy(p.mSignatures, 0, pi.signatures, 0, N); + if (p.mSigningDetails.hasSignatures()) { + int numberOfSigs = p.mSigningDetails.signatures.length; + pi.signatures = new Signature[numberOfSigs]; + System.arraycopy(p.mSigningDetails.signatures, 0, pi.signatures, 0, numberOfSigs); } } return pi; @@ -1349,7 +1342,7 @@ public class PackageParser { pkg.setVolumeUuid(volumeUuid); pkg.setApplicationVolumeUuid(volumeUuid); pkg.setBaseCodePath(apkPath); - pkg.setSignatures(null); + pkg.setSigningDetails(SigningDetails.UNKNOWN); return pkg; @@ -1469,57 +1462,19 @@ public class PackageParser { return pkg; } - public static int getApkSigningVersion(Package pkg) { - try { - if (ApkSignatureSchemeV2Verifier.hasSignature(pkg.baseCodePath)) { - return APK_SIGNING_V2; - } - return APK_SIGNING_V1; - } catch (IOException e) { - } - return APK_SIGNING_UNKNOWN; - } - - /** - * Populates the correct packages fields with the given certificates. - * <p> - * This is useful when we've already processed the certificates [such as during package - * installation through an installer session]. We don't re-process the archive and - * simply populate the correct fields. - */ - public static void populateCertificates(Package pkg, Certificate[][] certificates) - throws PackageParserException { - pkg.mCertificates = null; - pkg.mSignatures = null; - pkg.mSigningKeys = null; - - pkg.mCertificates = certificates; - try { - pkg.mSignatures = ApkSignatureVerifier.convertToSignatures(certificates); - } catch (CertificateEncodingException e) { - // certificates weren't encoded properly; something went wrong - throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES, - "Failed to collect certificates from " + pkg.baseCodePath, e); - } - pkg.mSigningKeys = new ArraySet<>(certificates.length); - for (int i = 0; i < certificates.length; i++) { - Certificate[] signerCerts = certificates[i]; - Certificate signerCert = signerCerts[0]; - pkg.mSigningKeys.add(signerCert.getPublicKey()); - } - // add signatures to child packages - final int childCount = (pkg.childPackages != null) ? pkg.childPackages.size() : 0; - for (int i = 0; i < childCount; i++) { - Package childPkg = pkg.childPackages.get(i); - childPkg.mCertificates = pkg.mCertificates; - childPkg.mSignatures = pkg.mSignatures; - childPkg.mSigningKeys = pkg.mSigningKeys; + /** Parses the public keys from the set of signatures. */ + public static ArraySet<PublicKey> toSigningKeys(Signature[] signatures) + throws CertificateException { + ArraySet<PublicKey> keys = new ArraySet<>(signatures.length); + for (int i = 0; i < signatures.length; i++) { + keys.add(signatures[i].getPublicKey()); } + return keys; } /** * Collect certificates from all the APKs described in the given package, - * populating {@link Package#mSignatures}. Also asserts that all APK + * populating {@link Package#mSigningDetails}. Also asserts that all APK * contents are signed correctly and consistently. */ public static void collectCertificates(Package pkg, @ParseFlags int parseFlags) @@ -1528,17 +1483,13 @@ public class PackageParser { final int childCount = (pkg.childPackages != null) ? pkg.childPackages.size() : 0; for (int i = 0; i < childCount; i++) { Package childPkg = pkg.childPackages.get(i); - childPkg.mCertificates = pkg.mCertificates; - childPkg.mSignatures = pkg.mSignatures; - childPkg.mSigningKeys = pkg.mSigningKeys; + childPkg.mSigningDetails = pkg.mSigningDetails; } } private static void collectCertificatesInternal(Package pkg, @ParseFlags int parseFlags) throws PackageParserException { - pkg.mCertificates = null; - pkg.mSignatures = null; - pkg.mSigningKeys = null; + pkg.mSigningDetails = SigningDetails.UNKNOWN; Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "collectCertificates"); try { @@ -1558,12 +1509,12 @@ public class PackageParser { throws PackageParserException { final String apkPath = apkFile.getAbsolutePath(); - int minSignatureScheme = ApkSignatureVerifier.VERSION_JAR_SIGNATURE_SCHEME; + int minSignatureScheme = SigningDetails.SignatureSchemeVersion.JAR; if (pkg.applicationInfo.isStaticSharedLibrary()) { // must use v2 signing scheme - minSignatureScheme = ApkSignatureVerifier.VERSION_APK_SIGNATURE_SCHEME_V2; + minSignatureScheme = SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V2; } - ApkSignatureVerifier.Result verified; + SigningDetails verified; if ((parseFlags & PARSE_IS_SYSTEM_DIR) != 0) { // systemDir APKs are already trusted, save time by not verifying verified = ApkSignatureVerifier.plsCertsNoVerifyOnlyCerts( @@ -1572,7 +1523,7 @@ public class PackageParser { verified = ApkSignatureVerifier.verify(apkPath, minSignatureScheme); } if (verified.signatureSchemeVersion - < ApkSignatureVerifier.VERSION_APK_SIGNATURE_SCHEME_V2) { + < SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V2) { // TODO (b/68860689): move this logic to packagemanagerserivce if ((parseFlags & PARSE_IS_EPHEMERAL) != 0) { throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES, @@ -1583,17 +1534,10 @@ public class PackageParser { // Verify that entries are signed consistently with the first pkg // we encountered. Note that for splits, certificates may have // already been populated during an earlier parse of a base APK. - if (pkg.mCertificates == null) { - pkg.mCertificates = verified.certs; - pkg.mSignatures = verified.sigs; - pkg.mSigningKeys = new ArraySet<>(verified.certs.length); - for (int i = 0; i < verified.certs.length; i++) { - Certificate[] signerCerts = verified.certs[i]; - Certificate signerCert = signerCerts[0]; - pkg.mSigningKeys.add(signerCert.getPublicKey()); - } + if (pkg.mSigningDetails == SigningDetails.UNKNOWN) { + pkg.mSigningDetails = verified; } else { - if (!Signature.areExactMatch(pkg.mSignatures, verified.sigs)) { + if (!Signature.areExactMatch(pkg.mSigningDetails.signatures, verified.signatures)) { throw new PackageParserException( INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES, apkPath + " has mismatched certificates"); @@ -1655,8 +1599,7 @@ public class PackageParser { parser = assets.openXmlResourceParser(cookie, ANDROID_MANIFEST_FILENAME); - final Signature[] signatures; - final Certificate[][] certificates; + final SigningDetails signingDetails; if ((flags & PARSE_COLLECT_CERTIFICATES) != 0) { // TODO: factor signature related items out of Package object final Package tempPkg = new Package((String) null); @@ -1666,15 +1609,13 @@ public class PackageParser { } finally { Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); } - signatures = tempPkg.mSignatures; - certificates = tempPkg.mCertificates; + signingDetails = tempPkg.mSigningDetails; } else { - signatures = null; - certificates = null; + signingDetails = SigningDetails.UNKNOWN; } final AttributeSet attrs = parser; - return parseApkLite(apkPath, parser, attrs, signatures, certificates); + return parseApkLite(apkPath, parser, attrs, signingDetails); } catch (XmlPullParserException | IOException | RuntimeException e) { Slog.w(TAG, "Failed to parse " + apkPath, e); @@ -1761,7 +1702,7 @@ public class PackageParser { } private static ApkLite parseApkLite(String codePath, XmlPullParser parser, AttributeSet attrs, - Signature[] signatures, Certificate[][] certificates) + SigningDetails signingDetails) throws IOException, XmlPullParserException, PackageParserException { final Pair<String, String> packageSplit = parsePackageSplitNames(parser, attrs); @@ -1854,7 +1795,7 @@ public class PackageParser { return new ApkLite(codePath, packageSplit.first, packageSplit.second, isFeatureSplit, configForSplit, usesSplitName, versionCode, versionCodeMajor, revisionCode, - installLocation, verifiers, signatures, certificates, coreApp, debuggable, + installLocation, verifiers, signingDetails, coreApp, debuggable, multiArch, use32bitAbi, extractNativeLibs, isolatedSplits); } @@ -5734,6 +5675,117 @@ public class PackageParser { return true; } + /** A container for signing-related data of an application package. */ + public static final class SigningDetails implements Parcelable { + + @IntDef({SigningDetails.SignatureSchemeVersion.UNKNOWN, + SigningDetails.SignatureSchemeVersion.JAR, + SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V2, + SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3}) + public @interface SignatureSchemeVersion { + int UNKNOWN = 0; + int JAR = 1; + int SIGNING_BLOCK_V2 = 2; + int SIGNING_BLOCK_V3 = 3; + } + + @Nullable + public final Signature[] signatures; + @SignatureSchemeVersion + public final int signatureSchemeVersion; + @Nullable + public final ArraySet<PublicKey> publicKeys; + + /** A representation of unknown signing details. Use instead of null. */ + public static final SigningDetails UNKNOWN = + new SigningDetails(null, SignatureSchemeVersion.UNKNOWN, null); + + @VisibleForTesting + public SigningDetails(Signature[] signatures, + @SignatureSchemeVersion int signatureSchemeVersion, + ArraySet<PublicKey> keys) { + this.signatures = signatures; + this.signatureSchemeVersion = signatureSchemeVersion; + this.publicKeys = keys; + } + + public SigningDetails(Signature[] signatures, + @SignatureSchemeVersion int signatureSchemeVersion) + throws CertificateException { + this(signatures, signatureSchemeVersion, toSigningKeys(signatures)); + } + + /** Returns true if the signing details have one or more signatures. */ + public boolean hasSignatures() { + return signatures != null && signatures.length > 0; + } + + /** Returns true if the signatures in this and other match exactly. */ + public boolean signaturesMatchExactly(SigningDetails other) { + return Signature.areExactMatch(this.signatures, other.signatures); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + boolean isUnknown = UNKNOWN == this; + dest.writeBoolean(isUnknown); + if (isUnknown) { + return; + } + dest.writeTypedArray(this.signatures, flags); + dest.writeInt(this.signatureSchemeVersion); + dest.writeArraySet(this.publicKeys); + } + + protected SigningDetails(Parcel in) { + final ClassLoader boot = Object.class.getClassLoader(); + this.signatures = in.createTypedArray(Signature.CREATOR); + this.signatureSchemeVersion = in.readInt(); + this.publicKeys = (ArraySet<PublicKey>) in.readArraySet(boot); + } + + public static final Creator<SigningDetails> CREATOR = new Creator<SigningDetails>() { + @Override + public SigningDetails createFromParcel(Parcel source) { + if (source.readBoolean()) { + return UNKNOWN; + } + return new SigningDetails(source); + } + + @Override + public SigningDetails[] newArray(int size) { + return new SigningDetails[size]; + } + }; + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof SigningDetails)) return false; + + SigningDetails that = (SigningDetails) o; + + if (signatureSchemeVersion != that.signatureSchemeVersion) return false; + if (!Signature.areExactMatch(signatures, that.signatures)) return false; + return publicKeys != null ? publicKeys.equals(that.publicKeys) + : that.publicKeys == null; + } + + @Override + public int hashCode() { + int result = +Arrays.hashCode(signatures); + result = 31 * result + signatureSchemeVersion; + result = 31 * result + (publicKeys != null ? publicKeys.hashCode() : 0); + return result; + } + } + /** * Representation of a full package parsed from APK files on disk. A package * consists of a single base APK, and zero or more split APKs. @@ -5840,8 +5892,7 @@ public class PackageParser { public int mSharedUserLabel; // Signatures that were read from the package. - public Signature[] mSignatures; - public Certificate[][] mCertificates; + @NonNull public SigningDetails mSigningDetails = SigningDetails.UNKNOWN; // For use by package manager service for quick lookup of // preferred up order. @@ -5893,7 +5944,6 @@ public class PackageParser { /** * Data used to feed the KeySetManagerService */ - public ArraySet<PublicKey> mSigningKeys; public ArraySet<String> mUpgradeKeySets; public ArrayMap<String, ArraySet<PublicKey>> mKeySetMapping; @@ -6037,12 +6087,13 @@ public class PackageParser { } } - public void setSignatures(Signature[] signatures) { - this.mSignatures = signatures; + /** Sets signing details on the package and any of its children. */ + public void setSigningDetails(@NonNull SigningDetails signingDetails) { + mSigningDetails = signingDetails; if (childPackages != null) { final int packageCount = childPackages.size(); for (int i = 0; i < packageCount; i++) { - childPackages.get(i).mSignatures = signatures; + childPackages.get(i).mSigningDetails = signingDetails; } } } @@ -6348,8 +6399,7 @@ public class PackageParser { } mSharedUserLabel = dest.readInt(); - mSignatures = (Signature[]) dest.readParcelableArray(boot, Signature.class); - mCertificates = (Certificate[][]) dest.readSerializable(); + mSigningDetails = dest.readParcelable(boot); mPreferredOrder = dest.readInt(); @@ -6389,7 +6439,6 @@ public class PackageParser { mTrustedOverlay = (dest.readInt() == 1); mCompileSdkVersion = dest.readInt(); mCompileSdkVersionCodename = dest.readString(); - mSigningKeys = (ArraySet<PublicKey>) dest.readArraySet(boot); mUpgradeKeySets = (ArraySet<String>) dest.readArraySet(boot); mKeySetMapping = readKeySetMapping(dest); @@ -6489,8 +6538,7 @@ public class PackageParser { dest.writeString(mSharedUserId); dest.writeInt(mSharedUserLabel); - dest.writeParcelableArray(mSignatures, flags); - dest.writeSerializable(mCertificates); + dest.writeParcelable(mSigningDetails, flags); dest.writeInt(mPreferredOrder); @@ -6515,7 +6563,6 @@ public class PackageParser { dest.writeInt(mTrustedOverlay ? 1 : 0); dest.writeInt(mCompileSdkVersion); dest.writeString(mCompileSdkVersionCodename); - dest.writeArraySet(mSigningKeys); dest.writeArraySet(mUpgradeKeySets); writeKeySetMapping(dest, mKeySetMapping); dest.writeString(cpuAbiOverride); diff --git a/core/java/android/net/IpSecConfig.java b/core/java/android/net/IpSecConfig.java index e6cd3fc130a0..f54ceb5c142a 100644 --- a/core/java/android/net/IpSecConfig.java +++ b/core/java/android/net/IpSecConfig.java @@ -102,17 +102,11 @@ public final class IpSecConfig implements Parcelable { /** Set the local IP address for Tunnel mode */ public void setLocalAddress(String localAddress) { - if (localAddress == null) { - throw new IllegalArgumentException("localAddress may not be null!"); - } mLocalAddress = localAddress; } /** Set the remote IP address for this IPsec transform */ public void setRemoteAddress(String remoteAddress) { - if (remoteAddress == null) { - throw new IllegalArgumentException("remoteAddress may not be null!"); - } mRemoteAddress = remoteAddress; } diff --git a/core/java/android/net/IpSecManager.java b/core/java/android/net/IpSecManager.java index 6a4b8914780c..34cfa9b2153d 100644 --- a/core/java/android/net/IpSecManager.java +++ b/core/java/android/net/IpSecManager.java @@ -69,7 +69,7 @@ public final class IpSecManager { } /** @hide */ - public static final int INVALID_RESOURCE_ID = 0; + public static final int INVALID_RESOURCE_ID = -1; /** * Thrown to indicate that a requested SPI is in use. @@ -128,7 +128,7 @@ public final class IpSecManager { private final InetAddress mRemoteAddress; private final CloseGuard mCloseGuard = CloseGuard.get(); private int mSpi = INVALID_SECURITY_PARAMETER_INDEX; - private int mResourceId; + private int mResourceId = INVALID_RESOURCE_ID; /** Get the underlying SPI held by this object. */ public int getSpi() { @@ -146,6 +146,7 @@ public final class IpSecManager { public void close() { try { mService.releaseSecurityParameterIndex(mResourceId); + mResourceId = INVALID_RESOURCE_ID; } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -501,7 +502,7 @@ public final class IpSecManager { public static final class UdpEncapsulationSocket implements AutoCloseable { private final ParcelFileDescriptor mPfd; private final IIpSecService mService; - private final int mResourceId; + private int mResourceId = INVALID_RESOURCE_ID; private final int mPort; private final CloseGuard mCloseGuard = CloseGuard.get(); @@ -554,6 +555,7 @@ public final class IpSecManager { public void close() throws IOException { try { mService.closeUdpEncapsulationSocket(mResourceId); + mResourceId = INVALID_RESOURCE_ID; } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/android/net/IpSecTransform.java b/core/java/android/net/IpSecTransform.java index 7cd742b417a4..102ba6d94faa 100644 --- a/core/java/android/net/IpSecTransform.java +++ b/core/java/android/net/IpSecTransform.java @@ -347,6 +347,9 @@ public final class IpSecTransform implements AutoCloseable { */ public IpSecTransform.Builder setSpi( @TransformDirection int direction, IpSecManager.SecurityParameterIndex spi) { + if (spi.getResourceId() == INVALID_RESOURCE_ID) { + throw new IllegalArgumentException("Invalid SecurityParameterIndex"); + } mConfig.setSpiResourceId(direction, spi.getResourceId()); return this; } @@ -381,6 +384,9 @@ public final class IpSecTransform implements AutoCloseable { public IpSecTransform.Builder setIpv4Encapsulation( IpSecManager.UdpEncapsulationSocket localSocket, int remotePort) { mConfig.setEncapType(ENCAP_ESPINUDP); + if (localSocket.getResourceId() == INVALID_RESOURCE_ID) { + throw new IllegalArgumentException("Invalid UdpEncapsulationSocket"); + } mConfig.setEncapSocketResourceId(localSocket.getResourceId()); mConfig.setEncapRemotePort(remotePort); return this; @@ -426,6 +432,9 @@ public final class IpSecTransform implements AutoCloseable { public IpSecTransform buildTransportModeTransform(InetAddress remoteAddress) throws IpSecManager.ResourceUnavailableException, IpSecManager.SpiUnavailableException, IOException { + if (remoteAddress == null) { + throw new IllegalArgumentException("Remote address may not be null or empty!"); + } mConfig.setMode(MODE_TRANSPORT); mConfig.setRemoteAddress(remoteAddress.getHostAddress()); // FIXME: modifying a builder after calling build can change the built transform. @@ -447,8 +456,12 @@ public final class IpSecTransform implements AutoCloseable { */ public IpSecTransform buildTunnelModeTransform( InetAddress localAddress, InetAddress remoteAddress) { - // FIXME: argument validation here - // throw new IllegalArgumentException("Natt Keepalive requires UDP Encapsulation"); + if (localAddress == null) { + throw new IllegalArgumentException("Local address may not be null or empty!"); + } + if (remoteAddress == null) { + throw new IllegalArgumentException("Remote address may not be null or empty!"); + } mConfig.setLocalAddress(localAddress.getHostAddress()); mConfig.setRemoteAddress(remoteAddress.getHostAddress()); mConfig.setMode(MODE_TUNNEL); diff --git a/core/java/android/os/HidlSupport.java b/core/java/android/os/HidlSupport.java index a080c8dcbcc5..4d7d9319c94e 100644 --- a/core/java/android/os/HidlSupport.java +++ b/core/java/android/os/HidlSupport.java @@ -86,6 +86,25 @@ public class HidlSupport { } /** + * Class which can be used to fetch an object out of a lambda. Fetching an object + * out of a local scope with HIDL is a common operation (although usually it can + * and should be avoided). + * + * @param <E> Inner object type. + */ + public static final class Mutable<E> { + public E value; + + public Mutable() { + value = null; + } + + public Mutable(E value) { + this.value = value; + } + } + + /** * Similar to Arrays.deepHashCode, but also take care of lists. */ public static int deepHashCode(Object o) { diff --git a/core/java/android/util/apk/ApkSignatureVerifier.java b/core/java/android/util/apk/ApkSignatureVerifier.java index 81467292d491..555c4740389a 100644 --- a/core/java/android/util/apk/ApkSignatureVerifier.java +++ b/core/java/android/util/apk/ApkSignatureVerifier.java @@ -25,6 +25,7 @@ import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER; import android.content.pm.PackageParser; import android.content.pm.PackageParser.PackageParserException; +import android.content.pm.PackageParser.SigningDetails.SignatureSchemeVersion; import android.content.pm.Signature; import android.os.Trace; import android.util.jar.StrictJarFile; @@ -52,10 +53,6 @@ import java.util.zip.ZipEntry; */ 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<>(); /** @@ -63,10 +60,11 @@ public class ApkSignatureVerifier { * * @throws PackageParserException if the APK's signature failed to verify. */ - public static Result verify(String apkPath, int minSignatureSchemeVersion) + public static PackageParser.SigningDetails verify(String apkPath, + @SignatureSchemeVersion int minSignatureSchemeVersion) throws PackageParserException { - if (minSignatureSchemeVersion > VERSION_APK_SIGNATURE_SCHEME_V3) { + if (minSignatureSchemeVersion > SignatureSchemeVersion.SIGNING_BLOCK_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 @@ -80,10 +78,11 @@ public class ApkSignatureVerifier { ApkSignatureSchemeV3Verifier.verify(apkPath); Certificate[][] signerCerts = new Certificate[][] { vSigner.certs }; Signature[] signerSigs = convertToSignatures(signerCerts); - return new Result(signerCerts, signerSigs, VERSION_APK_SIGNATURE_SCHEME_V3); + return new PackageParser.SigningDetails(signerSigs, + SignatureSchemeVersion.SIGNING_BLOCK_V3); } catch (SignatureNotFoundException e) { // not signed with v2, try older if allowed - if (minSignatureSchemeVersion >= VERSION_APK_SIGNATURE_SCHEME_V3) { + if (minSignatureSchemeVersion >= SignatureSchemeVersion.SIGNING_BLOCK_V3) { throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES, "No APK Signature Scheme v3 signature in package " + apkPath, e); } @@ -97,7 +96,7 @@ public class ApkSignatureVerifier { } // redundant, protective version check - if (minSignatureSchemeVersion > VERSION_APK_SIGNATURE_SCHEME_V2) { + if (minSignatureSchemeVersion > SignatureSchemeVersion.SIGNING_BLOCK_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 @@ -110,10 +109,11 @@ public class ApkSignatureVerifier { Certificate[][] signerCerts = ApkSignatureSchemeV2Verifier.verify(apkPath); Signature[] signerSigs = convertToSignatures(signerCerts); - return new Result(signerCerts, signerSigs, VERSION_APK_SIGNATURE_SCHEME_V2); + return new PackageParser.SigningDetails( + signerSigs, SignatureSchemeVersion.SIGNING_BLOCK_V2); } catch (SignatureNotFoundException e) { // not signed with v2, try older if allowed - if (minSignatureSchemeVersion >= VERSION_APK_SIGNATURE_SCHEME_V2) { + if (minSignatureSchemeVersion >= SignatureSchemeVersion.SIGNING_BLOCK_V2) { throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES, "No APK Signature Scheme v2 signature in package " + apkPath, e); } @@ -127,7 +127,7 @@ public class ApkSignatureVerifier { } // redundant, protective version check - if (minSignatureSchemeVersion > VERSION_JAR_SIGNATURE_SCHEME) { + if (minSignatureSchemeVersion > SignatureSchemeVersion.JAR) { // 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 @@ -145,7 +145,8 @@ public class ApkSignatureVerifier { * * @throws PackageParserException if there was a problem collecting certificates */ - private static Result verifyV1Signature(String apkPath, boolean verifyFull) + private static PackageParser.SigningDetails verifyV1Signature( + String apkPath, boolean verifyFull) throws PackageParserException { StrictJarFile jarFile = null; @@ -211,7 +212,7 @@ public class ApkSignatureVerifier { } } } - return new Result(lastCerts, lastSigs, VERSION_JAR_SIGNATURE_SCHEME); + return new PackageParser.SigningDetails(lastSigs, SignatureSchemeVersion.JAR); } catch (GeneralSecurityException e) { throw new PackageParserException(INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING, "Failed to collect certificates from " + apkPath, e); @@ -289,10 +290,11 @@ public class ApkSignatureVerifier { * @throws PackageParserException if the APK's signature failed to verify. * or greater is not found, except in the case of no JAR signature. */ - public static Result plsCertsNoVerifyOnlyCerts(String apkPath, int minSignatureSchemeVersion) + public static PackageParser.SigningDetails plsCertsNoVerifyOnlyCerts( + String apkPath, int minSignatureSchemeVersion) throws PackageParserException { - if (minSignatureSchemeVersion > VERSION_APK_SIGNATURE_SCHEME_V3) { + if (minSignatureSchemeVersion > SignatureSchemeVersion.SIGNING_BLOCK_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 @@ -306,10 +308,11 @@ public class ApkSignatureVerifier { ApkSignatureSchemeV3Verifier.plsCertsNoVerifyOnlyCerts(apkPath); Certificate[][] signerCerts = new Certificate[][] { vSigner.certs }; Signature[] signerSigs = convertToSignatures(signerCerts); - return new Result(signerCerts, signerSigs, VERSION_APK_SIGNATURE_SCHEME_V3); + return new PackageParser.SigningDetails(signerSigs, + SignatureSchemeVersion.SIGNING_BLOCK_V3); } catch (SignatureNotFoundException e) { // not signed with v2, try older if allowed - if (minSignatureSchemeVersion >= VERSION_APK_SIGNATURE_SCHEME_V3) { + if (minSignatureSchemeVersion >= SignatureSchemeVersion.SIGNING_BLOCK_V3) { throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES, "No APK Signature Scheme v3 signature in package " + apkPath, e); } @@ -323,7 +326,7 @@ public class ApkSignatureVerifier { } // redundant, protective version check - if (minSignatureSchemeVersion > VERSION_APK_SIGNATURE_SCHEME_V2) { + if (minSignatureSchemeVersion > SignatureSchemeVersion.SIGNING_BLOCK_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 @@ -336,10 +339,11 @@ public class ApkSignatureVerifier { Certificate[][] signerCerts = ApkSignatureSchemeV2Verifier.plsCertsNoVerifyOnlyCerts(apkPath); Signature[] signerSigs = convertToSignatures(signerCerts); - return new Result(signerCerts, signerSigs, VERSION_APK_SIGNATURE_SCHEME_V2); + return new PackageParser.SigningDetails(signerSigs, + SignatureSchemeVersion.SIGNING_BLOCK_V2); } catch (SignatureNotFoundException e) { // not signed with v2, try older if allowed - if (minSignatureSchemeVersion >= VERSION_APK_SIGNATURE_SCHEME_V2) { + if (minSignatureSchemeVersion >= SignatureSchemeVersion.SIGNING_BLOCK_V2) { throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES, "No APK Signature Scheme v2 signature in package " + apkPath, e); } @@ -353,7 +357,7 @@ public class ApkSignatureVerifier { } // redundant, protective version check - if (minSignatureSchemeVersion > VERSION_JAR_SIGNATURE_SCHEME) { + if (minSignatureSchemeVersion > SignatureSchemeVersion.JAR) { // 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 @@ -363,19 +367,4 @@ public class ApkSignatureVerifier { // v2 didn't work, try jarsigner return verifyV1Signature(apkPath, false); } - - /** - * Result of a successful APK verification operation. - */ - public static class Result { - public final Certificate[][] certs; - public final Signature[] sigs; - public final int signatureSchemeVersion; - - public Result(Certificate[][] certs, Signature[] sigs, int signingVersion) { - this.certs = certs; - this.sigs = sigs; - this.signatureSchemeVersion = signingVersion; - } - } } diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 73d53d4dfc8f..dc54127d85b2 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -311,7 +311,6 @@ import java.util.Locale; public class TextView extends View implements ViewTreeObserver.OnPreDrawListener { static final String LOG_TAG = "TextView"; static final boolean DEBUG_EXTRACT = false; - static final boolean DEBUG_AUTOFILL = false; private static final float[] TEMP_POSITION = new float[2]; // Enum for the "typeface" XML parameter. @@ -9516,7 +9515,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } final AutofillManager afm = mContext.getSystemService(AutofillManager.class); if (afm != null) { - if (DEBUG_AUTOFILL) { + if (android.view.autofill.Helper.sVerbose) { Log.v(LOG_TAG, "sendAfterTextChanged(): notify AFM for text=" + mText); } afm.notifyValueChanged(TextView.this); @@ -10296,7 +10295,14 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (forAutofill) { structure.setDataIsSensitive(!mSetFromXmlOrResourceId); if (mTextId != ResourceId.ID_NULL) { - structure.setTextIdEntry(getResources().getResourceEntryName(mTextId)); + try { + structure.setTextIdEntry(getResources().getResourceEntryName(mTextId)); + } catch (Resources.NotFoundException e) { + if (android.view.autofill.Helper.sVerbose) { + Log.v(LOG_TAG, "onProvideAutofillStructure(): cannot set name for text id " + + mTextId + ": " + e.getMessage()); + } + } } } diff --git a/core/jni/android/graphics/CreateJavaOutputStreamAdaptor.cpp b/core/jni/android/graphics/CreateJavaOutputStreamAdaptor.cpp index de2b7c3db11d..4257c981be18 100644 --- a/core/jni/android/graphics/CreateJavaOutputStreamAdaptor.cpp +++ b/core/jni/android/graphics/CreateJavaOutputStreamAdaptor.cpp @@ -12,21 +12,62 @@ static jmethodID gInputStream_readMethodID; static jmethodID gInputStream_skipMethodID; +// FIXME: Share with ByteBufferStreamAdaptor.cpp? +static JNIEnv* get_env_or_die(JavaVM* jvm) { + JNIEnv* env; + if (jvm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) { + LOG_ALWAYS_FATAL("Failed to get JNIEnv for JavaVM: %p", jvm); + } + return env; +} + /** * Wrapper for a Java InputStream. */ class JavaInputStreamAdaptor : public SkStream { + JavaInputStreamAdaptor(JavaVM* jvm, jobject js, jbyteArray ar, jint capacity, + bool swallowExceptions) + : fJvm(jvm) + , fJavaInputStream(js) + , fJavaByteArray(ar) + , fCapacity(capacity) + , fBytesRead(0) + , fIsAtEnd(false) + , fSwallowExceptions(swallowExceptions) {} + public: - JavaInputStreamAdaptor(JNIEnv* env, jobject js, jbyteArray ar) - : fEnv(env), fJavaInputStream(js), fJavaByteArray(ar) { - SkASSERT(ar); - fCapacity = env->GetArrayLength(ar); - SkASSERT(fCapacity > 0); - fBytesRead = 0; - fIsAtEnd = false; + static JavaInputStreamAdaptor* Create(JNIEnv* env, jobject js, jbyteArray ar, + bool swallowExceptions) { + JavaVM* jvm; + LOG_ALWAYS_FATAL_IF(env->GetJavaVM(&jvm) != JNI_OK); + + js = env->NewGlobalRef(js); + if (!js) { + return nullptr; + } + + ar = (jbyteArray) env->NewGlobalRef(ar); + if (!ar) { + env->DeleteGlobalRef(js); + return nullptr; + } + + jint capacity = env->GetArrayLength(ar); + return new JavaInputStreamAdaptor(jvm, js, ar, capacity, swallowExceptions); + } + + ~JavaInputStreamAdaptor() override { + auto* env = get_env_or_die(fJvm); + env->DeleteGlobalRef(fJavaInputStream); + env->DeleteGlobalRef(fJavaByteArray); } - virtual size_t read(void* buffer, size_t size) { + size_t read(void* buffer, size_t size) override { + auto* env = get_env_or_die(fJvm); + if (!fSwallowExceptions && checkException(env)) { + // Just in case the caller did not clear from a previous exception. + return 0; + } if (NULL == buffer) { if (0 == size) { return 0; @@ -37,10 +78,10 @@ public: */ size_t amountSkipped = 0; do { - size_t amount = this->doSkip(size - amountSkipped); + size_t amount = this->doSkip(size - amountSkipped, env); if (0 == amount) { char tmp; - amount = this->doRead(&tmp, 1); + amount = this->doRead(&tmp, 1, env); if (0 == amount) { // if read returned 0, we're at EOF fIsAtEnd = true; @@ -52,16 +93,13 @@ public: return amountSkipped; } } - return this->doRead(buffer, size); + return this->doRead(buffer, size, env); } - virtual bool isAtEnd() const { - return fIsAtEnd; - } + bool isAtEnd() const override { return fIsAtEnd; } private: - size_t doRead(void* buffer, size_t size) { - JNIEnv* env = fEnv; + size_t doRead(void* buffer, size_t size, JNIEnv* env) { size_t bytesRead = 0; // read the bytes do { @@ -76,13 +114,9 @@ private: jint n = env->CallIntMethod(fJavaInputStream, gInputStream_readMethodID, fJavaByteArray, 0, requested); - if (env->ExceptionCheck()) { - env->ExceptionDescribe(); - env->ExceptionClear(); + if (checkException(env)) { SkDebugf("---- read threw an exception\n"); - // Consider the stream to be at the end, since there was an error. - fIsAtEnd = true; - return 0; + return bytesRead; } if (n < 0) { // n == 0 should not be possible, see InputStream read() specifications. @@ -92,14 +126,9 @@ private: env->GetByteArrayRegion(fJavaByteArray, 0, n, reinterpret_cast<jbyte*>(buffer)); - if (env->ExceptionCheck()) { - env->ExceptionDescribe(); - env->ExceptionClear(); + if (checkException(env)) { SkDebugf("---- read:GetByteArrayRegion threw an exception\n"); - // The error was not with the stream itself, but consider it to be at the - // end, since we do not have a way to recover. - fIsAtEnd = true; - return 0; + return bytesRead; } buffer = (void*)((char*)buffer + n); @@ -111,14 +140,10 @@ private: return bytesRead; } - size_t doSkip(size_t size) { - JNIEnv* env = fEnv; - + size_t doSkip(size_t size, JNIEnv* env) { jlong skipped = env->CallLongMethod(fJavaInputStream, gInputStream_skipMethodID, (jlong)size); - if (env->ExceptionCheck()) { - env->ExceptionDescribe(); - env->ExceptionClear(); + if (checkException(env)) { SkDebugf("------- skip threw an exception\n"); return 0; } @@ -129,20 +154,37 @@ private: return (size_t)skipped; } - JNIEnv* fEnv; - jobject fJavaInputStream; // the caller owns this object - jbyteArray fJavaByteArray; // the caller owns this object - jint fCapacity; + bool checkException(JNIEnv* env) { + if (!env->ExceptionCheck()) { + return false; + } + + env->ExceptionDescribe(); + if (fSwallowExceptions) { + env->ExceptionClear(); + } + + // There is no way to recover from the error, so consider the stream + // to be at the end. + fIsAtEnd = true; + + return true; + } + + JavaVM* fJvm; + jobject fJavaInputStream; + jbyteArray fJavaByteArray; + const jint fCapacity; size_t fBytesRead; bool fIsAtEnd; + const bool fSwallowExceptions; }; -SkStream* CreateJavaInputStreamAdaptor(JNIEnv* env, jobject stream, - jbyteArray storage) { - return new JavaInputStreamAdaptor(env, stream, storage); +SkStream* CreateJavaInputStreamAdaptor(JNIEnv* env, jobject stream, jbyteArray storage, + bool swallowExceptions) { + return JavaInputStreamAdaptor::Create(env, stream, storage, swallowExceptions); } - static SkMemoryStream* adaptor_to_mem_stream(SkStream* stream) { SkASSERT(stream != NULL); size_t bufferSize = 4096; diff --git a/core/jni/android/graphics/CreateJavaOutputStreamAdaptor.h b/core/jni/android/graphics/CreateJavaOutputStreamAdaptor.h index 56cba51222a0..fccd4717c4b7 100644 --- a/core/jni/android/graphics/CreateJavaOutputStreamAdaptor.h +++ b/core/jni/android/graphics/CreateJavaOutputStreamAdaptor.h @@ -16,13 +16,16 @@ class SkWStream; * @param stream Pointer to Java InputStream. * @param storage Java byte array for retrieving data from the * Java InputStream. + * @param swallowExceptions Whether to call ExceptionClear() after + * an Exception is thrown. If false, it is up to the client to + * clear or propagate the exception. * @return SkStream Simple subclass of SkStream which supports its * basic methods like reading. Only valid until the calling * function returns, since the Java InputStream is not managed * by the SkStream. */ -SkStream* CreateJavaInputStreamAdaptor(JNIEnv* env, jobject stream, - jbyteArray storage); +SkStream* CreateJavaInputStreamAdaptor(JNIEnv* env, jobject stream, jbyteArray storage, + bool swallowExceptions = true); /** * Copy a Java InputStream. The result will be rewindable. @@ -33,10 +36,8 @@ SkStream* CreateJavaInputStreamAdaptor(JNIEnv* env, jobject stream, * @return SkStreamRewindable The data in stream will be copied * to a new SkStreamRewindable. */ -SkStreamRewindable* CopyJavaInputStream(JNIEnv* env, jobject stream, - jbyteArray storage); +SkStreamRewindable* CopyJavaInputStream(JNIEnv* env, jobject stream, jbyteArray storage); -SkWStream* CreateJavaOutputStreamAdaptor(JNIEnv* env, jobject stream, - jbyteArray storage); +SkWStream* CreateJavaOutputStreamAdaptor(JNIEnv* env, jobject stream, jbyteArray storage); #endif // _ANDROID_GRAPHICS_CREATE_JAVA_OUTPUT_STREAM_ADAPTOR_H_ diff --git a/core/jni/android/graphics/ImageDecoder.cpp b/core/jni/android/graphics/ImageDecoder.cpp index bacab2a304cc..5bdad08e3e1e 100644 --- a/core/jni/android/graphics/ImageDecoder.cpp +++ b/core/jni/android/graphics/ImageDecoder.cpp @@ -16,6 +16,7 @@ #include "Bitmap.h" #include "ByteBufferStreamAdaptor.h" +#include "CreateJavaOutputStreamAdaptor.h" #include "GraphicsJNI.h" #include "NinePatchPeeker.h" #include "Utils.h" @@ -26,10 +27,12 @@ #include <SkAndroidCodec.h> #include <SkEncodedImageFormat.h> +#include <SkFrontBufferedStream.h> #include <SkStream.h> #include <androidfw/Asset.h> #include <jni.h> +#include <sys/stat.h> using namespace android; @@ -69,15 +72,15 @@ struct ImageDecoder { static jobject native_create(JNIEnv* env, std::unique_ptr<SkStream> stream) { if (!stream.get()) { - return nullObjectReturn("Failed to create a stream"); + doThrowIOE(env, "Failed to create a stream"); + return nullptr; } std::unique_ptr<ImageDecoder> decoder(new ImageDecoder); decoder->mCodec = SkAndroidCodec::MakeFromStream(std::move(stream), &decoder->mPeeker); if (!decoder->mCodec.get()) { - // FIXME: Add an error code to SkAndroidCodec::MakeFromStream, like - // SkCodec? Then this can print a more informative error message. - // (Or we can print one from within SkCodec.) - ALOGE("Failed to create an SkCodec"); + // FIXME: (b/71578461) Use the error message from + // SkCodec::MakeFromStream to report a more informative error message. + doThrowIOE(env, "Failed to create an SkCodec"); return nullptr; } @@ -88,7 +91,52 @@ static jobject native_create(JNIEnv* env, std::unique_ptr<SkStream> stream) { reinterpret_cast<jlong>(decoder.release()), width, height); } -static jobject ImageDecoder_nCreate(JNIEnv* env, jobject /*clazz*/, jlong assetPtr) { +static jobject ImageDecoder_nCreateFd(JNIEnv* env, jobject /*clazz*/, + jobject fileDescriptor) { + int descriptor = jniGetFDFromFileDescriptor(env, fileDescriptor); + + struct stat fdStat; + if (fstat(descriptor, &fdStat) == -1) { + doThrowIOE(env, "broken file descriptor; fstat returned -1"); + return nullptr; + } + + int dupDescriptor = dup(descriptor); + FILE* file = fdopen(dupDescriptor, "r"); + if (file == NULL) { + close(dupDescriptor); + doThrowIOE(env, "Could not open file"); + return nullptr; + } + std::unique_ptr<SkFILEStream> fileStream(new SkFILEStream(file)); + + if (::lseek(descriptor, 0, SEEK_CUR) == 0) { + return native_create(env, std::move(fileStream)); + } + + // FIXME: This allows us to pretend the current location is the beginning, + // but it would be better if SkFILEStream allowed treating its starting + // point as the beginning. + std::unique_ptr<SkStream> stream(SkFrontBufferedStream::Make(std::move(fileStream), + SkCodec::MinBufferedBytesNeeded())); + return native_create(env, std::move(stream)); +} + +static jobject ImageDecoder_nCreateInputStream(JNIEnv* env, jobject /*clazz*/, + jobject is, jbyteArray storage) { + std::unique_ptr<SkStream> stream(CreateJavaInputStreamAdaptor(env, is, storage, false)); + + if (!stream.get()) { + doThrowIOE(env, "Failed to create stream!"); + return nullptr; + } + std::unique_ptr<SkStream> bufferedStream( + SkFrontBufferedStream::Make(std::move(stream), + SkCodec::MinBufferedBytesNeeded())); + return native_create(env, std::move(bufferedStream)); +} + +static jobject ImageDecoder_nCreateAsset(JNIEnv* env, jobject /*clazz*/, jlong assetPtr) { Asset* asset = reinterpret_cast<Asset*>(assetPtr); std::unique_ptr<SkStream> stream(new AssetStreamAdaptor(asset)); return native_create(env, std::move(stream)); @@ -99,6 +147,7 @@ static jobject ImageDecoder_nCreateByteBuffer(JNIEnv* env, jobject /*clazz*/, jo std::unique_ptr<SkStream> stream = CreateByteBufferStreamAdaptor(env, jbyteBuffer, initialPosition, limit); if (!stream) { + doThrowIOE(env, "Failed to read ByteBuffer"); return nullptr; } return native_create(env, std::move(stream)); @@ -114,6 +163,7 @@ static bool supports_any_down_scale(const SkAndroidCodec* codec) { return codec->getEncodedFormat() == SkEncodedImageFormat::kWEBP; } +// This method should never return null. Instead, it should throw an exception. static jobject ImageDecoder_nDecodeBitmap(JNIEnv* env, jobject /*clazz*/, jlong nativePtr, jobject jcallback, jobject jpostProcess, jint desiredWidth, jint desiredHeight, jobject jsubset, @@ -165,7 +215,8 @@ static jobject ImageDecoder_nDecodeBitmap(JNIEnv* env, jobject /*clazz*/, jlong case kOpaque_SkAlphaType: break; case kUnknown_SkAlphaType: - return nullObjectReturn("Unknown alpha type"); + doThrowIOE(env, "Unknown alpha type"); + return nullptr; } SkColorType colorType = kN32_SkColorType; @@ -200,7 +251,8 @@ static jobject ImageDecoder_nDecodeBitmap(JNIEnv* env, jobject /*clazz*/, jlong bitmapInfo = bitmapInfo.makeColorType(kAlpha_8_SkColorType); } if (!bm.setInfo(bitmapInfo)) { - return nullObjectReturn("Failed to setInfo properly"); + doThrowIOE(env, "Failed to setInfo properly"); + return nullptr; } sk_sp<Bitmap> nativeBitmap; @@ -213,35 +265,44 @@ static jobject ImageDecoder_nDecodeBitmap(JNIEnv* env, jobject /*clazz*/, jlong nativeBitmap = Bitmap::allocateHeapBitmap(&bm); } if (!nativeBitmap) { - ALOGE("OOM allocating Bitmap with dimensions %i x %i", - decodeInfo.width(), decodeInfo.height()); - doThrowOOME(env); + SkString msg; + msg.printf("OOM allocating Bitmap with dimensions %i x %i", + decodeInfo.width(), decodeInfo.height()); + doThrowOOME(env, msg.c_str()); return nullptr; } - jobject jexception = nullptr; SkAndroidCodec::AndroidOptions options; options.fSampleSize = sampleSize; auto result = codec->getAndroidPixels(decodeInfo, bm.getPixels(), bm.rowBytes(), &options); + jobject jexception = env->ExceptionOccurred(); + if (jexception) { + env->ExceptionClear(); + } switch (result) { case SkCodec::kSuccess: + // Ignore the exception, since the decode was successful anyway. + jexception = nullptr; break; case SkCodec::kIncompleteInput: - if (jcallback) { + if (jcallback && !jexception) { jexception = env->NewObject(gIncomplete_class, gIncomplete_constructorMethodID); } break; case SkCodec::kErrorInInput: - if (jcallback) { + if (jcallback && !jexception) { jexception = env->NewObject(gCorrupt_class, gCorrupt_constructorMethodID); } break; default: - ALOGE("getPixels failed with error %i", result); + SkString msg; + msg.printf("getPixels failed with error %i", result); + doThrowIOE(env, msg.c_str()); return nullptr; } if (jexception) { + // FIXME: Do not provide a way for the client to force the method to return null. if (!env->CallBooleanMethod(jcallback, gCallback_onExceptionMethodID, jexception) || env->ExceptionCheck()) { return nullptr; @@ -268,7 +329,8 @@ static jobject ImageDecoder_nDecodeBitmap(JNIEnv* env, jobject /*clazz*/, jlong size_t ninePatchArraySize = decoder->mPeeker.mPatch->serializedSize(); ninePatchChunk = env->NewByteArray(ninePatchArraySize); if (ninePatchChunk == nullptr) { - return nullObjectReturn("ninePatchChunk == null"); + doThrowOOME(env, "Failed to allocate nine patch chunk."); + return nullptr; } env->SetByteArrayRegion(ninePatchChunk, 0, decoder->mPeeker.mPatchSize, @@ -278,7 +340,8 @@ static jobject ImageDecoder_nDecodeBitmap(JNIEnv* env, jobject /*clazz*/, jlong if (decoder->mPeeker.mHasInsets) { ninePatchInsets = decoder->mPeeker.createNinePatchInsets(env, 1.0f); if (ninePatchInsets == nullptr) { - return nullObjectReturn("nine patch insets == null"); + doThrowOOME(env, "Failed to allocate nine patch insets."); + return nullptr; } } } @@ -303,7 +366,8 @@ static jobject ImageDecoder_nDecodeBitmap(JNIEnv* env, jobject /*clazz*/, jlong SkImageInfo scaledInfo = bitmapInfo.makeWH(desiredWidth, desiredHeight); SkBitmap scaledBm; if (!scaledBm.setInfo(scaledInfo)) { - nullObjectReturn("Failed scaled setInfo"); + doThrowIOE(env, "Failed scaled setInfo"); + return nullptr; } sk_sp<Bitmap> scaledPixelRef; @@ -313,9 +377,10 @@ static jobject ImageDecoder_nDecodeBitmap(JNIEnv* env, jobject /*clazz*/, jlong scaledPixelRef = Bitmap::allocateHeapBitmap(&scaledBm); } if (!scaledPixelRef) { - ALOGE("OOM allocating scaled Bitmap with dimensions %i x %i", - desiredWidth, desiredHeight); - doThrowOOME(env); + SkString msg; + msg.printf("OOM allocating scaled Bitmap with dimensions %i x %i", + desiredWidth, desiredHeight); + doThrowOOME(env, msg.c_str()); return nullptr; } @@ -334,13 +399,11 @@ static jobject ImageDecoder_nDecodeBitmap(JNIEnv* env, jobject /*clazz*/, jlong if (jpostProcess) { std::unique_ptr<Canvas> canvas(Canvas::create_canvas(bm)); - if (!canvas) { - return nullObjectReturn("Failed to create Canvas for PostProcess!"); - } jobject jcanvas = env->NewObject(gCanvas_class, gCanvas_constructorMethodID, reinterpret_cast<jlong>(canvas.get())); if (!jcanvas) { - return nullObjectReturn("Failed to create Java Canvas for PostProcess!"); + doThrowOOME(env, "Failed to create Java Canvas for PostProcess!"); + return nullptr; } // jcanvas will now own canvas. canvas.release(); @@ -368,15 +431,17 @@ static jobject ImageDecoder_nDecodeBitmap(JNIEnv* env, jobject /*clazz*/, jlong newAlphaType = kOpaque_SkAlphaType; break; default: - ALOGE("invalid return from postProcess: %i", pixelFormat); - doThrowIAE(env); + SkString msg; + msg.printf("invalid return from postProcess: %i", pixelFormat); + doThrowIAE(env, msg.c_str()); return nullptr; } if (newAlphaType != bm.alphaType()) { if (!bm.setAlphaType(newAlphaType)) { - ALOGE("incompatible return from postProcess: %i", pixelFormat); - doThrowIAE(env); + SkString msg; + msg.printf("incompatible return from postProcess: %i", pixelFormat); + doThrowIAE(env, msg.c_str()); return nullptr; } nativeBitmap->setAlphaType(newAlphaType); @@ -405,7 +470,8 @@ static jobject ImageDecoder_nDecodeBitmap(JNIEnv* env, jobject /*clazz*/, jlong ninePatchChunk, ninePatchInsets); } if (allocator == ImageDecoder::kHardware_Allocator) { - return nullObjectReturn("failed to allocate hardware Bitmap!"); + doThrowOOME(env, "failed to allocate hardware Bitmap!"); + return nullptr; } // If we failed to create a hardware bitmap, go ahead and create a // software one. @@ -430,19 +496,21 @@ static void ImageDecoder_nGetPadding(JNIEnv* env, jobject /*clazz*/, jlong nativ decoder->mPeeker.getPadding(env, outPadding); } -static void ImageDecoder_nRecycle(JNIEnv* /*env*/, jobject /*clazz*/, jlong nativePtr) { +static void ImageDecoder_nClose(JNIEnv* /*env*/, jobject /*clazz*/, jlong nativePtr) { delete reinterpret_cast<ImageDecoder*>(nativePtr); } static const JNINativeMethod gImageDecoderMethods[] = { - { "nCreate", "(J)Landroid/graphics/ImageDecoder;", (void*) ImageDecoder_nCreate }, + { "nCreate", "(J)Landroid/graphics/ImageDecoder;", (void*) ImageDecoder_nCreateAsset }, { "nCreate", "(Ljava/nio/ByteBuffer;II)Landroid/graphics/ImageDecoder;", (void*) ImageDecoder_nCreateByteBuffer }, { "nCreate", "([BII)Landroid/graphics/ImageDecoder;", (void*) ImageDecoder_nCreateByteArray }, + { "nCreate", "(Ljava/io/InputStream;[B)Landroid/graphics/ImageDecoder;", (void*) ImageDecoder_nCreateInputStream }, + { "nCreate", "(Ljava/io/FileDescriptor;)Landroid/graphics/ImageDecoder;", (void*) ImageDecoder_nCreateFd }, { "nDecodeBitmap", "(JLandroid/graphics/ImageDecoder$OnExceptionListener;Landroid/graphics/PostProcess;IILandroid/graphics/Rect;ZIZZZ)Landroid/graphics/Bitmap;", (void*) ImageDecoder_nDecodeBitmap }, { "nGetSampledSize","(JI)Landroid/graphics/Point;", (void*) ImageDecoder_nGetSampledSize }, { "nGetPadding", "(JLandroid/graphics/Rect;)V", (void*) ImageDecoder_nGetPadding }, - { "nRecycle", "(J)V", (void*) ImageDecoder_nRecycle}, + { "nClose", "(J)V", (void*) ImageDecoder_nClose}, }; int register_android_graphics_ImageDecoder(JNIEnv* env) { @@ -459,7 +527,7 @@ int register_android_graphics_ImageDecoder(JNIEnv* env) { gCorrupt_constructorMethodID = GetMethodIDOrDie(env, gCorrupt_class, "<init>", "()V"); jclass callback_class = FindClassOrDie(env, "android/graphics/ImageDecoder$OnExceptionListener"); - gCallback_onExceptionMethodID = GetMethodIDOrDie(env, callback_class, "onException", "(Ljava/lang/Exception;)Z"); + gCallback_onExceptionMethodID = GetMethodIDOrDie(env, callback_class, "onException", "(Ljava/io/IOException;)Z"); jclass postProcess_class = FindClassOrDie(env, "android/graphics/PostProcess"); gPostProcess_postProcessMethodID = GetMethodIDOrDie(env, postProcess_class, "postProcess", "(Landroid/graphics/Canvas;II)I"); diff --git a/core/proto/android/os/incident.proto b/core/proto/android/os/incident.proto index a6db31f67b33..2f856ab68768 100644 --- a/core/proto/android/os/incident.proto +++ b/core/proto/android/os/incident.proto @@ -43,6 +43,8 @@ import "frameworks/base/core/proto/android/service/notification.proto"; import "frameworks/base/core/proto/android/service/package.proto"; import "frameworks/base/core/proto/android/service/print.proto"; import "frameworks/base/core/proto/android/service/procstats.proto"; +import "frameworks/base/core/proto/android/util/event_log_tags.proto"; +import "frameworks/base/core/proto/android/util/log.proto"; import "frameworks/base/libs/incident/proto/android/privacy.proto"; import "frameworks/base/libs/incident/proto/android/section.proto"; @@ -59,6 +61,8 @@ message IncidentMetadata { // the sections are able to be controlled and configured by section ids. // Instead privacy field options need to be configured in each section proto message. message IncidentProto { + reserved 1001; + // Incident header from callers repeated IncidentHeaderProto header = 1; // Internal metadata of incidentd @@ -70,6 +74,52 @@ message IncidentProto { (section).args = "getprop" ]; + // Device Logs + optional android.util.EventLogTagMapProto event_log_tag_map = 1100 [ + (section).type = SECTION_FILE, + (section).args = "/system/etc/event-log-tags" + ]; + + optional android.util.LogProto main_logs = 1101 [ + (section).type = SECTION_LOG, + (section).args = "LOG_ID_MAIN" + ]; + + optional android.util.LogProto radio_logs = 1102 [ + (section).type = SECTION_LOG, + (section).args = "LOG_ID_RADIO" + ]; + + optional android.util.LogProto events_logs = 1103 [ + (section).type = SECTION_LOG, + (section).args = "LOG_ID_EVENTS" + ]; + + optional android.util.LogProto system_logs = 1104 [ + (section).type = SECTION_LOG, + (section).args = "LOG_ID_SYSTEM" + ]; + + optional android.util.LogProto crash_logs = 1105 [ + (section).type = SECTION_LOG, + (section).args = "LOG_ID_CRASH" + ]; + + optional android.util.LogProto stats_logs = 1106 [ + (section).type = SECTION_LOG, + (section).args = "LOG_ID_STATS" + ]; + + optional android.util.LogProto security_logs = 1107 [ + (section).type = SECTION_LOG, + (section).args = "LOG_ID_SECURITY" + ]; + + optional android.util.LogProto kernel_logs = 1108 [ + (section).type = SECTION_LOG, + (section).args = "LOG_ID_KERNEL" + ]; + // Linux services optional Procrank procrank = 2000 [ (section).type = SECTION_COMMAND, @@ -196,4 +246,5 @@ message IncidentProto { (section).type = SECTION_DUMPSYS, (section).args = "graphicsstats --proto" ]; + } diff --git a/core/proto/android/util/event_log_tags.proto b/core/proto/android/util/event_log_tags.proto new file mode 100644 index 000000000000..cb039be55b85 --- /dev/null +++ b/core/proto/android/util/event_log_tags.proto @@ -0,0 +1,58 @@ +/* + * 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.util; + +option java_multiple_files = true; + +// Proto representation of event.logtags. +// Usually sit in /system/etc/event-log-tags. +message EventLogTagMapProto { + repeated EventLogTag event_log_tags = 1; +} + +message EventLogTag { + optional uint32 tag_number = 1; // keyed by tag number. + optional string tag_name = 2; + + message ValueDescriptor { + optional string name = 1; + + enum DataType { + UNKNOWN = 0; + INT = 1; + LONG = 2; + STRING = 3; + LIST = 4; + FLOAT = 5; + } + optional DataType type = 2; + + enum DataUnit { + UNSET = 0; // this field is optional, so default is unset + OBJECTS = 1; // Number of objects + BYTES = 2; // Number of bytes (default for type of int/long) + MILLISECONDS = 3; // Number of milliseconds + ALLOCATIONS = 4; // Number of allocations + ID = 5; // Id + PERCENT = 6; // Percent + SECONDS = 115; // 's', Number of seconds (monotonic time) + } + optional DataUnit unit = 3; + } + repeated ValueDescriptor value_descriptors = 3; +}
\ No newline at end of file diff --git a/core/proto/android/util/log.proto b/core/proto/android/util/log.proto new file mode 100644 index 000000000000..30ff41242bcc --- /dev/null +++ b/core/proto/android/util/log.proto @@ -0,0 +1,88 @@ +/* + * 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.util; + +option java_multiple_files = true; + +// Represents a Text Log in logd +// Next Tag: 9 +message TextLogEntry { + optional uint64 sec = 1; + optional uint64 nanosec = 2; + + enum LogPriority { + LOG_UNKNOWN = 0; + LOG_DEFAULT = 1; + LOG_VERBOSE = 2; + LOG_DEBUG = 3; + LOG_INFO = 4; + LOG_WARN = 5; + LOG_ERROR = 6; + LOG_FATAL = 7; + LOG_SILENT = 8; + } + optional LogPriority priority = 3; + optional int32 uid = 4; + optional int32 pid = 5; + optional int32 tid = 6; + optional string tag = 7; + optional string log = 8; +} + +// Represents a Binary Log in logd, need to look event-log-tags for more info. +// Next Tag: 8 +message BinaryLogEntry { + optional uint64 sec = 1; + optional uint64 nanosec = 2; + optional int32 uid = 3; + optional int32 pid = 4; + optional int32 tid = 5; + + // Index of the event tag, can look up in event-log-tags file + optional uint32 tag_index = 6; + + message Elem { + // must be sync with liblog log/log.h + enum Type { + EVENT_TYPE_LIST_STOP = 10; // '\n' + EVENT_TYPE_UNKNOWN = 63; // '?' + + EVENT_TYPE_INT = 0; + EVENT_TYPE_LONG = 1; + EVENT_TYPE_STRING = 2; + EVENT_TYPE_LIST = 3; + EVENT_TYPE_FLOAT = 4; + } + optional Type type = 1 [default=EVENT_TYPE_UNKNOWN]; + + oneof value { + int32 val_int32 = 2; + int64 val_int64 = 3; + string val_string = 4; + float val_float = 5; + } + } + repeated Elem elems = 7; +} + +message LogProto { + repeated TextLogEntry text_logs = 1; + + repeated BinaryLogEntry binary_logs = 2; +} + diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 6b89489d586e..c40f3748008b 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -173,6 +173,8 @@ <protected-broadcast android:name="android.bluetooth.a2dp.profile.action.CONNECTION_STATE_CHANGED" /> <protected-broadcast + android:name="android.bluetooth.a2dp.profile.action.ACTIVE_DEVICE_CHANGED" /> + <protected-broadcast android:name="android.bluetooth.a2dp.profile.action.PLAYING_STATE_CHANGED" /> <protected-broadcast android:name="android.bluetooth.a2dp.profile.action.CODEC_CONFIG_CHANGED" /> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index fd05bb442247..d2685cfb5a8f 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -2784,7 +2784,7 @@ <bool name="config_eap_sim_based_auth_supported">true</bool> <!-- How long history of previous vibrations should be kept for the dumpsys. --> - <integer name="config_previousVibrationsDumpLimit">20</integer> + <integer name="config_previousVibrationsDumpLimit">50</integer> <!-- The default vibration strength, must be between 1 and 255 inclusive. --> <integer name="config_defaultVibrationAmplitude">255</integer> diff --git a/core/tests/coretests/src/android/content/pm/PackageBackwardCompatibilityTest.java b/core/tests/coretests/src/android/content/pm/PackageBackwardCompatibilityTest.java index 1a54bd608d48..63a5e4cc1dd8 100644 --- a/core/tests/coretests/src/android/content/pm/PackageBackwardCompatibilityTest.java +++ b/core/tests/coretests/src/android/content/pm/PackageBackwardCompatibilityTest.java @@ -41,6 +41,8 @@ public class PackageBackwardCompatibilityTest { private static final String ANDROID_TEST_MOCK = "android.test.mock"; + private static final String OTHER_LIBRARY = "other.library"; + private Package mPackage; private static ArrayList<String> arrayList(String... strings) { @@ -78,6 +80,18 @@ public class PackageBackwardCompatibilityTest { } @Test + public void targeted_at_O_not_empty_usesLibraries() { + mPackage.applicationInfo.targetSdkVersion = Build.VERSION_CODES.O; + mPackage.usesLibraries = arrayList(OTHER_LIBRARY); + PackageBackwardCompatibility.modifySharedLibraries(mPackage); + // The org.apache.http.legacy jar should be added at the start of the list. + assertEquals("usesLibraries not updated correctly", + arrayList(ORG_APACHE_HTTP_LEGACY, OTHER_LIBRARY), + mPackage.usesLibraries); + assertNull("usesOptionalLibraries not updated correctly", mPackage.usesOptionalLibraries); + } + + @Test public void targeted_at_O_org_apache_http_legacy_in_usesLibraries() { mPackage.applicationInfo.targetSdkVersion = Build.VERSION_CODES.O; mPackage.usesLibraries = arrayList(ORG_APACHE_HTTP_LEGACY); diff --git a/graphics/java/android/graphics/ImageDecoder.java b/graphics/java/android/graphics/ImageDecoder.java index 60416a720231..97ce88606331 100644 --- a/graphics/java/android/graphics/ImageDecoder.java +++ b/graphics/java/android/graphics/ImageDecoder.java @@ -16,29 +16,44 @@ package android.graphics; +import static android.system.OsConstants.SEEK_SET; + import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.RawRes; +import android.content.ContentResolver; +import android.content.res.AssetFileDescriptor; import android.content.res.AssetManager; import android.content.res.Resources; import android.graphics.drawable.Drawable; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.NinePatchDrawable; +import android.net.Uri; +import android.system.ErrnoException; +import android.system.Os; + +import libcore.io.IoUtils; +import dalvik.system.CloseGuard; import java.nio.ByteBuffer; +import java.io.FileDescriptor; +import java.io.FileInputStream; +import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.lang.ArrayIndexOutOfBoundsException; +import java.lang.AutoCloseable; import java.lang.NullPointerException; import java.lang.RuntimeException; import java.lang.annotation.Retention; import static java.lang.annotation.RetentionPolicy.SOURCE; +import java.util.concurrent.atomic.AtomicBoolean; /** * Class for decoding images as {@link Bitmap}s or {@link Drawable}s. * @hide */ -public final class ImageDecoder { +public final class ImageDecoder implements AutoCloseable { /** * Source of the encoded image data. */ @@ -47,10 +62,7 @@ public final class ImageDecoder { Resources getResources() { return null; } /* @hide */ - void close() {} - - /* @hide */ - abstract ImageDecoder createImageDecoder(); + abstract ImageDecoder createImageDecoder() throws IOException; }; private static class ByteArraySource extends Source { @@ -64,7 +76,7 @@ public final class ImageDecoder { private final int mLength; @Override - public ImageDecoder createImageDecoder() { + public ImageDecoder createImageDecoder() throws IOException { return nCreate(mData, mOffset, mLength); } } @@ -76,7 +88,7 @@ public final class ImageDecoder { private final ByteBuffer mBuffer; @Override - public ImageDecoder createImageDecoder() { + public ImageDecoder createImageDecoder() throws IOException { if (!mBuffer.isDirect() && mBuffer.hasArray()) { int offset = mBuffer.arrayOffset() + mBuffer.position(); int length = mBuffer.limit() - mBuffer.position(); @@ -86,61 +98,110 @@ public final class ImageDecoder { } } - private static class ResourceSource extends Source { - ResourceSource(Resources res, int resId) - throws Resources.NotFoundException { - // Test that the resource can be found. - InputStream is = null; + private static class ContentResolverSource extends Source { + ContentResolverSource(ContentResolver resolver, Uri uri) { + mResolver = resolver; + mUri = uri; + } + + private final ContentResolver mResolver; + private final Uri mUri; + + @Override + public ImageDecoder createImageDecoder() throws IOException { + AssetFileDescriptor assetFd = null; try { - is = res.openRawResource(resId); + if (mUri.getScheme() == ContentResolver.SCHEME_CONTENT) { + assetFd = mResolver.openTypedAssetFileDescriptor(mUri, + "image/*", null); + } else { + assetFd = mResolver.openAssetFileDescriptor(mUri, "r"); + } + } catch (FileNotFoundException e) { + // Some images cannot be opened as AssetFileDescriptors (e.g. + // bmp, ico). Open them as InputStreams. + InputStream is = mResolver.openInputStream(mUri); + if (is == null) { + throw new FileNotFoundException(mUri.toString()); + } + + return createFromStream(is); + } + + final FileDescriptor fd = assetFd.getFileDescriptor(); + final long offset = assetFd.getStartOffset(); + + ImageDecoder decoder = null; + try { + try { + Os.lseek(fd, offset, SEEK_SET); + decoder = nCreate(fd); + } catch (ErrnoException e) { + decoder = createFromStream(new FileInputStream(fd)); + } } finally { - if (is != null) { - try { - is.close(); - } catch (IOException e) { - } + if (decoder == null) { + IoUtils.closeQuietly(assetFd); + } else { + decoder.mAssetFd = assetFd; } } + return decoder; + } + } + + private static ImageDecoder createFromStream(InputStream is) throws IOException { + // Arbitrary size matches BitmapFactory. + byte[] storage = new byte[16 * 1024]; + ImageDecoder decoder = null; + try { + decoder = nCreate(is, storage); + } finally { + if (decoder == null) { + IoUtils.closeQuietly(is); + } else { + decoder.mInputStream = is; + decoder.mTempStorage = storage; + } + } + + return decoder; + } + private static class ResourceSource extends Source { + ResourceSource(Resources res, int resId) { mResources = res; mResId = resId; } final Resources mResources; final int mResId; - // This is just stored here in order to keep the underlying Asset - // alive. FIXME: Can I access the Asset (and keep it alive) without - // this object? - InputStream mInputStream; @Override public Resources getResources() { return mResources; } @Override - public ImageDecoder createImageDecoder() { - // FIXME: Can I bypass creating the stream? - try { - mInputStream = mResources.openRawResource(mResId); - } catch (Resources.NotFoundException e) { - // This should never happen, since we already tested in the - // constructor. - } - if (!(mInputStream instanceof AssetManager.AssetInputStream)) { - // This should never happen. - throw new RuntimeException("Resource is not an asset?"); - } - long asset = ((AssetManager.AssetInputStream) mInputStream).getNativeAsset(); - return nCreate(asset); - } - - @Override - public void close() { + public ImageDecoder createImageDecoder() throws IOException { + // This is just used in order to access the underlying Asset and + // keep it alive. FIXME: Can we skip creating this object? + InputStream is = null; + ImageDecoder decoder = null; try { - mInputStream.close(); - } catch (IOException e) { + is = mResources.openRawResource(mResId); + if (!(is instanceof AssetManager.AssetInputStream)) { + // This should never happen. + throw new RuntimeException("Resource is not an asset?"); + } + long asset = ((AssetManager.AssetInputStream) is).getNativeAsset(); + decoder = nCreate(asset); } finally { - mInputStream = null; + if (decoder == null) { + IoUtils.closeQuietly(is); + } else { + decoder.mInputStream = is; + } } + return decoder; } } @@ -159,18 +220,23 @@ public final class ImageDecoder { }; /** - * Used if the provided data is incomplete. + * Supplied to onException if the provided data is incomplete. + * + * Will never be thrown by ImageDecoder. * * There may be a partial image to display. */ - public class IncompleteException extends Exception {}; + public static class IncompleteException extends IOException {}; /** * Used if the provided data is corrupt. * - * There may be a partial image to display. + * May be thrown if there is nothing to display. + * + * If supplied to onException, there may be a correct partial image to + * display. */ - public class CorruptException extends Exception {}; + public static class CorruptException extends IOException {}; /** * Optional listener supplied to {@link #decodeDrawable} or @@ -193,15 +259,14 @@ public final class ImageDecoder { public static interface OnExceptionListener { /** * Called when there is a problem in the stream or in the data. - * FIXME: Or do not allow streams? * FIXME: Report how much of the image has been decoded? * - * @param e Exception containing information about the error. + * @param e IOException containing information about the error. * @return True to create and return a {@link Drawable}/ * {@link Bitmap} with partial data. False to return * {@code null}. True is the default. */ - public boolean onException(Exception e); + public boolean onException(IOException e); }; // Fields @@ -221,9 +286,15 @@ public final class ImageDecoder { private PostProcess mPostProcess; private OnExceptionListener mOnExceptionListener; + // Objects for interacting with the input. + private InputStream mInputStream; + private byte[] mTempStorage; + private AssetFileDescriptor mAssetFd; + private final AtomicBoolean mClosed = new AtomicBoolean(); + private final CloseGuard mCloseGuard = CloseGuard.get(); /** - * Private constructor called by JNI. {@link #recycle} must be + * Private constructor called by JNI. {@link #close} must be * called after decoding to delete native resources. */ @SuppressWarnings("unused") @@ -233,6 +304,20 @@ public final class ImageDecoder { mHeight = height; mDesiredWidth = width; mDesiredHeight = height; + mCloseGuard.open("close"); + } + + @Override + protected void finalize() throws Throwable { + try { + if (mCloseGuard != null) { + mCloseGuard.warnIfOpen(); + } + + close(); + } finally { + super.finalize(); + } } /** @@ -243,14 +328,28 @@ public final class ImageDecoder { * // FIXME: Can be an @DrawableRes? * @return a new Source object, which can be passed to * {@link #decodeDrawable} or {@link #decodeBitmap}. - * @throws Resources.NotFoundException if the asset does not exist. */ + @NonNull public static Source createSource(@NonNull Resources res, @RawRes int resId) - throws Resources.NotFoundException { + { return new ResourceSource(res, resId); } /** + * Create a new {@link Source} from a {@link android.net.Uri}. + * + * @param cr to retrieve from. + * @param uri of the image file. + * @return a new Source object, which can be passed to + * {@link #decodeDrawable} or {@link #decodeBitmap}. + */ + @NonNull + public static Source createSource(@NonNull ContentResolver cr, + @NonNull Uri uri) { + return new ContentResolverSource(cr, uri); + } + + /** * Create a new {@link Source} from a byte array. * @param data byte array of compressed image data. * @param offset offset into data for where the decoder should begin @@ -307,7 +406,7 @@ public final class ImageDecoder { + "provided " + sampleSize); } if (mNativePtr == 0) { - throw new IllegalStateException("ImageDecoder is recycled!"); + throw new IllegalStateException("ImageDecoder is closed!"); } return nGetSampledSize(mNativePtr, sampleSize); @@ -500,24 +599,26 @@ public final class ImageDecoder { mAsAlphaMask = true; } - /** - * Clean up resources. - * - * ImageDecoder has a private constructor, and will always be recycled - * by decodeDrawable or decodeBitmap which creates it, so there is no - * need for a finalizer. - */ - private void recycle() { - if (mNativePtr == 0) { + @Override + public void close() { + mCloseGuard.close(); + if (!mClosed.compareAndSet(false, true)) { return; } - nRecycle(mNativePtr); + nClose(mNativePtr); mNativePtr = 0; + + IoUtils.closeQuietly(mInputStream); + IoUtils.closeQuietly(mAssetFd); + + mInputStream = null; + mAssetFd = null; + mTempStorage = null; } private void checkState() { if (mNativePtr == 0) { - throw new IllegalStateException("Cannot reuse ImageDecoder.Source!"); + throw new IllegalStateException("Cannot use closed ImageDecoder!"); } checkSubset(mDesiredWidth, mDesiredHeight, mCropRect); @@ -548,44 +649,47 @@ public final class ImageDecoder { /** * Create a {@link Drawable}. + * @throws IOException if {@code src} is not found, is an unsupported + * format, or cannot be decoded for any reason. */ - public static Drawable decodeDrawable(Source src, OnHeaderDecodedListener listener) { - ImageDecoder decoder = src.createImageDecoder(); - if (decoder == null) { - return null; - } - - if (listener != null) { - ImageInfo info = new ImageInfo(decoder.mWidth, decoder.mHeight); - listener.onHeaderDecoded(info, decoder); - } + @NonNull + public static Drawable decodeDrawable(Source src, OnHeaderDecodedListener listener) + throws IOException { + try (ImageDecoder decoder = src.createImageDecoder()) { + if (listener != null) { + ImageInfo info = new ImageInfo(decoder.mWidth, decoder.mHeight); + listener.onHeaderDecoded(info, decoder); + } - decoder.checkState(); + decoder.checkState(); - if (decoder.mRequireUnpremultiplied) { - // Though this could be supported (ignored) for opaque images, it - // seems better to always report this error. - throw new IllegalStateException("Cannot decode a Drawable with" + - " unpremultiplied pixels!"); - } + if (decoder.mRequireUnpremultiplied) { + // Though this could be supported (ignored) for opaque images, + // it seems better to always report this error. + throw new IllegalStateException("Cannot decode a Drawable " + + "with unpremultiplied pixels!"); + } - if (decoder.mMutable) { - throw new IllegalStateException("Cannot decode a mutable Drawable!"); - } + if (decoder.mMutable) { + throw new IllegalStateException("Cannot decode a mutable " + + "Drawable!"); + } - try { Bitmap bm = nDecodeBitmap(decoder.mNativePtr, decoder.mOnExceptionListener, decoder.mPostProcess, - decoder.mDesiredWidth, decoder.mDesiredHeight, + decoder.mDesiredWidth, + decoder.mDesiredHeight, decoder.mCropRect, - false, // decoder.mMutable + false, // mMutable decoder.mAllocator, - false, // decoder.mRequireUnpremultiplied + false, // mRequireUnpremultiplied decoder.mPreferRamOverQuality, - decoder.mAsAlphaMask - ); + decoder.mAsAlphaMask); if (bm == null) { + // FIXME: bm should never be null. Currently a return value + // of false from onException will result in bm being null. What + // is the right API to choose to discard partial Bitmaps? return null; } @@ -606,60 +710,58 @@ public final class ImageDecoder { // TODO: Handle animation. return new BitmapDrawable(res, bm); - } finally { - decoder.recycle(); - src.close(); } } /** - * Create a {@link Bitmap}. + * Create a {@link Bitmap}. + * @throws IOException if {@code src} is not found, is an unsupported + * format, or cannot be decoded for any reason. */ - public static Bitmap decodeBitmap(Source src, OnHeaderDecodedListener listener) { - ImageDecoder decoder = src.createImageDecoder(); - if (decoder == null) { - return null; - } - - if (listener != null) { - ImageInfo info = new ImageInfo(decoder.mWidth, decoder.mHeight); - listener.onHeaderDecoded(info, decoder); - } + @NonNull + public static Bitmap decodeBitmap(Source src, OnHeaderDecodedListener listener) + throws IOException { + try (ImageDecoder decoder = src.createImageDecoder()) { + if (listener != null) { + ImageInfo info = new ImageInfo(decoder.mWidth, decoder.mHeight); + listener.onHeaderDecoded(info, decoder); + } - decoder.checkState(); + decoder.checkState(); - try { return nDecodeBitmap(decoder.mNativePtr, decoder.mOnExceptionListener, decoder.mPostProcess, - decoder.mDesiredWidth, decoder.mDesiredHeight, + decoder.mDesiredWidth, + decoder.mDesiredHeight, decoder.mCropRect, decoder.mMutable, decoder.mAllocator, decoder.mRequireUnpremultiplied, decoder.mPreferRamOverQuality, decoder.mAsAlphaMask); - } finally { - decoder.recycle(); - src.close(); } } - private static native ImageDecoder nCreate(long asset); + private static native ImageDecoder nCreate(long asset) throws IOException; private static native ImageDecoder nCreate(ByteBuffer buffer, int position, - int limit); + int limit) throws IOException; private static native ImageDecoder nCreate(byte[] data, int offset, - int length); + int length) throws IOException; + private static native ImageDecoder nCreate(InputStream is, byte[] storage); + private static native ImageDecoder nCreate(FileDescriptor fd) throws IOException; + @NonNull private static native Bitmap nDecodeBitmap(long nativePtr, OnExceptionListener listener, PostProcess postProcess, int width, int height, Rect cropRect, boolean mutable, int allocator, boolean requireUnpremul, - boolean preferRamOverQuality, boolean asAlphaMask); + boolean preferRamOverQuality, boolean asAlphaMask) + throws IOException; private static native Point nGetSampledSize(long nativePtr, int sampleSize); private static native void nGetPadding(long nativePtr, Rect outRect); - private static native void nRecycle(long nativePtr); + private static native void nClose(long nativePtr); } diff --git a/libs/incident/proto/android/section.proto b/libs/incident/proto/android/section.proto index d268cf4fc09a..49bfe1e8a598 100644 --- a/libs/incident/proto/android/section.proto +++ b/libs/incident/proto/android/section.proto @@ -37,6 +37,9 @@ enum SectionType { // incidentd calls dumpsys for annotated field SECTION_DUMPSYS = 3; + + // incidentd calls logs for annotated field + SECTION_LOG = 4; } message SectionFlags { diff --git a/location/java/android/location/ILocationManager.aidl b/location/java/android/location/ILocationManager.aidl index f9075cfd10d9..0990dcccaecb 100644 --- a/location/java/android/location/ILocationManager.aidl +++ b/location/java/android/location/ILocationManager.aidl @@ -79,6 +79,7 @@ interface ILocationManager boolean startGnssBatch(long periodNanos, boolean wakeOnFifoFull, String packageName); void flushGnssBatch(String packageName); boolean stopGnssBatch(); + boolean injectLocation(in Location location); // --- deprecated --- List<String> getAllProviders(); diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java index 4802b2357906..f0b2774a4276 100644 --- a/location/java/android/location/LocationManager.java +++ b/location/java/android/location/LocationManager.java @@ -42,6 +42,7 @@ import java.util.List; import static android.Manifest.permission.ACCESS_COARSE_LOCATION; import static android.Manifest.permission.ACCESS_FINE_LOCATION; +import static android.Manifest.permission.LOCATION_HARDWARE; /** * This class provides access to the system location services. These @@ -882,6 +883,34 @@ public class LocationManager { requestLocationUpdates(request, null, null, intent); } + /** + * Set the last known location with a new location. + * + * <p>A privileged client can inject a {@link Location} if it has a better estimate of what + * the recent location is. This is especially useful when the device boots up and the GPS + * chipset is in the process of getting the first fix. If the client has cached the location, + * it can inject the {@link Location}, so if an app requests for a {@link Location} from {@link + * #getLastKnownLocation(String)}, the location information is still useful before getting + * the first fix.</p> + * + * <p> Useful in products like Auto. + * + * @param newLocation newly available {@link Location} object + * @return true if update was successful, false if not + * + * @throws SecurityException if no suitable permission is present + * + * @hide + */ + @RequiresPermission(allOf = {LOCATION_HARDWARE, ACCESS_FINE_LOCATION}) + public boolean injectLocation(Location newLocation) { + try { + return mService.injectLocation(newLocation); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + private ListenerTransport wrapListener(LocationListener listener, Looper looper) { if (listener == null) return null; synchronized (mListeners) { diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_status_area.xml b/packages/SystemUI/res-keyguard/layout/keyguard_status_area.xml index b154d46f162c..5e09e754f9a5 100644 --- a/packages/SystemUI/res-keyguard/layout/keyguard_status_area.xml +++ b/packages/SystemUI/res-keyguard/layout/keyguard_status_area.xml @@ -32,6 +32,8 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginBottom="@dimen/widget_vertical_padding" + android:paddingStart="64dp" + android:paddingEnd="64dp" android:theme="@style/TextAppearance.Keyguard" /> <LinearLayout android:id="@+id/row" diff --git a/packages/SystemUI/res-keyguard/values/dimens.xml b/packages/SystemUI/res-keyguard/values/dimens.xml index 463af61c16f1..04cf6b0e29ea 100644 --- a/packages/SystemUI/res-keyguard/values/dimens.xml +++ b/packages/SystemUI/res-keyguard/values/dimens.xml @@ -57,7 +57,7 @@ <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> + <dimen name="widget_icon_padding">8dp</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 d50bab533856..5f52e2ac10a9 100644 --- a/packages/SystemUI/res-keyguard/values/styles.xml +++ b/packages/SystemUI/res-keyguard/values/styles.xml @@ -83,13 +83,13 @@ <item name="android:gravity">center</item> <item name="android:ellipsize">end</item> <item name="android:maxLines">2</item> + <item name="android:fontFamily">@*android:string/config_headlineFontFamilyLight</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/src/com/android/keyguard/KeyguardSliceView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java index b8adb6a91bf2..8135c616d87e 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java @@ -26,6 +26,9 @@ import android.graphics.Paint; import android.graphics.drawable.Drawable; import android.net.Uri; import android.provider.Settings; +import android.text.Layout; +import android.text.TextUtils; +import android.text.TextUtils.TruncateAt; import android.util.AttributeSet; import android.util.Log; import android.view.View; @@ -40,6 +43,7 @@ import com.android.systemui.R; import com.android.systemui.keyguard.KeyguardSliceProvider; import com.android.systemui.tuner.TunerService; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.function.Consumer; @@ -129,7 +133,20 @@ public class KeyguardSliceView extends LinearLayout implements View.OnClickListe android.app.slice.SliceItem.FORMAT_TEXT, new String[]{android.app.slice.Slice.HINT_TITLE}, null /* nonHints */); - mTitle.setText(mainTitle.getText()); + CharSequence title = mainTitle.getText(); + mTitle.setText(title); + + // Check if we're already ellipsizing the text. + // We're going to figure out the best possible line break if not. + Layout layout = mTitle.getLayout(); + if (layout != null){ + final int lineCount = layout.getLineCount(); + if (lineCount > 0) { + if (layout.getEllipsisCount(lineCount - 1) == 0) { + mTitle.setText(findBestLineBreak(title)); + } + } + } } mClickActions.clear(); @@ -195,6 +212,46 @@ public class KeyguardSliceView extends LinearLayout implements View.OnClickListe mListener.accept(mHasHeader); } + /** + * Breaks a string in 2 lines where both have similar character count + * but first line is always longer. + * + * @param charSequence Original text. + * @return Optimal string. + */ + private CharSequence findBestLineBreak(CharSequence charSequence) { + if (TextUtils.isEmpty(charSequence)) { + return charSequence; + } + + String source = charSequence.toString(); + // Ignore if there is only 1 word, + // or if line breaks were manually set. + if (source.contains("\n") || !source.contains(" ")) { + return source; + } + + final String[] words = source.split(" "); + final StringBuilder optimalString = new StringBuilder(source.length()); + int current = 0; + while (optimalString.length() < source.length() - optimalString.length()) { + optimalString.append(words[current]); + if (current < words.length - 1) { + optimalString.append(" "); + } + current++; + } + optimalString.append("\n"); + for (int i = current; i < words.length; i++) { + optimalString.append(words[i]); + if (current < words.length - 1) { + optimalString.append(" "); + } + } + + return optimalString.toString(); + } + public void setDark(float darkAmount) { mDarkAmount = darkAmount; updateTextColors(); @@ -287,6 +344,9 @@ public class KeyguardSliceView extends LinearLayout implements View.OnClickListe setPadding(horizontalPadding, 0, horizontalPadding, 0); setCompoundDrawablePadding((int) context.getResources() .getDimension(R.dimen.widget_icon_padding)); + setMaxWidth(KeyguardSliceView.this.getWidth() / 2); + setMaxLines(1); + setEllipsize(TruncateAt.END); } public void setHasDivider(boolean hasDivider) { diff --git a/packages/SystemUI/src/com/android/systemui/usb/UsbConfirmActivity.java b/packages/SystemUI/src/com/android/systemui/usb/UsbConfirmActivity.java index e117969c5993..0a3e34ee951d 100644 --- a/packages/SystemUI/src/com/android/systemui/usb/UsbConfirmActivity.java +++ b/packages/SystemUI/src/com/android/systemui/usb/UsbConfirmActivity.java @@ -68,7 +68,6 @@ public class UsbConfirmActivity extends AlertActivity String appName = mResolveInfo.loadLabel(packageManager).toString(); final AlertController.AlertParams ap = mAlertParams; - ap.mIcon = mResolveInfo.loadIcon(packageManager); ap.mTitle = appName; if (mDevice == null) { ap.mMessage = getString(R.string.usb_accessory_confirm_prompt, appName, diff --git a/packages/SystemUI/src/com/android/systemui/usb/UsbPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/usb/UsbPermissionActivity.java index 4606aee346cc..238407a9a6f1 100644 --- a/packages/SystemUI/src/com/android/systemui/usb/UsbPermissionActivity.java +++ b/packages/SystemUI/src/com/android/systemui/usb/UsbPermissionActivity.java @@ -90,7 +90,6 @@ public class UsbPermissionActivity extends AlertActivity String appName = aInfo.loadLabel(packageManager).toString(); final AlertController.AlertParams ap = mAlertParams; - ap.mIcon = aInfo.loadIcon(packageManager); ap.mTitle = appName; if (mDevice == null) { ap.mMessage = getString(R.string.usb_accessory_permission_prompt, appName, diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java index 472723dfa909..a024d5a8e3ff 100644 --- a/services/core/java/com/android/server/AlarmManagerService.java +++ b/services/core/java/com/android/server/AlarmManagerService.java @@ -149,6 +149,9 @@ class AlarmManagerService extends SystemService { private long mNextNonWakeup; private long mLastWakeupSet; private long mLastWakeup; + private long mLastTickSet; + private long mLastTickIssued; // elapsed + private long mLastTickReceived; int mBroadcastRefCount = 0; PowerManager.WakeLock mWakeLock; boolean mLastWakeLockUnimportantForLogging; @@ -1596,6 +1599,7 @@ class AlarmManagerService extends SystemService { pw.println(); mForceAppStandbyTracker.dump(pw, " "); + pw.println(); final long nowRTC = System.currentTimeMillis(); final long nowELAPSED = SystemClock.elapsedRealtime(); @@ -1607,8 +1611,12 @@ class AlarmManagerService extends SystemService { pw.println(); pw.print(" mLastTimeChangeClockTime="); pw.print(mLastTimeChangeClockTime); pw.print("="); pw.println(sdf.format(new Date(mLastTimeChangeClockTime))); - pw.print(" mLastTimeChangeRealtime="); - TimeUtils.formatDuration(mLastTimeChangeRealtime, pw); + pw.print(" mLastTimeChangeRealtime="); pw.println(mLastTimeChangeRealtime); + pw.print(" mLastTickIssued="); + TimeUtils.formatDuration(mLastTickIssued - nowELAPSED, pw); + pw.println(); + pw.print(" mLastTickReceived="); pw.println(sdf.format(new Date(mLastTickReceived))); + pw.print(" mLastTickSet="); pw.println(sdf.format(new Date(mLastTickSet))); pw.println(); if (!mInteractive) { pw.print(" Time since non-interactive: "); @@ -3284,6 +3292,9 @@ class AlarmManagerService extends SystemService { if (DEBUG_BATCH) { Slog.v(TAG, "Received TIME_TICK alarm; rescheduling"); } + synchronized (mLock) { + mLastTickReceived = System.currentTimeMillis(); + } scheduleTimeTickEvent(); } else if (intent.getAction().equals(Intent.ACTION_DATE_CHANGED)) { // Since the kernel does not keep track of DST, we need to @@ -3309,6 +3320,11 @@ class AlarmManagerService extends SystemService { setImpl(ELAPSED_REALTIME, SystemClock.elapsedRealtime() + tickEventDelay, 0, 0, mTimeTickSender, null, null, AlarmManager.FLAG_STANDALONE, workSource, null, Process.myUid(), "android"); + + // Finally, remember when we set the tick alarm + synchronized (mLock) { + mLastTickSet = currentTime; + } } public void scheduleDateChangedEvent() { @@ -3660,6 +3676,11 @@ class AlarmManagerService extends SystemService { if (alarm.operation != null) { // PendingIntent alarm mSendCount++; + + if (alarm.priorityClass.priority == PRIO_TICK) { + mLastTickIssued = nowELAPSED; + } + try { alarm.operation.send(getContext(), 0, mBackgroundIntent.putExtra( diff --git a/services/core/java/com/android/server/EventLogTags.logtags b/services/core/java/com/android/server/EventLogTags.logtags index 6174aece3937..8361132f24bc 100644 --- a/services/core/java/com/android/server/EventLogTags.logtags +++ b/services/core/java/com/android/server/EventLogTags.logtags @@ -261,6 +261,10 @@ option java_package com.android.server 2756 fstrim_finish (time|2|3) # --------------------------- +# Job scheduler +# --------------------------- +8000 job_deferred_execution (time|2|3) + # AudioService.java # --------------------------- 40000 volume_changed (stream|1), (prev_level|1), (level|1), (max_level|1), (caller|3) diff --git a/services/core/java/com/android/server/IpSecService.java b/services/core/java/com/android/server/IpSecService.java index 989cb886c126..5c098e32045b 100644 --- a/services/core/java/com/android/server/IpSecService.java +++ b/services/core/java/com/android/server/IpSecService.java @@ -102,8 +102,14 @@ public class IpSecService extends IIpSecService.Stub { /* Binder context for this service */ private final Context mContext; - /** Should be a never-repeating global ID for resources */ - private static AtomicInteger mNextResourceId = new AtomicInteger(0x00FADED0); + /** + * The next non-repeating global ID for tracking resources between users, this service, + * and kernel data structures. Accessing this variable is not thread safe, so it is + * only read or modified within blocks synchronized on IpSecService.this. We want to + * avoid -1 (INVALID_RESOURCE_ID) and 0 (we probably forgot to initialize it). + */ + @GuardedBy("IpSecService.this") + private int mNextResourceId = 1; interface IpSecServiceConfiguration { INetd getNetdInstance() throws RemoteException; @@ -856,7 +862,7 @@ public class IpSecService extends IIpSecService.Stub { checkNotNull(binder, "Null Binder passed to allocateSecurityParameterIndex"); UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid()); - int resourceId = mNextResourceId.getAndIncrement(); + final int resourceId = mNextResourceId++; int spi = IpSecManager.INVALID_SECURITY_PARAMETER_INDEX; String localAddress = ""; @@ -979,7 +985,7 @@ public class IpSecService extends IIpSecService.Stub { int callingUid = Binder.getCallingUid(); UserRecord userRecord = mUserResourceTracker.getUserRecord(callingUid); - int resourceId = mNextResourceId.getAndIncrement(); + final int resourceId = mNextResourceId++; FileDescriptor sockFd = null; try { if (!userRecord.mSocketQuotaTracker.isAvailable()) { @@ -1102,7 +1108,7 @@ public class IpSecService extends IIpSecService.Stub { IpSecConfig c, IBinder binder) throws RemoteException { checkIpSecConfig(c); checkNotNull(binder, "Null Binder passed to createTransportModeTransform"); - int resourceId = mNextResourceId.getAndIncrement(); + final int resourceId = mNextResourceId++; UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid()); diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java index 57c992fdcf48..ea748db159e1 100644 --- a/services/core/java/com/android/server/LocationManagerService.java +++ b/services/core/java/com/android/server/LocationManagerService.java @@ -2254,6 +2254,64 @@ public class LocationManagerService extends ILocationManager.Stub { } } + /** + * Provides an interface to inject and set the last location if location is not available + * currently. + * + * This helps in cases where the product (Cars for example) has saved the last known location + * before powering off. This interface lets the client inject the saved location while the GPS + * chipset is getting its first fix, there by improving user experience. + * + * @param location - Location object to inject + * @return true if update was successful, false if not + */ + @Override + public boolean injectLocation(Location location) { + mContext.enforceCallingPermission(android.Manifest.permission.LOCATION_HARDWARE, + "Location Hardware permission not granted to inject location"); + mContext.enforceCallingPermission(android.Manifest.permission.ACCESS_FINE_LOCATION, + "Access Fine Location permission not granted to inject Location"); + + if (location == null) { + if (D) { + Log.d(TAG, "injectLocation(): called with null location"); + } + return false; + } + LocationProviderInterface p = null; + String provider = location.getProvider(); + if (provider != null) { + p = mProvidersByName.get(provider); + } + if (p == null) { + if (D) { + Log.d(TAG, "injectLocation(): unknown provider"); + } + return false; + } + synchronized (mLock) { + if (!isAllowedByCurrentUserSettingsLocked(provider)) { + if (D) { + Log.d(TAG, "Location disabled in Settings for current user:" + mCurrentUserId); + } + return false; + } else { + // NOTE: If last location is already available, location is not injected. If + // provider's normal source (like a GPS chipset) have already provided an output, + // there is no need to inject this location. + if (mLastLocation.get(provider) == null) { + updateLastLocationLocked(location, provider); + } else { + if (D) { + Log.d(TAG, "injectLocation(): Location exists. Not updating"); + } + return false; + } + } + } + return true; + } + @Override public void requestGeofence(LocationRequest request, Geofence geofence, PendingIntent intent, String packageName) { @@ -2614,30 +2672,18 @@ public class LocationManagerService extends ILocationManager.Stub { private void handleLocationChangedLocked(Location location, boolean passive) { if (D) Log.d(TAG, "incoming location: " + location); - long now = SystemClock.elapsedRealtime(); String provider = (passive ? LocationManager.PASSIVE_PROVIDER : location.getProvider()); - // Skip if the provider is unknown. LocationProviderInterface p = mProvidersByName.get(provider); if (p == null) return; - - // Update last known locations - Location noGPSLocation = location.getExtraLocation(Location.EXTRA_NO_GPS_LOCATION); - Location lastNoGPSLocation; + updateLastLocationLocked(location, provider); + // mLastLocation should have been updated from the updateLastLocationLocked call above. Location lastLocation = mLastLocation.get(provider); if (lastLocation == null) { - lastLocation = new Location(provider); - mLastLocation.put(provider, lastLocation); - } else { - lastNoGPSLocation = lastLocation.getExtraLocation(Location.EXTRA_NO_GPS_LOCATION); - if (noGPSLocation == null && lastNoGPSLocation != null) { - // New location has no no-GPS location: adopt last no-GPS location. This is set - // directly into location because we do not want to notify COARSE clients. - location.setExtraLocation(Location.EXTRA_NO_GPS_LOCATION, lastNoGPSLocation); - } + Log.e(TAG, "handleLocationChangedLocked() updateLastLocation failed"); + return; } - lastLocation.set(location); // Update last known coarse interval location if enough time has passed. Location lastLocationCoarseInterval = mLastLocationCoarseInterval.get(provider); @@ -2653,7 +2699,7 @@ public class LocationManagerService extends ILocationManager.Stub { // Don't ever return a coarse location that is more recent than the allowed update // interval (i.e. don't allow an app to keep registering and unregistering for // location updates to overcome the minimum interval). - noGPSLocation = + Location noGPSLocation = lastLocationCoarseInterval.getExtraLocation(Location.EXTRA_NO_GPS_LOCATION); // Skip if there are no UpdateRecords for this provider. @@ -2778,6 +2824,30 @@ public class LocationManagerService extends ILocationManager.Stub { } } + /** + * Updates last location with the given location + * + * @param location new location to update + * @param provider Location provider to update for + */ + private void updateLastLocationLocked(Location location, String provider) { + Location noGPSLocation = location.getExtraLocation(Location.EXTRA_NO_GPS_LOCATION); + Location lastNoGPSLocation; + Location lastLocation = mLastLocation.get(provider); + if (lastLocation == null) { + lastLocation = new Location(provider); + mLastLocation.put(provider, lastLocation); + } else { + lastNoGPSLocation = lastLocation.getExtraLocation(Location.EXTRA_NO_GPS_LOCATION); + if (noGPSLocation == null && lastNoGPSLocation != null) { + // New location has no no-GPS location: adopt last no-GPS location. This is set + // directly into location because we do not want to notify COARSE clients. + location.setExtraLocation(Location.EXTRA_NO_GPS_LOCATION, lastNoGPSLocation); + } + } + lastLocation.set(location); + } + private class LocationWorkerHandler extends Handler { public LocationWorkerHandler(Looper looper) { super(looper, null, true); diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java index 8b13aa052da0..dc2f2a5f53d5 100644 --- a/services/core/java/com/android/server/StorageManagerService.java +++ b/services/core/java/com/android/server/StorageManagerService.java @@ -2191,7 +2191,7 @@ class StorageManagerService extends IStorageManager.Stub } try { - mVold.fdeEnable(type, password, IVold.ENCRYPTION_FLAG_IN_PLACE); + mVold.fdeEnable(type, password, 0); } catch (Exception e) { Slog.wtf(TAG, e); return -1; diff --git a/services/core/java/com/android/server/VibratorService.java b/services/core/java/com/android/server/VibratorService.java index 0e51fda0f39f..c1cda985a9ba 100644 --- a/services/core/java/com/android/server/VibratorService.java +++ b/services/core/java/com/android/server/VibratorService.java @@ -26,6 +26,7 @@ import android.content.res.Resources; import android.database.ContentObserver; import android.hardware.input.InputManager; import android.hardware.vibrator.V1_0.Constants.EffectStrength; +import android.icu.text.DateFormat; import android.media.AudioManager; import android.os.PowerManager.ServiceType; import android.os.PowerSaveState; @@ -62,6 +63,7 @@ import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; import java.util.LinkedList; +import java.util.Date; public class VibratorService extends IVibratorService.Stub implements InputManager.InputDeviceListener { @@ -69,6 +71,8 @@ public class VibratorService extends IVibratorService.Stub private static final boolean DEBUG = false; private static final String SYSTEM_UI_PACKAGE = "com.android.systemui"; + private static final long[] DOUBLE_CLICK_EFFECT_FALLBACK_TIMINGS = { 0, 30, 100, 30 }; + private final LinkedList<VibrationInfo> mPreviousVibrations; private final int mPreviousVibrationsLimit; private final boolean mAllowPriorityVibrationsInLowPowerMode; @@ -110,7 +114,12 @@ public class VibratorService extends IVibratorService.Stub private class Vibration implements IBinder.DeathRecipient { private final IBinder mToken; private final VibrationEffect mEffect; + // Start time in CLOCK_BOOTTIME base. private final long mStartTime; + // Start time in unix epoch time. Only to be used for debugging purposes and to correlate + // with other system events, any duration calculations should be done use mStartTime so as + // not to be affected by discontinuities created by RTC adjustments. + private final long mStartTimeDebug; private final int mUsageHint; private final int mUid; private final String mOpPkg; @@ -119,7 +128,8 @@ public class VibratorService extends IVibratorService.Stub int usageHint, int uid, String opPkg) { mToken = token; mEffect = effect; - mStartTime = SystemClock.uptimeMillis(); + mStartTime = SystemClock.elapsedRealtime(); + mStartTimeDebug = System.currentTimeMillis(); mUsageHint = usageHint; mUid = uid; mOpPkg = opPkg; @@ -153,18 +163,22 @@ public class VibratorService extends IVibratorService.Stub return (mUid == Process.SYSTEM_UID || mUid == 0 || SYSTEM_UI_PACKAGE.equals(mOpPkg)) && !repeating; } + + public VibrationInfo toInfo() { + return new VibrationInfo(mStartTimeDebug, mEffect, mUsageHint, mUid, mOpPkg); + } } private static class VibrationInfo { - private final long mStartTime; + private final long mStartTimeDebug; private final VibrationEffect mEffect; private final int mUsageHint; private final int mUid; private final String mOpPkg; - public VibrationInfo(long startTime, VibrationEffect effect, + public VibrationInfo(long startTimeDebug, VibrationEffect effect, int usageHint, int uid, String opPkg) { - mStartTime = startTime; + mStartTimeDebug = startTimeDebug; mEffect = effect; mUsageHint = usageHint; mUid = uid; @@ -174,8 +188,8 @@ public class VibratorService extends IVibratorService.Stub @Override public String toString() { return new StringBuilder() - .append(", startTime: ") - .append(mStartTime) + .append("startTime: ") + .append(DateFormat.getDateTimeInstance().format(new Date(mStartTimeDebug))) .append(", effect: ") .append(mEffect) .append(", usageHint: ") @@ -225,7 +239,7 @@ public class VibratorService extends IVibratorService.Stub com.android.internal.R.array.config_virtualKeyVibePattern); VibrationEffect clickEffect = createEffect(clickEffectTimings); VibrationEffect doubleClickEffect = VibrationEffect.createWaveform( - new long[] {0, 30, 100, 30} /*timings*/, -1); + DOUBLE_CLICK_EFFECT_FALLBACK_TIMINGS, -1 /*repeatIndex*/); long[] tickEffectTimings = getLongIntArray(context.getResources(), com.android.internal.R.array.config_clockTickVibePattern); VibrationEffect tickEffect = createEffect(tickEffectTimings); @@ -392,17 +406,7 @@ public class VibratorService extends IVibratorService.Stub } Vibration vib = new Vibration(token, effect, usageHint, uid, opPkg); - - // Only link against waveforms since they potentially don't have a finish if - // they're repeating. Let other effects just play out until they're done. - if (effect instanceof VibrationEffect.Waveform) { - try { - token.linkToDeath(vib, 0); - } catch (RemoteException e) { - return; - } - } - + linkVibration(vib); long ident = Binder.clearCallingIdentity(); try { @@ -430,8 +434,7 @@ public class VibratorService extends IVibratorService.Stub if (mPreviousVibrations.size() > mPreviousVibrationsLimit) { mPreviousVibrations.removeFirst(); } - mPreviousVibrations.addLast(new VibrationInfo( - vib.mStartTime, vib.mEffect, vib.mUsageHint, vib.mUid, vib.mOpPkg)); + mPreviousVibrations.addLast(vib.toInfo()); } @Override // Binder call @@ -589,10 +592,23 @@ public class VibratorService extends IVibratorService.Stub AppOpsManager.OP_VIBRATE, mCurrentVibration.mUid, mCurrentVibration.mOpPkg); } catch (RemoteException e) { } + unlinkVibration(mCurrentVibration); mCurrentVibration = null; } } + private void linkVibration(Vibration vib) { + // Only link against waveforms since they potentially don't have a finish if + // they're repeating. Let other effects just play out until they're done. + if (vib.mEffect instanceof VibrationEffect.Waveform) { + try { + vib.mToken.linkToDeath(vib, 0); + } catch (RemoteException e) { + return; + } + } + } + private void unlinkVibration(Vibration vib) { if (vib.mEffect instanceof VibrationEffect.Waveform) { vib.mToken.unlinkToDeath(vib, 0); @@ -742,8 +758,7 @@ public class VibratorService extends IVibratorService.Stub synchronized (mInputDeviceVibrators) { VibrationEffect.Prebaked prebaked = (VibrationEffect.Prebaked) vib.mEffect; // Input devices don't support prebaked effect, so skip trying it with them. - final int vibratorCount = mInputDeviceVibrators.size(); - if (vibratorCount == 0) { + if (mInputDeviceVibrators.isEmpty()) { long timeout = vibratorPerformEffect(prebaked.getId(), EffectStrength.MEDIUM); if (timeout > 0) { noteVibratorOnLocked(vib.mUid, timeout); @@ -753,12 +768,11 @@ public class VibratorService extends IVibratorService.Stub if (!prebaked.shouldFallback()) { return 0; } - final int id = prebaked.getId(); - if (id < 0 || id >= mFallbackEffects.length || mFallbackEffects[id] == null) { + VibrationEffect effect = getFallbackEffect(prebaked.getId()); + if (effect == null) { Slog.w(TAG, "Failed to play prebaked effect, no fallback"); return 0; } - VibrationEffect effect = mFallbackEffects[id]; Vibration fallbackVib = new Vibration(vib.mToken, effect, vib.mUsageHint, vib.mUid, vib.mOpPkg); startVibrationInnerLocked(fallbackVib); @@ -766,6 +780,13 @@ public class VibratorService extends IVibratorService.Stub return 0; } + private VibrationEffect getFallbackEffect(int effectId) { + if (effectId < 0 || effectId >= mFallbackEffects.length) { + return null; + } + return mFallbackEffects[effectId]; + } + private void noteVibratorOnLocked(int uid, long millis) { try { mBatteryStatsService.noteVibratorOn(uid, millis); diff --git a/services/core/java/com/android/server/am/ActivityDisplay.java b/services/core/java/com/android/server/am/ActivityDisplay.java index af5cf1ee4202..e38148c7bd42 100644 --- a/services/core/java/com/android/server/am/ActivityDisplay.java +++ b/services/core/java/com/android/server/am/ActivityDisplay.java @@ -556,10 +556,10 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> { return stack == getTopStack(); } - boolean isTopFullscreenStack(ActivityStack stack) { + boolean isTopNotPinnedStack(ActivityStack stack) { for (int i = mStacks.size() - 1; i >= 0; --i) { final ActivityStack current = mStacks.get(i); - if (current.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) { + if (!current.inPinnedWindowingMode()) { return current == stack; } } diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java index 8eb519797641..e1907d37fdf2 100644 --- a/services/core/java/com/android/server/am/ActivityRecord.java +++ b/services/core/java/com/android/server/am/ActivityRecord.java @@ -2730,12 +2730,14 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo } /** - * @return true if the activity contains windows that have - * {@link LayoutParams#FLAG_SHOW_WHEN_LOCKED} set or if the activity has set - * {@link #mShowWhenLocked}. + * @return true if the activity windowing mode is not + * {@link android.app.WindowConfiguration#WINDOWING_MODE_PINNED} and activity contains + * windows that have {@link LayoutParams#FLAG_SHOW_WHEN_LOCKED} set or if the activity + * has set {@link #mShowWhenLocked}. + * Multi-windowing mode will be exited if true is returned. */ boolean canShowWhenLocked() { - return !inMultiWindowMode() && (mShowWhenLocked + return !inPinnedWindowingMode() && (mShowWhenLocked || service.mWindowManager.containsShowWhenLockedWindow(appToken)); } diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java index 10c801da7c03..831b31ed89c2 100644 --- a/services/core/java/com/android/server/am/ActivityStack.java +++ b/services/core/java/com/android/server/am/ActivityStack.java @@ -16,7 +16,6 @@ package com.android.server.am; -import static android.app.ITaskStackListener.FORCED_RESIZEABLE_REASON_SECONDARY_DISPLAY; import static android.app.ITaskStackListener.FORCED_RESIZEABLE_REASON_SPLIT_SCREEN; import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; @@ -144,7 +143,6 @@ import com.android.internal.app.IVoiceInteractor; import com.android.internal.os.BatteryStatsImpl; import com.android.server.Watchdog; import com.android.server.am.ActivityManagerService.ItemMatcher; -import com.android.server.am.EventLogTags; import com.android.server.wm.ConfigurationContainer; import com.android.server.wm.StackWindowController; import com.android.server.wm.StackWindowListener; @@ -1013,6 +1011,14 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai return; } + /** + * The intent behind moving a primary split screen stack to the back is usually to hide + * behind the home stack. Exit split screen in this case. + */ + if (getWindowingMode() == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) { + setWindowingMode(WINDOWING_MODE_FULLSCREEN); + } + getDisplay().positionChildAtBottom(this); mStackSupervisor.setFocusStackUnchecked(reason, getDisplay().getTopStack()); if (task != null) { @@ -1811,7 +1817,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai boolean behindFullscreenActivity = !stackShouldBeVisible; boolean resumeNextActivity = mStackSupervisor.isFocusedStack(this) && (isInStackLocked(starting) == null); - final boolean isTopFullscreenStack = getDisplay().isTopFullscreenStack(this); + final boolean isTopNotPinnedStack = getDisplay().isTopNotPinnedStack(this); for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) { final TaskRecord task = mTaskHistory.get(taskNdx); final ArrayList<ActivityRecord> activities = task.mActivities; @@ -1833,7 +1839,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai // Now check whether it's really visible depending on Keyguard state. final boolean reallyVisible = checkKeyguardVisibility(r, - visibleIgnoringKeyguard, isTop && isTopFullscreenStack); + visibleIgnoringKeyguard, isTop && isTopNotPinnedStack); if (visibleIgnoringKeyguard) { behindFullscreenActivity = updateBehindFullscreen(!stackShouldBeVisible, behindFullscreenActivity, r); diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java index bcb57efffc19..d895636895d3 100644 --- a/services/core/java/com/android/server/job/JobSchedulerService.java +++ b/services/core/java/com/android/server/job/JobSchedulerService.java @@ -185,6 +185,11 @@ public final class JobSchedulerService extends com.android.server.SystemService boolean mReportedActive; /** + * Are we currently in device-wide standby parole? + */ + volatile boolean mInParole; + + /** * Current limit on the number of concurrent JobServiceContext entries we want to * keep actively running a job. */ @@ -1720,28 +1725,31 @@ public final class JobSchedulerService extends com.android.server.SystemService } // If the app is in a non-active standby bucket, make sure we've waited - // an appropriate amount of time since the last invocation - final int bucket = job.getStandbyBucket(); - if (mHeartbeat < mNextBucketHeartbeat[bucket]) { - // Only skip this job if it's still waiting for the end of its (initial) nominal - // bucket interval. Once it's waited that long, we let it go ahead and clear. - // The final (NEVER) bucket is special; we never age those apps' jobs into - // runnability. - if (bucket >= mConstants.STANDBY_BEATS.length - || (mHeartbeat < job.getBaseHeartbeat() + mConstants.STANDBY_BEATS[bucket])) { - // TODO: log/trace that we're deferring the job due to bucketing if we hit this - if (job.getWhenStandbyDeferred() == 0) { + // an appropriate amount of time since the last invocation. During device- + // wide parole, standby bucketing is ignored. + if (!mInParole) { + final int bucket = job.getStandbyBucket(); + if (mHeartbeat < mNextBucketHeartbeat[bucket]) { + // Only skip this job if it's still waiting for the end of its (initial) nominal + // bucket interval. Once it's waited that long, we let it go ahead and clear. + // The final (NEVER) bucket is special; we never age those apps' jobs into + // runnability. + if (bucket >= mConstants.STANDBY_BEATS.length + || (mHeartbeat < job.getBaseHeartbeat() + mConstants.STANDBY_BEATS[bucket])) { + // TODO: log/trace that we're deferring the job due to bucketing if we hit this + if (job.getWhenStandbyDeferred() == 0) { + if (DEBUG_STANDBY) { + Slog.v(TAG, "Bucket deferral: " + mHeartbeat + " < " + + mNextBucketHeartbeat[job.getStandbyBucket()] + " for " + job); + } + job.setWhenStandbyDeferred(sElapsedRealtimeClock.millis()); + } + return false; + } else { if (DEBUG_STANDBY) { - Slog.v(TAG, "Bucket deferral: " + mHeartbeat + " < " - + mNextBucketHeartbeat[job.getStandbyBucket()] + " for " + job); + Slog.v(TAG, "Bucket deferred job aged into runnability at " + + mHeartbeat + " : " + job); } - job.setWhenStandbyDeferred(sElapsedRealtimeClock.millis()); - } - return false; - } else { - if (DEBUG_STANDBY) { - Slog.v(TAG, "Bucket deferred job aged into runnability at " - + mHeartbeat + " : " + job); } } } @@ -2086,7 +2094,10 @@ public final class JobSchedulerService extends com.android.server.SystemService @Override public void onParoleStateChanged(boolean isParoleOn) { - // Unused + if (DEBUG_STANDBY) { + Slog.i(TAG, "Global parole state now " + (isParoleOn ? "ON" : "OFF")); + } + mInParole = isParoleOn; } } diff --git a/services/core/java/com/android/server/job/JobServiceContext.java b/services/core/java/com/android/server/job/JobServiceContext.java index 6a3fd04a4e26..709deeb81ac9 100644 --- a/services/core/java/com/android/server/job/JobServiceContext.java +++ b/services/core/java/com/android/server/job/JobServiceContext.java @@ -38,12 +38,14 @@ import android.os.PowerManager; import android.os.RemoteException; import android.os.UserHandle; import android.os.WorkSource; +import android.util.EventLog; import android.util.Slog; import android.util.TimeUtils; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.IBatteryStats; +import com.android.server.EventLogTags; import com.android.server.job.controllers.JobStatus; /** @@ -222,17 +224,20 @@ public final class JobServiceContext implements ServiceConnection { isDeadlineExpired, triggeredUris, triggeredAuthorities, job.network); mExecutionStartTimeElapsed = sElapsedRealtimeClock.millis(); - if (DEBUG_STANDBY) { - final long whenDeferred = job.getWhenStandbyDeferred(); - if (whenDeferred > 0) { + final long whenDeferred = job.getWhenStandbyDeferred(); + if (whenDeferred > 0) { + final long deferral = mExecutionStartTimeElapsed - whenDeferred; + EventLog.writeEvent(EventLogTags.JOB_DEFERRED_EXECUTION, deferral); + if (DEBUG_STANDBY) { StringBuilder sb = new StringBuilder(128); sb.append("Starting job deferred for standby by "); - TimeUtils.formatDuration(mExecutionStartTimeElapsed - whenDeferred, sb); - sb.append(" : "); + TimeUtils.formatDuration(deferral, sb); + sb.append(" ms : "); sb.append(job.toShortString()); Slog.v(TAG, sb.toString()); } } + // Once we'e begun executing a job, we by definition no longer care whether // it was inflated from disk with not-yet-coherent delay/deadline bounds. job.clearPersistedUtcTimes(); diff --git a/services/core/java/com/android/server/pm/InstantAppRegistry.java b/services/core/java/com/android/server/pm/InstantAppRegistry.java index c964f912feb1..af20cd77e626 100644 --- a/services/core/java/com/android/server/pm/InstantAppRegistry.java +++ b/services/core/java/com/android/server/pm/InstantAppRegistry.java @@ -49,7 +49,6 @@ import com.android.internal.os.BackgroundThread; import com.android.internal.os.SomeArgs; import com.android.internal.util.ArrayUtils; import com.android.internal.util.XmlUtils; -import com.android.server.pm.permission.BasePermission; import libcore.io.IoUtils; import org.xmlpull.v1.XmlPullParser; @@ -302,7 +301,7 @@ class InstantAppRegistry { // into account but also allow the value from the old computation to avoid // data loss. final String[] signaturesSha256Digests = PackageUtils.computeSignaturesSha256Digests( - pkg.mSignatures); + pkg.mSigningDetails.signatures); final String signaturesSha256Digest = PackageUtils.computeSignaturesSha256Digest( signaturesSha256Digests); @@ -313,7 +312,7 @@ class InstantAppRegistry { } // For backwards compatibility we accept match based on first signature - if (pkg.mSignatures.length > 1 && currentCookieFile.equals(computeInstantCookieFile( + if (pkg.mSigningDetails.signatures.length > 1 && currentCookieFile.equals(computeInstantCookieFile( pkg.packageName, signaturesSha256Digests[0], userId))) { return; } @@ -1176,12 +1175,13 @@ class InstantAppRegistry { // We prefer the modern computation procedure where all certs are taken // into account and delete the file derived via the legacy hash computation. File newCookieFile = computeInstantCookieFile(pkg.packageName, - PackageUtils.computeSignaturesSha256Digest(pkg.mSignatures), userId); - if (pkg.mSignatures.length > 0) { - File oldCookieFile = peekInstantCookieFile(pkg.packageName, userId); - if (oldCookieFile != null && !newCookieFile.equals(oldCookieFile)) { - oldCookieFile.delete(); - } + PackageUtils.computeSignaturesSha256Digest(pkg.mSigningDetails.signatures), userId); + if (!pkg.mSigningDetails.hasSignatures()) { + Slog.wtf(LOG_TAG, "Parsed Instant App contains no valid signatures!"); + } + File oldCookieFile = peekInstantCookieFile(pkg.packageName, userId); + if (oldCookieFile != null && !newCookieFile.equals(oldCookieFile)) { + oldCookieFile.delete(); } cancelPendingPersistLPw(pkg, userId); addPendingPersistCookieLPw(userId, pkg, cookie, newCookieFile); diff --git a/services/core/java/com/android/server/pm/KeySetManagerService.java b/services/core/java/com/android/server/pm/KeySetManagerService.java index fca95857c7e0..93d3b77511bc 100644 --- a/services/core/java/com/android/server/pm/KeySetManagerService.java +++ b/services/core/java/com/android/server/pm/KeySetManagerService.java @@ -188,7 +188,7 @@ public class KeySetManagerService { throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, "Passed invalid package to keyset validation."); } - ArraySet<PublicKey> signingKeys = pkg.mSigningKeys; + ArraySet<PublicKey> signingKeys = pkg.mSigningDetails.publicKeys; if (signingKeys == null || !(signingKeys.size() > 0) || signingKeys.contains(null)) { throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, "Package has invalid signing-key-set."); @@ -223,7 +223,7 @@ public class KeySetManagerService { PackageSetting ps = mPackages.get(pkg.packageName); Preconditions.checkNotNull(ps, "pkg: " + pkg.packageName + "does not have a corresponding entry in mPackages."); - addSigningKeySetToPackageLPw(ps, pkg.mSigningKeys); + addSigningKeySetToPackageLPw(ps, pkg.mSigningDetails.publicKeys); if (pkg.mKeySetMapping != null) { addDefinedKeySetsToPackageLPw(ps, pkg.mKeySetMapping); if (pkg.mUpgradeKeySets != null) { @@ -371,7 +371,7 @@ public class KeySetManagerService { long[] upgradeKeySets = oldPS.keySetData.getUpgradeKeySets(); for (int i = 0; i < upgradeKeySets.length; i++) { Set<PublicKey> upgradeSet = getPublicKeysFromKeySetLPr(upgradeKeySets[i]); - if (upgradeSet != null && newPkg.mSigningKeys.containsAll(upgradeSet)) { + if (upgradeSet != null && newPkg.mSigningDetails.publicKeys.containsAll(upgradeSet)) { return true; } } diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index 5577de8ccde9..4e918983ddd6 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -58,7 +58,6 @@ import android.content.pm.PackageParser; import android.content.pm.PackageParser.ApkLite; import android.content.pm.PackageParser.PackageLite; import android.content.pm.PackageParser.PackageParserException; -import android.content.pm.Signature; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.os.Binder; @@ -84,6 +83,7 @@ import android.util.ArraySet; import android.util.ExceptionUtils; import android.util.MathUtils; import android.util.Slog; +import android.util.apk.ApkSignatureVerifier; import com.android.internal.annotations.GuardedBy; import com.android.internal.content.NativeLibraryHelper; @@ -107,7 +107,7 @@ import java.io.FileDescriptor; import java.io.FileFilter; import java.io.FileOutputStream; import java.io.IOException; -import java.security.cert.Certificate; +import java.security.cert.CertificateException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -227,9 +227,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { @GuardedBy("mLock") private long mVersionCode; @GuardedBy("mLock") - private Signature[] mSignatures; - @GuardedBy("mLock") - private Certificate[][] mCertificates; + private PackageParser.SigningDetails mSigningDetails; /** * Path to the validated base APK for this session, which may point at an @@ -857,7 +855,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } Preconditions.checkNotNull(mPackageName); - Preconditions.checkNotNull(mSignatures); + Preconditions.checkNotNull(mSigningDetails); Preconditions.checkNotNull(mResolvedBaseFile); if (needToAskForPermissionsLocked()) { @@ -938,7 +936,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { mRelinquished = true; mPm.installStage(mPackageName, stageDir, localObserver, params, - mInstallerPackageName, mInstallerUid, user, mCertificates); + mInstallerPackageName, mInstallerUid, user, mSigningDetails); } /** @@ -957,7 +955,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { throws PackageManagerException { mPackageName = null; mVersionCode = -1; - mSignatures = null; + mSigningDetails = PackageParser.SigningDetails.UNKNOWN; mResolvedBaseFile = null; mResolvedStagedFiles.clear(); @@ -1009,9 +1007,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { mPackageName = apk.packageName; mVersionCode = apk.getLongVersionCode(); } - if (mSignatures == null) { - mSignatures = apk.signatures; - mCertificates = apk.certificates; + if (mSigningDetails == PackageParser.SigningDetails.UNKNOWN) { + mSigningDetails = apk.signingDetails; } assertApkConsistentLocked(String.valueOf(addedFile), apk); @@ -1060,8 +1057,15 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { mPackageName = pkgInfo.packageName; mVersionCode = pkgInfo.getLongVersionCode(); } - if (mSignatures == null) { - mSignatures = pkgInfo.signatures; + if (mSigningDetails == PackageParser.SigningDetails.UNKNOWN) { + try { + mSigningDetails = ApkSignatureVerifier.plsCertsNoVerifyOnlyCerts( + pkgInfo.applicationInfo.sourceDir, + PackageParser.SigningDetails.SignatureSchemeVersion.JAR); + } catch (PackageParserException e) { + throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, + "Couldn't obtain signatures from base APK"); + } } } @@ -1155,7 +1159,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { + " version code " + apk.versionCode + " inconsistent with " + mVersionCode); } - if (!Signature.areExactMatch(mSignatures, apk.signatures)) { + if (!mSigningDetails.signaturesMatchExactly(apk.signingDetails)) { throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, tag + " signatures are inconsistent"); } diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 44aad4405a1b..3df7c47e16d7 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -337,7 +337,6 @@ import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.PublicKey; import java.security.SecureRandom; -import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.util.ArrayList; import java.util.Arrays; @@ -5360,7 +5359,7 @@ public class PackageManagerService extends IPackageManager.Stub || filterAppAccessLPr(ps2, callingUid, callingUserId)) { return PackageManager.SIGNATURE_UNKNOWN_PACKAGE; } - return compareSignatures(p1.mSignatures, p2.mSignatures); + return compareSignatures(p1.mSigningDetails.signatures, p2.mSigningDetails.signatures); } } @@ -8194,19 +8193,11 @@ public class PackageManagerService extends IPackageManager.Stub && ps.timeStamp == lastModifiedTime && !isCompatSignatureUpdateNeeded(pkg) && !isRecoverSignatureUpdateNeeded(pkg)) { - long mSigningKeySetId = ps.keySetData.getProperSigningKeySet(); - final KeySetManagerService ksms = mSettings.mKeySetManagerService; - ArraySet<PublicKey> signingKs; - synchronized (mPackages) { - signingKs = ksms.getPublicKeysFromKeySetLPr(mSigningKeySetId); - } - if (ps.signatures.mSignatures != null - && ps.signatures.mSignatures.length != 0 - && signingKs != null) { - // Optimization: reuse the existing cached certificates + if ((ps.pkg != null) && + PackageParser.SigningDetails.UNKNOWN != ps.pkg.mSigningDetails) { + // Optimization: reuse the existing cached signing data // if the package appears to be unchanged. - pkg.mSignatures = ps.signatures.mSignatures; - pkg.mSigningKeys = signingKs; + pkg.mSigningDetails = ps.pkg.mSigningDetails; return; } @@ -8546,7 +8537,7 @@ public class PackageManagerService extends IPackageManager.Stub * Check to make sure the signatures match first. If they don't, * wipe the installed application and its data. */ - if (compareSignatures(ps.signatures.mSignatures, pkg.mSignatures) + if (compareSignatures(ps.signatures.mSignatures, pkg.mSigningDetails.signatures) != PackageManager.SIGNATURE_MATCH) { logCriticalInfo(Log.WARN, "Package " + ps.name + " appeared on system, but" + " signatures don't match existing userdata copy; removing"); @@ -9518,9 +9509,10 @@ public class PackageManagerService extends IPackageManager.Stub final String[] expectedCertDigests = requiredCertDigests[i]; // For apps targeting O MR1 we require explicit enumeration of all certs. final String[] libCertDigests = (targetSdk > Build.VERSION_CODES.O) - ? PackageUtils.computeSignaturesSha256Digests(libPkg.mSignatures) + ? PackageUtils.computeSignaturesSha256Digests( + libPkg.mSigningDetails.signatures) : PackageUtils.computeSignaturesSha256Digests( - new Signature[]{libPkg.mSignatures[0]}); + new Signature[]{libPkg.mSigningDetails.signatures[0]}); // Take a shortcut if sizes don't match. Note that if an app doesn't // target O we don't parse the "additional-certificate" tags similarly @@ -9856,14 +9848,14 @@ public class PackageManagerService extends IPackageManager.Stub if (ksms.checkUpgradeKeySetLocked(signatureCheckPs, pkg)) { // We just determined the app is signed correctly, so bring // over the latest parsed certs. - pkgSetting.signatures.mSignatures = pkg.mSignatures; + pkgSetting.signatures.mSignatures = pkg.mSigningDetails.signatures; } else { if ((parseFlags & PackageParser.PARSE_IS_SYSTEM_DIR) == 0) { throw new PackageManagerException(INSTALL_FAILED_UPDATE_INCOMPATIBLE, "Package " + pkg.packageName + " upgrade keys do not match the " + "previously installed version"); } else { - pkgSetting.signatures.mSignatures = pkg.mSignatures; + pkgSetting.signatures.mSignatures = pkg.mSigningDetails.signatures; String msg = "System package " + pkg.packageName + " signature changed; retaining data."; reportSettingsProblem(Log.WARN, msg); @@ -9873,7 +9865,8 @@ public class PackageManagerService extends IPackageManager.Stub try { final boolean compareCompat = isCompatSignatureUpdateNeeded(pkg); final boolean compareRecover = isRecoverSignatureUpdateNeeded(pkg); - final boolean compatMatch = verifySignatures(signatureCheckPs, pkg.mSignatures, + final boolean compatMatch = verifySignatures(signatureCheckPs, + pkg.mSigningDetails.signatures, compareCompat, compareRecover); // The new KeySets will be re-added later in the scanning process. if (compatMatch) { @@ -9883,14 +9876,14 @@ public class PackageManagerService extends IPackageManager.Stub } // We just determined the app is signed correctly, so bring // over the latest parsed certs. - pkgSetting.signatures.mSignatures = pkg.mSignatures; + pkgSetting.signatures.mSignatures = pkg.mSigningDetails.signatures; } catch (PackageManagerException e) { if ((parseFlags & PackageParser.PARSE_IS_SYSTEM_DIR) == 0) { throw e; } // The signature has changed, but this package is in the system // image... let's recover! - pkgSetting.signatures.mSignatures = pkg.mSignatures; + pkgSetting.signatures.mSignatures = pkg.mSigningDetails.signatures; // However... if this package is part of a shared user, but it // doesn't match the signature of the shared user, let's fail. // What this means is that you can't change the signatures @@ -9898,7 +9891,7 @@ public class PackageManagerService extends IPackageManager.Stub // that unreasonable. if (signatureCheckPs.sharedUser != null) { if (compareSignatures(signatureCheckPs.sharedUser.signatures.mSignatures, - pkg.mSignatures) != PackageManager.SIGNATURE_MATCH) { + pkg.mSigningDetails.signatures) != PackageManager.SIGNATURE_MATCH) { throw new PackageManagerException( INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES, "Signature mismatch for shared user: " @@ -13204,7 +13197,7 @@ public class PackageManagerService extends IPackageManager.Stub void installStage(String packageName, File stagedDir, IPackageInstallObserver2 observer, PackageInstaller.SessionParams sessionParams, String installerPackageName, int installerUid, UserHandle user, - Certificate[][] certificates) { + PackageParser.SigningDetails signingDetails) { if (DEBUG_EPHEMERAL) { if ((sessionParams.installFlags & PackageManager.INSTALL_INSTANT_APP) != 0) { Slog.d(TAG, "Ephemeral install of " + packageName); @@ -13222,7 +13215,7 @@ public class PackageManagerService extends IPackageManager.Stub final InstallParams params = new InstallParams(origin, null, observer, sessionParams.installFlags, installerPackageName, sessionParams.volumeUuid, verificationInfo, user, sessionParams.abiOverride, - sessionParams.grantedRuntimePermissions, certificates, installReason); + sessionParams.grantedRuntimePermissions, signingDetails, installReason); params.setTraceMethod("installStage").setTraceCookie(System.identityHashCode(params)); msg.obj = params; @@ -13826,7 +13819,7 @@ public class PackageManagerService extends IPackageManager.Stub final PackageParser.Package pkg = mPackages.get(verifierInfo.packageName); if (pkg == null) { return -1; - } else if (pkg.mSignatures.length != 1) { + } else if (pkg.mSigningDetails.signatures.length != 1) { Slog.i(TAG, "Verifier package " + verifierInfo.packageName + " has more than one signature; ignoring"); return -1; @@ -13840,7 +13833,7 @@ public class PackageManagerService extends IPackageManager.Stub final byte[] expectedPublicKey; try { - final Signature verifierSig = pkg.mSignatures[0]; + final Signature verifierSig = pkg.mSigningDetails.signatures[0]; final PublicKey publicKey = verifierSig.getPublicKey(); expectedPublicKey = publicKey.getEncoded(); } catch (CertificateException e) { @@ -14532,13 +14525,13 @@ public class PackageManagerService extends IPackageManager.Stub final String packageAbiOverride; final String[] grantedRuntimePermissions; final VerificationInfo verificationInfo; - final Certificate[][] certificates; + final PackageParser.SigningDetails signingDetails; final int installReason; InstallParams(OriginInfo origin, MoveInfo move, IPackageInstallObserver2 observer, int installFlags, String installerPackageName, String volumeUuid, VerificationInfo verificationInfo, UserHandle user, String packageAbiOverride, - String[] grantedPermissions, Certificate[][] certificates, int installReason) { + String[] grantedPermissions, PackageParser.SigningDetails signingDetails, int installReason) { super(user); this.origin = origin; this.move = move; @@ -14549,7 +14542,7 @@ public class PackageManagerService extends IPackageManager.Stub this.verificationInfo = verificationInfo; this.packageAbiOverride = packageAbiOverride; this.grantedRuntimePermissions = grantedPermissions; - this.certificates = certificates; + this.signingDetails = signingDetails; this.installReason = installReason; } @@ -14980,7 +14973,7 @@ public class PackageManagerService extends IPackageManager.Stub /** If non-null, drop an async trace when the install completes */ final String traceMethod; final int traceCookie; - final Certificate[][] certificates; + final PackageParser.SigningDetails signingDetails; final int installReason; // The list of instruction sets supported by this app. This is currently @@ -14992,7 +14985,7 @@ public class PackageManagerService extends IPackageManager.Stub int installFlags, String installerPackageName, String volumeUuid, UserHandle user, String[] instructionSets, String abiOverride, String[] installGrantPermissions, - String traceMethod, int traceCookie, Certificate[][] certificates, + String traceMethod, int traceCookie, PackageParser.SigningDetails signingDetails, int installReason) { this.origin = origin; this.move = move; @@ -15006,7 +14999,7 @@ public class PackageManagerService extends IPackageManager.Stub this.installGrantPermissions = installGrantPermissions; this.traceMethod = traceMethod; this.traceCookie = traceCookie; - this.certificates = certificates; + this.signingDetails = signingDetails; this.installReason = installReason; } @@ -15102,7 +15095,7 @@ public class PackageManagerService extends IPackageManager.Stub params.installerPackageName, params.volumeUuid, params.getUser(), null /*instructionSets*/, params.packageAbiOverride, params.grantedRuntimePermissions, - params.traceMethod, params.traceCookie, params.certificates, + params.traceMethod, params.traceCookie, params.signingDetails, params.installReason); if (isFwdLocked()) { throw new IllegalArgumentException("Forward locking only supported in ASEC"); @@ -15112,7 +15105,7 @@ public class PackageManagerService extends IPackageManager.Stub /** Existing install */ FileInstallArgs(String codePath, String resourcePath, String[] instructionSets) { super(OriginInfo.fromNothing(), null, null, 0, null, null, null, instructionSets, - null, null, null, 0, null /*certificates*/, + null, null, null, 0, PackageParser.SigningDetails.UNKNOWN, PackageManager.INSTALL_REASON_UNKNOWN); this.codeFile = (codePath != null) ? new File(codePath) : null; this.resourceFile = (resourcePath != null) ? new File(resourcePath) : null; @@ -15333,7 +15326,7 @@ public class PackageManagerService extends IPackageManager.Stub params.installerPackageName, params.volumeUuid, params.getUser(), null /* instruction sets */, params.packageAbiOverride, params.grantedRuntimePermissions, - params.traceMethod, params.traceCookie, params.certificates, + params.traceMethod, params.traceCookie, params.signingDetails, params.installReason); } @@ -15672,7 +15665,8 @@ public class PackageManagerService extends IPackageManager.Stub } } else { // default to original signature matching - if (compareSignatures(oldPackage.mSignatures, pkg.mSignatures) + if (compareSignatures(oldPackage.mSigningDetails.signatures, + pkg.mSigningDetails.signatures) != PackageManager.SIGNATURE_MATCH) { res.setError(INSTALL_FAILED_UPDATE_INCOMPATIBLE, "New package has a different signature: " + pkgName); @@ -16484,14 +16478,8 @@ public class PackageManagerService extends IPackageManager.Stub try { // either use what we've been given or parse directly from the APK - if (args.certificates != null) { - try { - PackageParser.populateCertificates(pkg, args.certificates); - } catch (PackageParserException e) { - // there was something wrong with the certificates we were given; - // try to pull them from the APK - PackageParser.collectCertificates(pkg, parseFlags); - } + if (args.signingDetails != PackageParser.SigningDetails.UNKNOWN) { + pkg.setSigningDetails(args.signingDetails); } else { PackageParser.collectCertificates(pkg, parseFlags); } @@ -16610,7 +16598,8 @@ public class PackageManagerService extends IPackageManager.Stub final boolean compareCompat = isCompatSignatureUpdateNeeded(pkg); final boolean compareRecover = isRecoverSignatureUpdateNeeded(pkg); final boolean compatMatch = verifySignatures( - signatureCheckPs, pkg.mSignatures, compareCompat, compareRecover); + signatureCheckPs, pkg.mSigningDetails.signatures, compareCompat, + compareRecover); // The new KeySets will be re-added later in the scanning process. if (compatMatch) { synchronized (mPackages) { @@ -16661,7 +16650,7 @@ public class PackageManagerService extends IPackageManager.Stub sigsOk = ksms.checkUpgradeKeySetLocked(sourcePackageSetting, pkg); } else { sigsOk = compareSignatures(sourcePackageSetting.signatures.mSignatures, - pkg.mSignatures) == PackageManager.SIGNATURE_MATCH; + pkg.mSigningDetails.signatures) == PackageManager.SIGNATURE_MATCH; } if (!sigsOk) { // If the owning package is the system itself, we log but allow @@ -16937,7 +16926,8 @@ public class PackageManagerService extends IPackageManager.Stub for (ActivityIntentInfo filter : a.intents) { if (filter.needsVerification() && needsNetworkVerificationLPr(filter)) { if (DEBUG_DOMAIN_VERIFICATION) { - Slog.d(TAG, "Intent filter needs verification, so processing all filters"); + Slog.d(TAG, + "Intent filter needs verification, so processing all filters"); } needToVerify = true; break; @@ -22245,8 +22235,8 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName()); final OriginInfo origin = OriginInfo.fromExistingFile(codeFile); final InstallParams params = new InstallParams(origin, move, installObserver, installFlags, installerPackageName, volumeUuid, null /*verificationInfo*/, user, - packageAbiOverride, null /*grantedPermissions*/, null /*certificates*/, - PackageManager.INSTALL_REASON_UNKNOWN); + packageAbiOverride, null /*grantedPermissions*/, + PackageParser.SigningDetails.UNKNOWN, PackageManager.INSTALL_REASON_UNKNOWN); params.setTraceMethod("movePackage").setTraceCookie(System.identityHashCode(params)); msg.obj = params; diff --git a/services/core/java/com/android/server/pm/SELinuxMMAC.java b/services/core/java/com/android/server/pm/SELinuxMMAC.java index fbf3d82455c8..37f9a74fe0ba 100644 --- a/services/core/java/com/android/server/pm/SELinuxMMAC.java +++ b/services/core/java/com/android/server/pm/SELinuxMMAC.java @@ -17,8 +17,6 @@ package com.android.server.pm; import android.content.pm.PackageParser; -import android.content.pm.PackageUserState; -import android.content.pm.SELinuxUtil; import android.content.pm.Signature; import android.os.Environment; import android.util.Slog; @@ -453,7 +451,7 @@ final class Policy { public String getMatchedSeInfo(PackageParser.Package pkg) { // Check for exact signature matches across all certs. Signature[] certs = mCerts.toArray(new Signature[0]); - if (!Signature.areExactMatch(certs, pkg.mSignatures)) { + if (!Signature.areExactMatch(certs, pkg.mSigningDetails.signatures)) { return null; } diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index 4cf18149d853..b5d3af1c6a28 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -955,7 +955,7 @@ public final class Settings { } // Update signatures if needed. if (p.signatures.mSignatures == null) { - p.signatures.assignSignatures(pkg.mSignatures); + p.signatures.assignSignatures(pkg.mSigningDetails.signatures); } // Update flags if needed. if (pkg.applicationInfo.flags != p.pkgFlags) { @@ -964,7 +964,7 @@ public final class Settings { // If this app defines a shared user id initialize // the shared user signatures as well. if (p.sharedUser != null && p.sharedUser.signatures.mSignatures == null) { - p.sharedUser.signatures.assignSignatures(pkg.mSignatures); + p.sharedUser.signatures.assignSignatures(pkg.mSigningDetails.signatures); } // Update static shared library dependencies if needed if (pkg.usesStaticLibraries != null && pkg.usesStaticLibrariesVersions != null @@ -4565,10 +4565,8 @@ public final class Settings { } pw.print(prefix); pw.print(" versionName="); pw.println(ps.pkg.mVersionName); pw.print(prefix); pw.print(" splits="); dumpSplitNames(pw, ps.pkg); pw.println(); - final int apkSigningVersion = PackageParser.getApkSigningVersion(ps.pkg); - if (apkSigningVersion != PackageParser.APK_SIGNING_UNKNOWN) { - pw.print(prefix); pw.print(" apkSigningVersion="); pw.println(apkSigningVersion); - } + final int apkSigningVersion = ps.pkg.mSigningDetails.signatureSchemeVersion; + pw.print(prefix); pw.print(" apkSigningVersion="); pw.println(apkSigningVersion); pw.print(prefix); pw.print(" applicationInfo="); pw.println(ps.pkg.applicationInfo.toString()); pw.print(prefix); pw.print(" flags="); printFlags(pw, ps.pkg.applicationInfo.flags, 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 34c3ce359e86..6e07eaac9c44 100644 --- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java +++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java @@ -27,7 +27,6 @@ import android.app.admin.DevicePolicyManager; import android.companion.CompanionDeviceManager; import android.content.Context; import android.content.Intent; -import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.IPackageManager; import android.content.pm.PackageList; @@ -62,8 +61,6 @@ import android.util.Slog; import android.util.Xml; import com.android.internal.util.XmlUtils; import com.android.server.LocalServices; -import com.android.server.pm.PackageManagerService; -import com.android.server.pm.PackageSetting; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -1169,7 +1166,8 @@ public final class DefaultPermissionGrantPolicy { final String systemPackageName = mServiceInternal.getKnownPackageName( PackageManagerInternal.PACKAGE_SYSTEM, UserHandle.USER_SYSTEM); final PackageParser.Package systemPackage = getPackage(systemPackageName); - return compareSignatures(systemPackage.mSignatures, pkg.mSignatures) + return compareSignatures(systemPackage.mSigningDetails.signatures, + pkg.mSigningDetails.signatures) == PackageManager.SIGNATURE_MATCH; } diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java index 90ac4ab7dd42..786b998862de 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java @@ -29,7 +29,6 @@ import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER; import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; -import android.app.AppOpsManager; import android.content.Context; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; @@ -56,21 +55,17 @@ import android.util.Log; import android.util.Slog; import android.util.SparseArray; -import com.android.internal.R; import com.android.internal.annotations.GuardedBy; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.os.RoSystemProperties; import com.android.internal.util.ArrayUtils; -import com.android.server.FgThread; import com.android.server.LocalServices; import com.android.server.ServiceThread; import com.android.server.SystemConfig; import com.android.server.Watchdog; -import com.android.server.pm.PackageManagerService; import com.android.server.pm.PackageManagerServiceUtils; import com.android.server.pm.PackageSetting; -import com.android.server.pm.ProcessLoggingHandler; import com.android.server.pm.SharedUserSetting; import com.android.server.pm.UserManagerService; import com.android.server.pm.permission.DefaultPermissionGrantPolicy.DefaultPermissionGrantedCallback; @@ -1015,10 +1010,10 @@ Slog.e(TAG, "TODD: Packages: " + Arrays.toString(packages)); final PackageParser.Package systemPackage = mPackageManagerInt.getPackage(systemPackageName); boolean allowed = (PackageManagerServiceUtils.compareSignatures( - bp.getSourceSignatures(), pkg.mSignatures) + bp.getSourceSignatures(), pkg.mSigningDetails.signatures) == PackageManager.SIGNATURE_MATCH) || (PackageManagerServiceUtils.compareSignatures( - systemPackage.mSignatures, pkg.mSignatures) + systemPackage.mSigningDetails.signatures, pkg.mSigningDetails.signatures) == PackageManager.SIGNATURE_MATCH); if (!allowed && (privilegedPermission || oemPermission)) { if (pkg.isSystem()) { diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 252eff567cfd..722486351e19 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -49,6 +49,7 @@ import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; import static android.view.WindowManager.LayoutParams.TYPE_BOOT_PROGRESS; +import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER; import static android.view.WindowManager.LayoutParams.TYPE_DRAWN_APPLICATION; import static android.view.WindowManager.LayoutParams.TYPE_DREAM; import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD; @@ -3496,37 +3497,47 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo @Override void assignChildLayers(SurfaceControl.Transaction t) { - int layer = 0; - // We allow stacks to change visual order from the AM specified order due to - // Z-boosting during animations. However we must take care to ensure TaskStacks - // which are marked as alwaysOnTop remain that way. - for (int i = 0; i < mChildren.size(); i++) { - final TaskStack s = mChildren.get(i); - s.assignChildLayers(); - if (!s.needsZBoost() && !s.isAlwaysOnTop()) { - s.assignLayer(t, layer++); + final int NORMAL_STACK_STATE = 0; + final int SPLIT_SCREEN_STACK_STATE = 1; + final int ASSISTANT_STACK_STATE = 2; + final int BOOSTED_STATE = 3; + final int ALWAYS_ON_TOP_STATE = 4; + + int layer = 0; + for (int state = 0; state <= ALWAYS_ON_TOP_STATE; state++) { + for (int i = 0; i < mChildren.size(); i++) { + final TaskStack s = mChildren.get(i); + layer++; + if (state == NORMAL_STACK_STATE && !s.inSplitScreenPrimaryWindowingMode() && + !s.isActivityTypeAssistant() && + !s.needsZBoost() && !s.isAlwaysOnTop()) { + s.assignLayer(t, layer); + } else if (state == SPLIT_SCREEN_STACK_STATE && + s.inSplitScreenPrimaryWindowingMode()) { + s.assignLayer(t, layer); + } else if (state == ASSISTANT_STACK_STATE && + s.isActivityTypeAssistant()) { + s.assignLayer(t, layer); + } else if (state == BOOSTED_STATE && s.needsZBoost()) { + s.assignLayer(t, layer); + } else if (state == ALWAYS_ON_TOP_STATE && + s.isAlwaysOnTop()) { + s.assignLayer(t, layer); + } } - } - for (int i = 0; i < mChildren.size(); i++) { - final TaskStack s = mChildren.get(i); - if (s.needsZBoost() && !s.isAlwaysOnTop()) { - s.assignLayer(t, layer++); + // The appropriate place for App-Transitions to occur is right + // above all other animations but still below things in the Picture-and-Picture + // windowing mode. + if (state == BOOSTED_STATE && mAppAnimationLayer != null) { + t.setLayer(mAppAnimationLayer, layer++); } } for (int i = 0; i < mChildren.size(); i++) { final TaskStack s = mChildren.get(i); - if (s.isAlwaysOnTop()) { - s.assignLayer(t, layer++); - } + s.assignChildLayers(t); } - // The appropriate place for App-Transitions to occur is right - // above all other animations but still below things in the Picture-and-Picture - // windowing mode. - if (mAppAnimationLayer != null) { - t.setLayer(mAppAnimationLayer, layer++); - } } @Override @@ -3560,6 +3571,18 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo && imeContainer.getSurfaceControl() != null; for (int j = 0; j < mChildren.size(); ++j) { final WindowToken wt = mChildren.get(j); + + // The divider is unique in that it does not have an AppWindowToken but needs to be + // interleaved with them. In particular it must be above any split-screen stacks + // but below any always-on-top stacks. + if (wt.windowType == TYPE_DOCK_DIVIDER) { + final TaskStack dockedStack = getSplitScreenPrimaryStack(); + if (dockedStack != null) { + wt.assignRelativeLayer(t, dockedStack.getSurfaceControl(), + Integer.MAX_VALUE); + continue; + } + } wt.assignLayer(t, j); wt.assignChildLayers(t); diff --git a/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java b/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java index 3a41eb0e2afc..dc62cc89c14d 100644 --- a/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java +++ b/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java @@ -67,6 +67,9 @@ class SurfaceAnimationRunner { @VisibleForTesting final ArrayMap<SurfaceControl, RunningAnimation> mRunningAnimations = new ArrayMap<>(); + @GuardedBy("mLock") + private boolean mAnimationStartDeferred; + SurfaceAnimationRunner() { this(null /* callbackProvider */, null /* animatorFactory */, new Transaction()); } @@ -86,13 +89,41 @@ class SurfaceAnimationRunner { : SfValueAnimator::new; } + /** + * Defers starting of animations until {@link #continueStartingAnimations} is called. This + * method is NOT nestable. + * + * @see #continueStartingAnimations + */ + void deferStartingAnimations() { + synchronized (mLock) { + mAnimationStartDeferred = true; + } + } + + /** + * Continues starting of animations. + * + * @see #deferStartingAnimations + */ + void continueStartingAnimations() { + synchronized (mLock) { + mAnimationStartDeferred = false; + if (!mPendingAnimations.isEmpty()) { + mChoreographer.postFrameCallback(this::startAnimations); + } + } + } + void startAnimation(AnimationSpec a, SurfaceControl animationLeash, Transaction t, Runnable finishCallback) { synchronized (mLock) { final RunningAnimation runningAnim = new RunningAnimation(a, animationLeash, finishCallback); mPendingAnimations.put(animationLeash, runningAnim); - mChoreographer.postFrameCallback(this::stepAnimation); + if (!mAnimationStartDeferred) { + mChoreographer.postFrameCallback(this::startAnimations); + } // Some animations (e.g. move animations) require the initial transform to be applied // immediately. @@ -185,7 +216,7 @@ class SurfaceAnimationRunner { a.mAnimSpec.apply(t, a.mLeash, currentPlayTime); } - private void stepAnimation(long frameTimeNanos) { + private void startAnimations(long frameTimeNanos) { synchronized (mLock) { startPendingAnimationsLocked(); } diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java index eb8eae1a95d7..bdda944f236b 100644 --- a/services/core/java/com/android/server/wm/TaskStack.java +++ b/services/core/java/com/android/server/wm/TaskStack.java @@ -44,7 +44,6 @@ import static com.android.server.wm.proto.StackProto.WINDOW_CONTAINER; import android.annotation.CallSuper; import android.content.res.Configuration; -import android.graphics.Point; import android.graphics.Rect; import android.graphics.Region; import android.os.RemoteException; @@ -146,7 +145,6 @@ public class TaskStack extends WindowContainer<Task> implements * For {@link #prepareSurfaces}. */ final Rect mTmpDimBoundsRect = new Rect(); - private final Point mLastSurfaceSize = new Point(); TaskStack(WindowManagerService service, int stackId, StackWindowController controller) { super(service); @@ -716,10 +714,11 @@ public class TaskStack extends WindowContainer<Task> implements @Override public void onConfigurationChanged(Configuration newParentConfig) { final int prevWindowingMode = getWindowingMode(); + super.onConfigurationChanged(newParentConfig); + // Only need to update surface size here since the super method will handle updating // surface position. updateSurfaceSize(getPendingTransaction()); - super.onConfigurationChanged(newParentConfig); final int windowingMode = getWindowingMode(); if (mDisplayContent == null || prevWindowingMode == windowingMode) { @@ -745,13 +744,7 @@ public class TaskStack extends WindowContainer<Task> implements } final Rect stackBounds = getBounds(); - final int width = stackBounds.width(); - final int height = stackBounds.height(); - if (width == mLastSurfaceSize.x && height == mLastSurfaceSize.y) { - return; - } - transaction.setSize(mSurfaceControl, width, height); - mLastSurfaceSize.set(width, height); + transaction.setSize(mSurfaceControl, stackBounds.width(), stackBounds.height()); } @Override diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index af314101cd2b..5d445eff0c92 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -95,7 +95,6 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< protected final WindowManagerService mService; private final Point mTmpPos = new Point(); - protected final Point mLastSurfacePosition = new Point(); /** Total number of elements in this subtree, including our own hierarchy element. */ private int mTreeWeight = 1; @@ -1178,12 +1177,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< } getRelativePosition(mTmpPos); - if (mTmpPos.equals(mLastSurfacePosition)) { - return; - } - transaction.setPosition(mSurfaceControl, mTmpPos.x, mTmpPos.y); - mLastSurfacePosition.set(mTmpPos.x, mTmpPos.y); for (int i = mChildren.size() - 1; i >= 0; i--) { mChildren.get(i).updateSurfacePosition(transaction); diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 08fb037e6195..b3809dd8f6c9 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -1543,10 +1543,13 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP @Override public boolean canAffectSystemUiFlags() { final boolean translucent = mAttrs.alpha == 0.0f; + if (translucent) { + return false; + } if (mAppToken == null) { final boolean shown = mWinAnimator.getShown(); final boolean exiting = mAnimatingExit || mDestroying; - return shown && !exiting && !translucent; + return shown && !exiting; } else { return !mAppToken.isHidden(); } @@ -4484,7 +4487,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP // Leash is now responsible for position, so set our position to 0. t.setPosition(mSurfaceControl, 0, 0); - mLastSurfacePosition.set(0, 0); } @Override @@ -4500,9 +4502,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } transformFrameToSurfacePosition(mFrame.left, mFrame.top, mSurfacePosition); - if (!mSurfaceAnimator.hasLeash() && !mLastSurfacePosition.equals(mSurfacePosition)) { + if (!mSurfaceAnimator.hasLeash()) { t.setPosition(mSurfaceControl, mSurfacePosition.x, mSurfacePosition.y); - mLastSurfacePosition.set(mSurfacePosition.x, mSurfacePosition.y); } } diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java index bdab9c765d36..08f49f689323 100644 --- a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java +++ b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java @@ -349,21 +349,28 @@ class WindowSurfacePlacer { animLp = null; } - processApplicationsAnimatingInPlace(transit); - - mTmpLayerAndToken.token = null; - handleClosingApps(transit, animLp, voiceInteraction, mTmpLayerAndToken); - final AppWindowToken topClosingApp = mTmpLayerAndToken.token; - final AppWindowToken topOpeningApp = handleOpeningApps(transit, animLp, voiceInteraction); - - mService.mAppTransition.setLastAppTransition(transit, topOpeningApp, topClosingApp); - - final int flags = mService.mAppTransition.getTransitFlags(); - int layoutRedo = mService.mAppTransition.goodToGo(transit, topOpeningApp, - topClosingApp, mService.mOpeningApps, mService.mClosingApps); - handleNonAppWindowsInTransition(transit, flags); - mService.mAppTransition.postAnimationCallback(); - mService.mAppTransition.clear(); + final int layoutRedo; + mService.mSurfaceAnimationRunner.deferStartingAnimations(); + try { + processApplicationsAnimatingInPlace(transit); + + mTmpLayerAndToken.token = null; + handleClosingApps(transit, animLp, voiceInteraction, mTmpLayerAndToken); + final AppWindowToken topClosingApp = mTmpLayerAndToken.token; + final AppWindowToken topOpeningApp = handleOpeningApps(transit, animLp, + voiceInteraction); + + mService.mAppTransition.setLastAppTransition(transit, topOpeningApp, topClosingApp); + + final int flags = mService.mAppTransition.getTransitFlags(); + layoutRedo = mService.mAppTransition.goodToGo(transit, topOpeningApp, + topClosingApp, mService.mOpeningApps, mService.mClosingApps); + handleNonAppWindowsInTransition(transit, flags); + mService.mAppTransition.postAnimationCallback(); + mService.mAppTransition.clear(); + } finally { + mService.mSurfaceAnimationRunner.continueStartingAnimations(); + } mService.mTaskSnapshotController.onTransitionStarting(); diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityStackTests.java b/services/tests/servicestests/src/com/android/server/am/ActivityStackTests.java index 6b09363cff0b..bcbf40e191e6 100644 --- a/services/tests/servicestests/src/com/android/server/am/ActivityStackTests.java +++ b/services/tests/servicestests/src/com/android/server/am/ActivityStackTests.java @@ -47,7 +47,7 @@ import org.junit.Test; * Tests for the {@link ActivityStack} class. * * Build/Install/Run: - * bit FrameworksServicesTests:com.android.server.am.ActivityStackTests + * atest ActivityStackTests */ @SmallTest @Presubmit @@ -104,6 +104,29 @@ public class ActivityStackTests extends ActivityTestsBase { } @Test + public void testPrimarySplitScreenToFullscreenWhenMovedToBack() throws Exception { + // Create primary splitscreen stack. This will create secondary stacks and places the + // existing fullscreen stack on the bottom. + final ActivityStack primarySplitScreen = mService.mStackSupervisor.getDefaultDisplay() + .createStack(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD, + true /* onTop */); + + // Assert windowing mode. + assertEquals(primarySplitScreen.getWindowingMode(), WINDOWING_MODE_SPLIT_SCREEN_PRIMARY); + + // Move primary to back. + primarySplitScreen.moveToBack("testPrimarySplitScreenToFullscreenWhenMovedToBack", + null /* task */); + + // Assert that stack is at the bottom. + assertEquals(mService.mStackSupervisor.getDefaultDisplay().getIndexOf(primarySplitScreen), + 0); + + // Ensure no longer in splitscreen. + assertEquals(primarySplitScreen.getWindowingMode(), WINDOWING_MODE_FULLSCREEN); + } + + @Test public void testStopActivityWhenActivityDestroyed() throws Exception { final ActivityRecord r = new ActivityBuilder(mService).setTask(mTask).build(); r.info.flags |= ActivityInfo.FLAG_NO_HISTORY; diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java index 32b0b266bafc..49601c32cdc2 100644 --- a/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java @@ -34,7 +34,6 @@ import java.io.File; import java.lang.reflect.Array; import java.lang.reflect.Field; import java.nio.charset.StandardCharsets; -import java.security.cert.Certificate; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; @@ -262,14 +261,13 @@ public class PackageParserTest { assertBundleApproximateEquals(a.mAppMetaData, b.mAppMetaData); assertEquals(a.mVersionName, b.mVersionName); assertEquals(a.mSharedUserId, b.mSharedUserId); - assertTrue(Arrays.equals(a.mSignatures, b.mSignatures)); - assertTrue(Arrays.equals(a.mCertificates, b.mCertificates)); + assertTrue(Arrays.equals(a.mSigningDetails.signatures, b.mSigningDetails.signatures)); assertTrue(Arrays.equals(a.mLastPackageUsageTimeInMills, b.mLastPackageUsageTimeInMills)); assertEquals(a.mExtras, b.mExtras); assertEquals(a.mRestrictedAccountType, b.mRestrictedAccountType); assertEquals(a.mRequiredAccountType, b.mRequiredAccountType); assertEquals(a.mOverlayTarget, b.mOverlayTarget); - assertEquals(a.mSigningKeys, b.mSigningKeys); + assertEquals(a.mSigningDetails.publicKeys, b.mSigningDetails.publicKeys); assertEquals(a.mUpgradeKeySets, b.mUpgradeKeySets); assertEquals(a.mKeySetMapping, b.mKeySetMapping); assertEquals(a.cpuAbiOverride, b.cpuAbiOverride); @@ -495,14 +493,16 @@ public class PackageParserTest { pkg.mAppMetaData = new Bundle(); pkg.mVersionName = "foo17"; pkg.mSharedUserId = "foo18"; - pkg.mSignatures = new Signature[] { new Signature(new byte[16]) }; - pkg.mCertificates = new Certificate[][] { new Certificate[] { null }}; + pkg.mSigningDetails = + new PackageParser.SigningDetails( + new Signature[] { new Signature(new byte[16]) }, + 2, + new ArraySet<>()); pkg.mExtras = new Bundle(); pkg.mRestrictedAccountType = "foo19"; pkg.mRequiredAccountType = "foo20"; pkg.mOverlayTarget = "foo21"; pkg.mOverlayPriority = 100; - pkg.mSigningKeys = new ArraySet<>(); pkg.mUpgradeKeySets = new ArraySet<>(); pkg.mKeySetMapping = new ArrayMap<>(); pkg.cpuAbiOverride = "foo22"; diff --git a/services/tests/servicestests/src/com/android/server/wm/SurfaceAnimationRunnerTest.java b/services/tests/servicestests/src/com/android/server/wm/SurfaceAnimationRunnerTest.java index 17fe6427f756..4831fcd67314 100644 --- a/services/tests/servicestests/src/com/android/server/wm/SurfaceAnimationRunnerTest.java +++ b/services/tests/servicestests/src/com/android/server/wm/SurfaceAnimationRunnerTest.java @@ -162,6 +162,20 @@ public class SurfaceAnimationRunnerTest extends WindowTestsBase { verify(mMockAnimationSpec, atLeastOnce()).apply(any(), any(), eq(0L)); } + @Test + public void testDeferStartingAnimations() throws Exception { + mSurfaceAnimationRunner.deferStartingAnimations(); + mSurfaceAnimationRunner.startAnimation(createTranslateAnimation(), mMockSurface, + mMockTransaction, this::finishedCallback); + waitUntilNextFrame(); + assertTrue(mSurfaceAnimationRunner.mRunningAnimations.isEmpty()); + mSurfaceAnimationRunner.continueStartingAnimations(); + waitUntilNextFrame(); + assertFalse(mSurfaceAnimationRunner.mRunningAnimations.isEmpty()); + mFinishCallbackLatch.await(1, SECONDS); + assertFinishCallbackCalled(); + } + private void waitUntilNextFrame() throws Exception { final CountDownLatch latch = new CountDownLatch(1); mSurfaceAnimationRunner.mChoreographer.postCallback(Choreographer.CALLBACK_COMMIT, diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowStateTests.java b/services/tests/servicestests/src/com/android/server/wm/WindowStateTests.java index 67db5f4d12dd..7be203a99391 100644 --- a/services/tests/servicestests/src/com/android/server/wm/WindowStateTests.java +++ b/services/tests/servicestests/src/com/android/server/wm/WindowStateTests.java @@ -16,7 +16,6 @@ package com.android.server.wm; -import android.util.MergedConfiguration; import android.view.WindowManager; import org.junit.Test; import org.junit.runner.RunWith; @@ -45,8 +44,7 @@ import static org.junit.Assert.assertTrue; /** * Tests for the {@link WindowState} class. * - * Build/Install/Run: - * bit FrameworksServicesTests:com.android.server.wm.WindowStateTests + * atest FrameworksServicesTests:com.android.server.wm.WindowStateTests */ @SmallTest @Presubmit @@ -213,6 +211,18 @@ public class WindowStateTests extends WindowTestsBase { testPrepareWindowToDisplayDuringRelayout(true /*wasVisible*/); } + @Test + public void testCanAffectSystemUiFlags() throws Exception { + final WindowState app = createWindow(null, TYPE_APPLICATION, "app"); + app.mToken.setHidden(false); + assertTrue(app.canAffectSystemUiFlags()); + app.mToken.setHidden(true); + assertFalse(app.canAffectSystemUiFlags()); + app.mToken.setHidden(false); + app.mAttrs.alpha = 0.0f; + assertFalse(app.canAffectSystemUiFlags()); + } + private void testPrepareWindowToDisplayDuringRelayout(boolean wasVisible) { final WindowState root = createWindow(null, TYPE_APPLICATION, "root"); root.mAttrs.flags |= WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON; diff --git a/services/tests/servicestests/src/com/android/server/wm/ZOrderingTests.java b/services/tests/servicestests/src/com/android/server/wm/ZOrderingTests.java index 6468763440a5..bcc9a1cbab7d 100644 --- a/services/tests/servicestests/src/com/android/server/wm/ZOrderingTests.java +++ b/services/tests/servicestests/src/com/android/server/wm/ZOrderingTests.java @@ -21,10 +21,12 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; +import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA_OVERLAY; import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; +import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER; import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL; import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL; import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL; @@ -74,11 +76,11 @@ public class ZOrderingTests extends WindowTestsBase { return super.setRelativeLayer(sc, relativeTo, layer); } - int getLayer(SurfaceControl sc) { + private int getLayer(SurfaceControl sc) { return mLayersForControl.getOrDefault(sc, 0); } - SurfaceControl getRelativeLayer(SurfaceControl sc) { + private SurfaceControl getRelativeLayer(SurfaceControl sc) { return mRelativeLayersForControl.get(sc); } }; @@ -146,8 +148,9 @@ public class ZOrderingTests extends WindowTestsBase { return p; } - void assertZOrderGreaterThan(LayerRecordingTransaction t, - SurfaceControl left, SurfaceControl right) throws Exception { + + void assertZOrderGreaterThan(LayerRecordingTransaction t, SurfaceControl left, + SurfaceControl right) throws Exception { final LinkedList<SurfaceControl> leftParentChain = getAncestors(t, left); final LinkedList<SurfaceControl> rightParentChain = getAncestors(t, right); @@ -171,9 +174,12 @@ public class ZOrderingTests extends WindowTestsBase { } } - void assertWindowLayerGreaterThan(LayerRecordingTransaction t, - WindowState left, WindowState right) throws Exception { - assertZOrderGreaterThan(t, left.getSurfaceControl(), right.getSurfaceControl()); + void assertWindowHigher(WindowState left, WindowState right) throws Exception { + assertZOrderGreaterThan(mTransaction, left.getSurfaceControl(), right.getSurfaceControl()); + } + + WindowState createWindow(String name) { + return createWindow(null, TYPE_BASE_APPLICATION, mDisplayContent, name); } @Test @@ -184,38 +190,37 @@ public class ZOrderingTests extends WindowTestsBase { // The Ime has an higher base layer than app windows and lower base layer than system // windows, so it should be above app windows and below system windows if there isn't an IME // target. - assertWindowLayerGreaterThan(mTransaction, mImeWindow, mChildAppWindowAbove); - assertWindowLayerGreaterThan(mTransaction, mImeWindow, mAppWindow); - assertWindowLayerGreaterThan(mTransaction, mNavBarWindow, mImeWindow); - assertWindowLayerGreaterThan(mTransaction, mStatusBarWindow, mImeWindow); + assertWindowHigher(mImeWindow, mChildAppWindowAbove); + assertWindowHigher(mImeWindow, mAppWindow); + assertWindowHigher(mNavBarWindow, mImeWindow); + assertWindowHigher(mStatusBarWindow, mImeWindow); // And, IME dialogs should always have an higher layer than the IME. - assertWindowLayerGreaterThan(mTransaction, mImeDialogWindow, mImeWindow); + assertWindowHigher(mImeDialogWindow, mImeWindow); } @Test public void testAssignWindowLayers_ForImeWithAppTarget() throws Exception { - final WindowState imeAppTarget = - createWindow(null, TYPE_BASE_APPLICATION, mDisplayContent, "imeAppTarget"); + final WindowState imeAppTarget = createWindow("imeAppTarget"); sWm.mInputMethodTarget = imeAppTarget; + mDisplayContent.assignChildLayers(mTransaction); // Ime should be above all app windows and below system windows if it is targeting an app // window. - assertWindowLayerGreaterThan(mTransaction, mImeWindow, imeAppTarget); - assertWindowLayerGreaterThan(mTransaction, mImeWindow, mChildAppWindowAbove); - assertWindowLayerGreaterThan(mTransaction, mImeWindow, mAppWindow); - assertWindowLayerGreaterThan(mTransaction, mNavBarWindow, mImeWindow); - assertWindowLayerGreaterThan(mTransaction, mStatusBarWindow, mImeWindow); + assertWindowHigher(mImeWindow, imeAppTarget); + assertWindowHigher(mImeWindow, mChildAppWindowAbove); + assertWindowHigher(mImeWindow, mAppWindow); + assertWindowHigher(mNavBarWindow, mImeWindow); + assertWindowHigher(mStatusBarWindow, mImeWindow); // And, IME dialogs should always have an higher layer than the IME. - assertWindowLayerGreaterThan(mTransaction, mImeDialogWindow, mImeWindow); + assertWindowHigher(mImeDialogWindow, mImeWindow); } @Test public void testAssignWindowLayers_ForImeWithAppTargetWithChildWindows() throws Exception { - final WindowState imeAppTarget = - createWindow(null, TYPE_BASE_APPLICATION, mDisplayContent, "imeAppTarget"); + final WindowState imeAppTarget = createWindow("imeAppTarget"); final WindowState imeAppTargetChildAboveWindow = createWindow(imeAppTarget, TYPE_APPLICATION_ATTACHED_DIALOG, imeAppTarget.mToken, "imeAppTargetChildAboveWindow"); @@ -228,41 +233,38 @@ public class ZOrderingTests extends WindowTestsBase { // Ime should be above all app windows except for child windows that are z-ordered above it // and below system windows if it is targeting an app window. - assertWindowLayerGreaterThan(mTransaction, mImeWindow, imeAppTarget); - assertWindowLayerGreaterThan(mTransaction, imeAppTargetChildAboveWindow, mImeWindow); - assertWindowLayerGreaterThan(mTransaction, mImeWindow, mChildAppWindowAbove); - assertWindowLayerGreaterThan(mTransaction, mImeWindow, mAppWindow); - assertWindowLayerGreaterThan(mTransaction, mNavBarWindow, mImeWindow); - assertWindowLayerGreaterThan(mTransaction, mStatusBarWindow, mImeWindow); + assertWindowHigher(mImeWindow, imeAppTarget); + assertWindowHigher(imeAppTargetChildAboveWindow, mImeWindow); + assertWindowHigher(mImeWindow, mChildAppWindowAbove); + assertWindowHigher(mImeWindow, mAppWindow); + assertWindowHigher(mNavBarWindow, mImeWindow); + assertWindowHigher(mStatusBarWindow, mImeWindow); // And, IME dialogs should always have an higher layer than the IME. - assertWindowLayerGreaterThan(mTransaction, mImeDialogWindow, mImeWindow); + assertWindowHigher(mImeDialogWindow, mImeWindow); } @Test public void testAssignWindowLayers_ForImeWithAppTargetAndAppAbove() throws Exception { - final WindowState appBelowImeTarget = - createWindow(null, TYPE_BASE_APPLICATION, mDisplayContent, "appBelowImeTarget"); - final WindowState imeAppTarget = - createWindow(null, TYPE_BASE_APPLICATION, mDisplayContent, "imeAppTarget"); - final WindowState appAboveImeTarget = - createWindow(null, TYPE_BASE_APPLICATION, mDisplayContent, "appAboveImeTarget"); + final WindowState appBelowImeTarget = createWindow("appBelowImeTarget"); + final WindowState imeAppTarget = createWindow("imeAppTarget"); + final WindowState appAboveImeTarget = createWindow("appAboveImeTarget"); sWm.mInputMethodTarget = imeAppTarget; mDisplayContent.assignChildLayers(mTransaction); // Ime should be above all app windows except for non-fullscreen app window above it and // below system windows if it is targeting an app window. - assertWindowLayerGreaterThan(mTransaction, mImeWindow, imeAppTarget); - assertWindowLayerGreaterThan(mTransaction, mImeWindow, appBelowImeTarget); - assertWindowLayerGreaterThan(mTransaction, appAboveImeTarget, mImeWindow); - assertWindowLayerGreaterThan(mTransaction, mImeWindow, mChildAppWindowAbove); - assertWindowLayerGreaterThan(mTransaction, mImeWindow, mAppWindow); - assertWindowLayerGreaterThan(mTransaction, mNavBarWindow, mImeWindow); - assertWindowLayerGreaterThan(mTransaction, mStatusBarWindow, mImeWindow); + assertWindowHigher(mImeWindow, imeAppTarget); + assertWindowHigher(mImeWindow, appBelowImeTarget); + assertWindowHigher(appAboveImeTarget, mImeWindow); + assertWindowHigher(mImeWindow, mChildAppWindowAbove); + assertWindowHigher(mImeWindow, mAppWindow); + assertWindowHigher(mNavBarWindow, mImeWindow); + assertWindowHigher(mStatusBarWindow, mImeWindow); // And, IME dialogs should always have an higher layer than the IME. - assertWindowLayerGreaterThan(mTransaction, mImeDialogWindow, mImeWindow); + assertWindowHigher(mImeDialogWindow, mImeWindow); } @Test @@ -276,20 +278,20 @@ public class ZOrderingTests extends WindowTestsBase { // The IME target base layer is higher than all window except for the nav bar window, so the // IME should be above all windows except for the nav bar. - assertWindowLayerGreaterThan(mTransaction, mImeWindow, imeSystemOverlayTarget); - assertWindowLayerGreaterThan(mTransaction, mImeWindow, mChildAppWindowAbove); - assertWindowLayerGreaterThan(mTransaction, mImeWindow, mAppWindow); - assertWindowLayerGreaterThan(mTransaction, mImeWindow, mDockedDividerWindow); + assertWindowHigher(mImeWindow, imeSystemOverlayTarget); + assertWindowHigher(mImeWindow, mChildAppWindowAbove); + assertWindowHigher(mImeWindow, mAppWindow); + assertWindowHigher(mImeWindow, mDockedDividerWindow); // The IME has a higher base layer than the status bar so we may expect it to go // above the status bar once they are both in the Non-App layer, as past versions of this // test enforced. However this seems like the wrong behavior unless the status bar is the // IME target. - assertWindowLayerGreaterThan(mTransaction, mNavBarWindow, mImeWindow); - assertWindowLayerGreaterThan(mTransaction, mStatusBarWindow, mImeWindow); + assertWindowHigher(mNavBarWindow, mImeWindow); + assertWindowHigher(mStatusBarWindow, mImeWindow); // And, IME dialogs should always have an higher layer than the IME. - assertWindowLayerGreaterThan(mTransaction, mImeDialogWindow, mImeWindow); + assertWindowHigher(mImeDialogWindow, mImeWindow); } @Test @@ -297,17 +299,18 @@ public class ZOrderingTests extends WindowTestsBase { sWm.mInputMethodTarget = mStatusBarWindow; mDisplayContent.assignChildLayers(mTransaction); - assertWindowLayerGreaterThan(mTransaction, mImeWindow, mChildAppWindowAbove); - assertWindowLayerGreaterThan(mTransaction, mImeWindow, mAppWindow); - assertWindowLayerGreaterThan(mTransaction, mImeWindow, mDockedDividerWindow); - assertWindowLayerGreaterThan(mTransaction, mImeWindow, mStatusBarWindow); + assertWindowHigher(mImeWindow, mChildAppWindowAbove); + assertWindowHigher(mImeWindow, mAppWindow); + assertWindowHigher(mImeWindow, mDockedDividerWindow); + assertWindowHigher(mImeWindow, mStatusBarWindow); // And, IME dialogs should always have an higher layer than the IME. - assertWindowLayerGreaterThan(mTransaction, mImeDialogWindow, mImeWindow); + assertWindowHigher(mImeDialogWindow, mImeWindow); } @Test public void testStackLayers() throws Exception { + final WindowState anyWindow1 = createWindow("anyWindow"); final WindowState pinnedStackWindow = createWindowOnStack(null, WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, TYPE_BASE_APPLICATION, mDisplayContent, "pinnedStackWindow"); @@ -317,12 +320,17 @@ public class ZOrderingTests extends WindowTestsBase { final WindowState assistantStackWindow = createWindowOnStack(null, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_ASSISTANT, TYPE_BASE_APPLICATION, mDisplayContent, "assistantStackWindow"); + final WindowState anyWindow2 = createWindow("anyWindow2"); mDisplayContent.assignChildLayers(mTransaction); - assertWindowLayerGreaterThan(mTransaction, dockedStackWindow, mAppWindow); - assertWindowLayerGreaterThan(mTransaction, assistantStackWindow, dockedStackWindow); - assertWindowLayerGreaterThan(mTransaction, pinnedStackWindow, assistantStackWindow); + // We compare the split-screen windowing mode to two different normal windowing + // mode windows added before and after it to ensure the correct Z ordering irrespective + // of ordering in the child list. + assertWindowHigher(dockedStackWindow, anyWindow1); + assertWindowHigher(dockedStackWindow, anyWindow2); + assertWindowHigher(assistantStackWindow, dockedStackWindow); + assertWindowHigher(pinnedStackWindow, assistantStackWindow); } @Test @@ -337,9 +345,9 @@ public class ZOrderingTests extends WindowTestsBase { // Ime should be above all app windows and below system windows if it is targeting an app // window. - assertWindowLayerGreaterThan(mTransaction, navBarPanel, mNavBarWindow); - assertWindowLayerGreaterThan(mTransaction, statusBarPanel, mStatusBarWindow); - assertWindowLayerGreaterThan(mTransaction, statusBarSubPanel, statusBarPanel); + assertWindowHigher(navBarPanel, mNavBarWindow); + assertWindowHigher(statusBarPanel, mStatusBarWindow); + assertWindowHigher(statusBarSubPanel, statusBarPanel); } @Test @@ -347,8 +355,7 @@ public class ZOrderingTests extends WindowTestsBase { // TODO(b/70040778): We should aim to eliminate the last user of TYPE_APPLICATION_MEDIA // then we can drop all negative layering on the windowing side. - final WindowState anyWindow = - createWindow(null, TYPE_BASE_APPLICATION, mDisplayContent, "anyWindow"); + final WindowState anyWindow = createWindow("anyWindow"); final WindowState child = createWindow(anyWindow, TYPE_APPLICATION_MEDIA, mDisplayContent, "TypeApplicationMediaChild"); final WindowState mediaOverlayChild = createWindow(anyWindow, TYPE_APPLICATION_MEDIA_OVERLAY, @@ -356,7 +363,32 @@ public class ZOrderingTests extends WindowTestsBase { mDisplayContent.assignChildLayers(mTransaction); - assertWindowLayerGreaterThan(mTransaction, anyWindow, mediaOverlayChild); - assertWindowLayerGreaterThan(mTransaction, mediaOverlayChild, child); + assertWindowHigher(anyWindow, mediaOverlayChild); + assertWindowHigher(mediaOverlayChild, child); + } + + @Test + public void testDockedDividerPosition() throws Exception { + final WindowState pinnedStackWindow = createWindowOnStack(null, WINDOWING_MODE_PINNED, + ACTIVITY_TYPE_STANDARD, TYPE_BASE_APPLICATION, mDisplayContent, + "pinnedStackWindow"); + final WindowState splitScreenWindow = createWindowOnStack(null, + WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD, TYPE_BASE_APPLICATION, + mDisplayContent, "splitScreenWindow"); + final WindowState splitScreenSecondaryWindow = createWindowOnStack(null, + WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD, + TYPE_BASE_APPLICATION, mDisplayContent, "splitScreenSecondaryWindow"); + final WindowState assistantStackWindow = createWindowOnStack(null, WINDOWING_MODE_FULLSCREEN, + ACTIVITY_TYPE_ASSISTANT, TYPE_BASE_APPLICATION, + mDisplayContent, "assistantStackWindow"); + final WindowState dockedDividerWindow = createWindow(null, TYPE_DOCK_DIVIDER, + mDisplayContent, "dockedDivider"); + + mDisplayContent.assignChildLayers(mTransaction); + + assertWindowHigher(dockedDividerWindow, splitScreenWindow); + assertWindowHigher(dockedDividerWindow, splitScreenSecondaryWindow); + assertWindowHigher(assistantStackWindow, dockedDividerWindow); + assertWindowHigher(pinnedStackWindow, dockedDividerWindow); } } diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 6a47d05018d6..6b40e7fa4981 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -1724,6 +1724,15 @@ public class CarrierConfigManager { */ public static final String KEY_CARRIER_CONFIG_APPLIED_BOOL = "carrier_config_applied_bool"; + /** + * Determines whether we should show a warning asking the user to check with their carrier + * on pricing when the user enabled data roaming. + * default to false. + * @hide + */ + public static final String KEY_CHECK_PRICING_WITH_CARRIER_FOR_DATA_ROAMING_BOOL = + "check_pricing_with_carrier_data_roaming_bool"; + /** The default value for every variable. */ private final static PersistableBundle sDefaults; @@ -2010,6 +2019,7 @@ public class CarrierConfigManager { sDefaults.putBoolean(KEY_SPN_DISPLAY_RULE_USE_ROAMING_FROM_SERVICE_STATE_BOOL, false); sDefaults.putBoolean(KEY_ALWAYS_SHOW_DATA_RAT_ICON_BOOL, false); sDefaults.putBoolean(KEY_CARRIER_CONFIG_APPLIED_BOOL, false); + sDefaults.putBoolean(KEY_CHECK_PRICING_WITH_CARRIER_FOR_DATA_ROAMING_BOOL, false); } /** diff --git a/test-base/Android.mk b/test-base/Android.mk index 5e5d0401dd3c..861385467a0f 100644 --- a/test-base/Android.mk +++ b/test-base/Android.mk @@ -112,17 +112,6 @@ update-android-test-base-api: $(ANDROID_TEST_BASE_OUTPUT_API_FILE) | $(ACP) endif # not TARGET_BUILD_APPS not TARGET_BUILD_PDK=true -# Build the legacy.test.stubs library -# =================================== -include $(CLEAR_VARS) - -LOCAL_MODULE := legacy.test.stubs -LOCAL_SDK_VERSION := current - -LOCAL_STATIC_JAVA_LIBRARIES := android.test.base.stubs - -include $(BUILD_STATIC_JAVA_LIBRARY) - ifeq ($(HOST_OS),linux) # Build the legacy-performance-test-hostdex library # ================================================= diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/jni/Android.mk b/tests/Camera2Tests/SmartCamera/SimpleCamera/jni/Android.mk index 088f322ed324..6e0d58a2a881 100644 --- a/tests/Camera2Tests/SmartCamera/SimpleCamera/jni/Android.mk +++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/jni/Android.mk @@ -40,6 +40,6 @@ LOCAL_SRC_FILES := contrast.cpp \ LOCAL_CFLAGS += -Wall -Wextra -Werror -Wno-unused-parameter -LOCAL_NDK_STL_VARIANT := stlport_static +LOCAL_NDK_STL_VARIANT := c++_static include $(BUILD_SHARED_LIBRARY) diff --git a/tools/aapt2/filter/AbiFilter.h b/tools/aapt2/filter/AbiFilter.h index d875cb2b127b..2832711efb2c 100644 --- a/tools/aapt2/filter/AbiFilter.h +++ b/tools/aapt2/filter/AbiFilter.h @@ -33,6 +33,8 @@ namespace aapt { */ class AbiFilter : public IPathFilter { public: + virtual ~AbiFilter() = default; + /** Factory method to create a filter from a list of configuration::Abi. */ static std::unique_ptr<AbiFilter> FromAbiList(const std::vector<configuration::Abi>& abi_list); diff --git a/tools/aapt2/filter/Filter.h b/tools/aapt2/filter/Filter.h index d737dc92e87b..f932f9ccc82e 100644 --- a/tools/aapt2/filter/Filter.h +++ b/tools/aapt2/filter/Filter.h @@ -27,7 +27,7 @@ namespace aapt { /** A filter to be applied to a path segment. */ class IPathFilter { public: - ~IPathFilter() = default; + virtual ~IPathFilter() = default; /** Returns true if the path should be kept. */ virtual bool Keep(const std::string& path) = 0; diff --git a/tools/incident_section_gen/main.cpp b/tools/incident_section_gen/main.cpp index 102bbf9d07d7..e7b269aaa9a5 100644 --- a/tools/incident_section_gen/main.cpp +++ b/tools/incident_section_gen/main.cpp @@ -408,6 +408,9 @@ static bool generateSectionListCpp(Descriptor const* descriptor) { splitAndPrint(s.args()); printf(" NULL),\n"); break; + case SECTION_LOG: + printf(" new LogSection(%d, %s),\n", field->number(), s.args().c_str()); + break; } } printf(" NULL };\n"); diff --git a/wifi/java/android/net/wifi/ScanResult.java b/wifi/java/android/net/wifi/ScanResult.java index b6ad92614bfe..eaad137b9b6e 100644 --- a/wifi/java/android/net/wifi/ScanResult.java +++ b/wifi/java/android/net/wifi/ScanResult.java @@ -21,7 +21,9 @@ import android.os.Parcel; import android.os.Parcelable; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; +import java.util.Objects; /** * Describes information about a detected access point. In addition @@ -227,6 +229,50 @@ public class ScanResult implements Parcelable { public long seen; /** + * On devices with multiple hardware radio chains, this class provides metadata about + * each radio chain that was used to receive this scan result (probe response or beacon). + * {@hide} + */ + public static class RadioChainInfo { + /** Vendor defined id for a radio chain. */ + public int id; + /** Detected signal level in dBm (also known as the RSSI) on this radio chain. */ + public int level; + + @Override + public String toString() { + return "RadioChainInfo: id=" + id + ", level=" + level; + } + + @Override + public boolean equals(Object otherObj) { + if (this == otherObj) { + return true; + } + if (!(otherObj instanceof RadioChainInfo)) { + return false; + } + RadioChainInfo other = (RadioChainInfo) otherObj; + return id == other.id && level == other.level; + } + + @Override + public int hashCode() { + return Objects.hash(id, level); + } + }; + + /** + * Information about the list of the radio chains used to receive this scan result + * (probe response or beacon). + * + * For Example: On devices with 2 hardware radio chains, this list could hold 1 or 2 + * entries based on whether this scan result was received using one or both the chains. + * {@hide} + */ + public RadioChainInfo[] radioChainInfos; + + /** * @hide * Update RSSI of the scan result * @param previousRssi @@ -481,6 +527,7 @@ public class ScanResult implements Parcelable { this.isCarrierAp = false; this.carrierApEapType = UNSPECIFIED; this.carrierName = null; + this.radioChainInfos = null; } /** {@hide} */ @@ -502,6 +549,7 @@ public class ScanResult implements Parcelable { this.isCarrierAp = false; this.carrierApEapType = UNSPECIFIED; this.carrierName = null; + this.radioChainInfos = null; } /** {@hide} */ @@ -530,6 +578,7 @@ public class ScanResult implements Parcelable { this.isCarrierAp = false; this.carrierApEapType = UNSPECIFIED; this.carrierName = null; + this.radioChainInfos = null; } /** {@hide} */ @@ -572,6 +621,7 @@ public class ScanResult implements Parcelable { isCarrierAp = source.isCarrierAp; carrierApEapType = source.carrierApEapType; carrierName = source.carrierName; + radioChainInfos = source.radioChainInfos; } } @@ -615,6 +665,7 @@ public class ScanResult implements Parcelable { sb.append(", Carrier AP: ").append(isCarrierAp ? "yes" : "no"); sb.append(", Carrier AP EAP Type: ").append(carrierApEapType); sb.append(", Carrier name: ").append(carrierName); + sb.append(", Radio Chain Infos: ").append(Arrays.toString(radioChainInfos)); return sb.toString(); } @@ -687,6 +738,16 @@ public class ScanResult implements Parcelable { dest.writeInt(isCarrierAp ? 1 : 0); dest.writeInt(carrierApEapType); dest.writeString(carrierName); + + if (radioChainInfos != null) { + dest.writeInt(radioChainInfos.length); + for (int i = 0; i < radioChainInfos.length; i++) { + dest.writeInt(radioChainInfos[i].id); + dest.writeInt(radioChainInfos[i].level); + } + } else { + dest.writeInt(0); + } } /** Implement the Parcelable interface {@hide} */ @@ -759,6 +820,15 @@ public class ScanResult implements Parcelable { sr.isCarrierAp = in.readInt() != 0; sr.carrierApEapType = in.readInt(); sr.carrierName = in.readString(); + n = in.readInt(); + if (n != 0) { + sr.radioChainInfos = new RadioChainInfo[n]; + for (int i = 0; i < n; i++) { + sr.radioChainInfos[i] = new RadioChainInfo(); + sr.radioChainInfos[i].id = in.readInt(); + sr.radioChainInfos[i].level = in.readInt(); + } + } return sr; } diff --git a/wifi/tests/src/android/net/wifi/ScanResultTest.java b/wifi/tests/src/android/net/wifi/ScanResultTest.java new file mode 100644 index 000000000000..689ebbafe527 --- /dev/null +++ b/wifi/tests/src/android/net/wifi/ScanResultTest.java @@ -0,0 +1,193 @@ +/* + * 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.net.wifi; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.mockito.Mockito.validateMockitoUsage; + +import android.os.Parcel; +import android.test.suitebuilder.annotation.SmallTest; +import android.net.wifi.WifiScanner.ScanSettings; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mockito.MockitoAnnotations; + + +/** + * Unit tests for {@link android.net.wifi.WifiScanner}. + */ +@SmallTest +public class ScanResultTest { + public static final String TEST_SSID = "\"test_ssid\""; + public static final String TEST_BSSID = "04:ac:fe:45:34:10"; + public static final String TEST_CAPS = "CCMP"; + public static final int TEST_LEVEL = -56; + public static final int TEST_FREQUENCY = 2412; + public static final long TEST_TSF = 04660l; + + /** + * Setup before tests. + */ + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + } + + /** + * Clean up after tests. + */ + @After + public void cleanup() { + validateMockitoUsage(); + } + + /** + * Verify parcel read/write for ScanResult. + */ + @Test + public void verifyScanResultParcelWithoutRadioChainInfo() throws Exception { + ScanResult writeScanResult = createScanResult(); + ScanResult readScanResult = parcelReadWrite(writeScanResult); + assertScanResultEquals(writeScanResult, readScanResult); + } + + /** + * Verify parcel read/write for ScanResult. + */ + @Test + public void verifyScanResultParcelWithZeroRadioChainInfo() throws Exception { + ScanResult writeScanResult = createScanResult(); + writeScanResult.radioChainInfos = new ScanResult.RadioChainInfo[0]; + ScanResult readScanResult = parcelReadWrite(writeScanResult); + assertNull(readScanResult.radioChainInfos); + } + + /** + * Verify parcel read/write for ScanResult. + */ + @Test + public void verifyScanResultParcelWithRadioChainInfo() throws Exception { + ScanResult writeScanResult = createScanResult(); + writeScanResult.radioChainInfos = new ScanResult.RadioChainInfo[2]; + writeScanResult.radioChainInfos[0] = new ScanResult.RadioChainInfo(); + writeScanResult.radioChainInfos[0].id = 0; + writeScanResult.radioChainInfos[0].level = -45; + writeScanResult.radioChainInfos[1] = new ScanResult.RadioChainInfo(); + writeScanResult.radioChainInfos[1].id = 1; + writeScanResult.radioChainInfos[1].level = -54; + ScanResult readScanResult = parcelReadWrite(writeScanResult); + assertScanResultEquals(writeScanResult, readScanResult); + } + + /** + * Verify copy constructor for ScanResult. + */ + @Test + public void verifyScanResultCopyWithoutRadioChainInfo() throws Exception { + ScanResult scanResult = createScanResult(); + ScanResult copyScanResult = new ScanResult(scanResult); + assertScanResultEquals(scanResult, copyScanResult); + } + + /** + * Verify copy constructor for ScanResult. + */ + @Test + public void verifyScanResultCopyWithRadioChainInfo() throws Exception { + ScanResult scanResult = createScanResult(); + scanResult.radioChainInfos = new ScanResult.RadioChainInfo[2]; + scanResult.radioChainInfos[0] = new ScanResult.RadioChainInfo(); + scanResult.radioChainInfos[0].id = 0; + scanResult.radioChainInfos[0].level = -45; + scanResult.radioChainInfos[1] = new ScanResult.RadioChainInfo(); + scanResult.radioChainInfos[1].id = 1; + scanResult.radioChainInfos[1].level = -54; + ScanResult copyScanResult = new ScanResult(scanResult); + assertScanResultEquals(scanResult, copyScanResult); + } + + /** + * Verify toString for ScanResult. + */ + @Test + public void verifyScanResultToStringWithoutRadioChainInfo() throws Exception { + ScanResult scanResult = createScanResult(); + assertEquals("SSID: \"test_ssid\", BSSID: 04:ac:fe:45:34:10, capabilities: CCMP, " + + "level: -56, frequency: 2412, timestamp: 2480, distance: 0(cm), distanceSd: 0(cm), " + + "passpoint: no, ChannelBandwidth: 0, centerFreq0: 0, centerFreq1: 0, " + + "80211mcResponder: is not supported, Carrier AP: no, " + + "Carrier AP EAP Type: 0, Carrier name: null, " + + "Radio Chain Infos: null", scanResult.toString()); + } + + /** + * Verify toString for ScanResult. + */ + @Test + public void verifyScanResultToStringWithRadioChainInfo() throws Exception { + ScanResult scanResult = createScanResult(); + scanResult.radioChainInfos = new ScanResult.RadioChainInfo[2]; + scanResult.radioChainInfos[0] = new ScanResult.RadioChainInfo(); + scanResult.radioChainInfos[0].id = 0; + scanResult.radioChainInfos[0].level = -45; + scanResult.radioChainInfos[1] = new ScanResult.RadioChainInfo(); + scanResult.radioChainInfos[1].id = 1; + scanResult.radioChainInfos[1].level = -54; + assertEquals("SSID: \"test_ssid\", BSSID: 04:ac:fe:45:34:10, capabilities: CCMP, " + + "level: -56, frequency: 2412, timestamp: 2480, distance: 0(cm), distanceSd: 0(cm), " + + "passpoint: no, ChannelBandwidth: 0, centerFreq0: 0, centerFreq1: 0, " + + "80211mcResponder: is not supported, Carrier AP: no, " + + "Carrier AP EAP Type: 0, Carrier name: null, " + + "Radio Chain Infos: [RadioChainInfo: id=0, level=-45, " + + "RadioChainInfo: id=1, level=-54]", scanResult.toString()); + } + + /** + * Write the provided {@link ScanResult} to a parcel and deserialize it. + */ + private static ScanResult parcelReadWrite(ScanResult writeResult) throws Exception { + Parcel parcel = Parcel.obtain(); + writeResult.writeToParcel(parcel, 0); + parcel.setDataPosition(0); // Rewind data position back to the beginning for read. + return ScanResult.CREATOR.createFromParcel(parcel); + } + + private static ScanResult createScanResult() { + ScanResult result = new ScanResult(); + result.wifiSsid = WifiSsid.createFromAsciiEncoded(TEST_SSID); + result.BSSID = TEST_BSSID; + result.capabilities = TEST_CAPS; + result.level = TEST_LEVEL; + result.frequency = TEST_FREQUENCY; + result.timestamp = TEST_TSF; + return result; + } + + private static void assertScanResultEquals(ScanResult expected, ScanResult actual) { + assertEquals(expected.SSID, actual.SSID); + assertEquals(expected.BSSID, actual.BSSID); + assertEquals(expected.capabilities, actual.capabilities); + assertEquals(expected.level, actual.level); + assertEquals(expected.frequency, actual.frequency); + assertEquals(expected.timestamp, actual.timestamp); + assertArrayEquals(expected.radioChainInfos, actual.radioChainInfos); + } +} |