Merge "Remove settings suggestion v2 feature flag."
diff --git a/Android.bp b/Android.bp
index 2dcbc92..69ee848 100644
--- a/Android.bp
+++ b/Android.bp
@@ -256,6 +256,7 @@
"core/java/android/service/euicc/IGetEidCallback.aidl",
"core/java/android/service/euicc/IGetEuiccInfoCallback.aidl",
"core/java/android/service/euicc/IGetEuiccProfileInfoListCallback.aidl",
+ "core/java/android/service/euicc/IGetOtaStatusCallback.aidl",
"core/java/android/service/euicc/IRetainSubscriptionsForFactoryResetCallback.aidl",
"core/java/android/service/euicc/ISwitchToSubscriptionCallback.aidl",
"core/java/android/service/euicc/IUpdateSubscriptionNicknameCallback.aidl",
@@ -721,11 +722,13 @@
],
srcs: [
+ "core/proto/android/os/batterytype.proto",
"core/proto/android/os/cpufreq.proto",
"core/proto/android/os/cpuinfo.proto",
"core/proto/android/os/kernelwake.proto",
"core/proto/android/os/pagetypeinfo.proto",
"core/proto/android/os/procrank.proto",
+ "core/proto/android/os/ps.proto",
"core/proto/android/os/system_properties.proto",
],
diff --git a/cmds/incident_helper/src/ih_util.cpp b/cmds/incident_helper/src/ih_util.cpp
index 4bf956a..e23e80a 100644
--- a/cmds/incident_helper/src/ih_util.cpp
+++ b/cmds/incident_helper/src/ih_util.cpp
@@ -52,6 +52,12 @@
return toLowerStr(trimDefault(s));
}
+static inline bool isNumber(const std::string& s) {
+ std::string::const_iterator it = s.begin();
+ while (it != s.end() && std::isdigit(*it)) ++it;
+ return !s.empty() && it == s.end();
+}
+
// This is similiar to Split in android-base/file.h, but it won't add empty string
static void split(const std::string& line, std::vector<std::string>& words,
const trans_func& func, const std::string& delimiters) {
@@ -86,24 +92,80 @@
return record;
}
+bool getColumnIndices(std::vector<int>& indices, const char** headerNames, const std::string& line) {
+ indices.clear();
+
+ size_t lastIndex = 0;
+ int i = 0;
+ while (headerNames[i] != NULL) {
+ string s = headerNames[i];
+ lastIndex = line.find(s, lastIndex);
+ if (lastIndex == string::npos) {
+ fprintf(stderr, "Bad Task Header: %s\n", line.c_str());
+ return false;
+ }
+ lastIndex += s.length();
+ indices.push_back(lastIndex);
+ i++;
+ }
+
+ return true;
+}
+
record_t parseRecordByColumns(const std::string& line, const std::vector<int>& indices, const std::string& delimiters) {
record_t record;
int lastIndex = 0;
+ int lastBeginning = 0;
int lineSize = (int)line.size();
for (std::vector<int>::const_iterator it = indices.begin(); it != indices.end(); ++it) {
int idx = *it;
- if (lastIndex > idx || idx > lineSize) {
- record.clear(); // The indices is wrong, return empty;
+ if (idx <= lastIndex) {
+ // We saved up until lastIndex last time, so we should start at
+ // lastIndex + 1 this time.
+ idx = lastIndex + 1;
+ }
+ if (idx > lineSize) {
+ if (lastIndex < idx && lastIndex < lineSize) {
+ // There's a little bit more for us to save, which we'll do
+ // outside of the loop.
+ break;
+ }
+ // If we're past the end of the line AND we've already saved everything up to the end.
+ fprintf(stderr, "index wrong: lastIndex: %d, idx: %d, lineSize: %d\n", lastIndex, idx, lineSize);
+ record.clear(); // The indices are wrong, return empty.
return record;
}
while (idx < lineSize && delimiters.find(line[idx++]) == std::string::npos);
record.push_back(trimDefault(line.substr(lastIndex, idx - lastIndex)));
+ lastBeginning = lastIndex;
lastIndex = idx;
}
- record.push_back(trimDefault(line.substr(lastIndex, lineSize - lastIndex)));
+ if (lineSize - lastIndex > 0) {
+ int beginning = lastIndex;
+ if (record.size() == indices.size()) {
+ // We've already encountered all of the columns...put whatever is
+ // left in the last column.
+ record.pop_back();
+ beginning = lastBeginning;
+ }
+ record.push_back(trimDefault(line.substr(beginning, lineSize - beginning)));
+ }
return record;
}
+void printRecord(const record_t& record) {
+ fprintf(stderr, "Record: { ");
+ if (record.size() == 0) {
+ fprintf(stderr, "}\n");
+ return;
+ }
+ for(size_t i = 0; i < record.size(); ++i) {
+ if(i != 0) fprintf(stderr, "\", ");
+ fprintf(stderr, "\"%s", record[i].c_str());
+ }
+ fprintf(stderr, "\" }\n");
+}
+
bool stripPrefix(std::string* line, const char* key, bool endAtDelimiter) {
const auto head = line->find_first_not_of(DEFAULT_WHITESPACE);
if (head == std::string::npos) return false;
@@ -210,7 +272,10 @@
void
Table::addEnumTypeMap(const char* field, const char* enumNames[], const int enumValues[], const int enumSize)
{
- if (mFields.find(field) == mFields.end()) return;
+ if (mFields.find(field) == mFields.end()) {
+ fprintf(stderr, "Field '%s' not found", string(field).c_str());
+ return;
+ }
map<std::string, int> enu;
for (int i = 0; i < enumSize; i++) {
@@ -268,6 +333,8 @@
}
} else if (mEnumValuesByName.find(value) != mEnumValuesByName.end()) {
proto->write(found, mEnumValuesByName[value]);
+ } else if (isNumber(value)) {
+ proto->write(found, toInt(value));
} else {
return false;
}
diff --git a/cmds/incident_helper/src/ih_util.h b/cmds/incident_helper/src/ih_util.h
index 58ef290..b063b2f 100644
--- a/cmds/incident_helper/src/ih_util.h
+++ b/cmds/incident_helper/src/ih_util.h
@@ -56,12 +56,23 @@
record_t parseRecord(const std::string& line, const std::string& delimiters = DEFAULT_WHITESPACE);
/**
+ * Gets the list of end indices of each word in the line and places it in the given vector,
+ * clearing out the vector beforehand. These indices can be used with parseRecordByColumns.
+ * Will return false if there was a problem getting the indices. headerNames
+ * must be NULL terminated.
+ */
+bool getColumnIndices(std::vector<int>& indices, const char* headerNames[], const std::string& line);
+
+/**
* When a text-format table aligns by its vertical position, it is not possible to split them by purely delimiters.
* This function allows to parse record by its header's column position' indices, must in ascending order.
* At the same time, it still looks at the char at index, if it doesn't belong to delimiters, moves forward to find the delimiters.
*/
record_t parseRecordByColumns(const std::string& line, const std::vector<int>& indices, const std::string& delimiters = DEFAULT_WHITESPACE);
+/** Prints record_t to stderr */
+void printRecord(const record_t& record);
+
/**
* When the line starts/ends with the given key, the function returns true
* as well as the line argument is changed to the rest trimmed part of the original.
diff --git a/cmds/incident_helper/src/main.cpp b/cmds/incident_helper/src/main.cpp
index c8a0883..8c6cd78 100644
--- a/cmds/incident_helper/src/main.cpp
+++ b/cmds/incident_helper/src/main.cpp
@@ -16,11 +16,13 @@
#define LOG_TAG "incident_helper"
+#include "parsers/BatteryTypeParser.h"
#include "parsers/CpuFreqParser.h"
#include "parsers/CpuInfoParser.h"
#include "parsers/KernelWakesParser.h"
#include "parsers/PageTypeInfoParser.h"
#include "parsers/ProcrankParser.h"
+#include "parsers/PsParser.h"
#include "parsers/SystemPropertiesParser.h"
#include <android-base/file.h>
@@ -63,6 +65,10 @@
return new CpuInfoParser();
case 2004:
return new CpuFreqParser();
+ case 2005:
+ return new PsParser();
+ case 2006:
+ return new BatteryTypeParser();
default:
return NULL;
}
diff --git a/cmds/incident_helper/src/parsers/BatteryTypeParser.cpp b/cmds/incident_helper/src/parsers/BatteryTypeParser.cpp
new file mode 100644
index 0000000..ced6cf8
--- /dev/null
+++ b/cmds/incident_helper/src/parsers/BatteryTypeParser.cpp
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#define LOG_TAG "incident_helper"
+
+#include <android/util/ProtoOutputStream.h>
+
+#include "frameworks/base/core/proto/android/os/batterytype.proto.h"
+#include "ih_util.h"
+#include "BatteryTypeParser.h"
+
+using namespace android::os;
+
+status_t
+BatteryTypeParser::Parse(const int in, const int out) const
+{
+ Reader reader(in);
+ string line;
+ bool readLine = false;
+
+ ProtoOutputStream proto;
+
+ // parse line by line
+ while (reader.readLine(&line)) {
+ if (line.empty()) continue;
+
+ if (readLine) {
+ fprintf(stderr, "Multiple lines in file. Unsure what to do.\n");
+ break;
+ }
+
+ proto.write(BatteryTypeProto::TYPE, line);
+
+ readLine = true;
+ }
+
+ if (!reader.ok(&line)) {
+ fprintf(stderr, "Bad read from fd %d: %s\n", in, line.c_str());
+ return -1;
+ }
+
+ if (!proto.flush(out)) {
+ fprintf(stderr, "[%s]Error writing proto back\n", this->name.string());
+ return -1;
+ }
+ fprintf(stderr, "[%s]Proto size: %zu bytes\n", this->name.string(), proto.size());
+ return NO_ERROR;
+}
diff --git a/cmds/incident_helper/src/parsers/BatteryTypeParser.h b/cmds/incident_helper/src/parsers/BatteryTypeParser.h
new file mode 100644
index 0000000..ac0c098
--- /dev/null
+++ b/cmds/incident_helper/src/parsers/BatteryTypeParser.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef BATTERY_TYPE_PARSER_H
+#define BATTERY_TYPE_PARSER_H
+
+#include "TextParserBase.h"
+
+using namespace android;
+
+/**
+ * Battery type parser, parses text in file
+ * /sys/class/power_supply/bms/battery_type.
+ */
+class BatteryTypeParser : public TextParserBase {
+public:
+ BatteryTypeParser() : TextParserBase(String8("BatteryTypeParser")) {};
+ ~BatteryTypeParser() {};
+
+ virtual status_t Parse(const int in, const int out) const;
+};
+
+#endif // BATTERY_TYPE_PARSER_H
diff --git a/cmds/incident_helper/src/parsers/CpuInfoParser.cpp b/cmds/incident_helper/src/parsers/CpuInfoParser.cpp
index 3faca00..d73de54 100644
--- a/cmds/incident_helper/src/parsers/CpuInfoParser.cpp
+++ b/cmds/incident_helper/src/parsers/CpuInfoParser.cpp
@@ -49,6 +49,7 @@
vector<int> columnIndices; // task table can't be split by purely delimiter, needs column positions.
record_t record;
int nline = 0;
+ int diff = 0;
bool nextToSwap = false;
bool nextToUsage = false;
@@ -107,18 +108,10 @@
header = parseHeader(line, "[ %]");
nextToUsage = false;
- // NAME is not in the list since the last split index is default to the end of line.
- const char* headerNames[11] = { "PID", "TID", "USER", "PR", "NI", "CPU", "S", "VIRT", "RES", "PCY", "CMD" };
- size_t lastIndex = 0;
- for (int i = 0; i < 11; i++) {
- string s = headerNames[i];
- lastIndex = line.find(s, lastIndex);
- if (lastIndex == string::npos) {
- fprintf(stderr, "Bad Task Header: %s\n", line.c_str());
- return -1;
- }
- lastIndex += s.length();
- columnIndices.push_back(lastIndex);
+ // NAME is not in the list since we need to modify the end of the CMD index.
+ const char* headerNames[] = { "PID", "TID", "USER", "PR", "NI", "CPU", "S", "VIRT", "RES", "PCY", "CMD", NULL };
+ if (!getColumnIndices(columnIndices, headerNames, line)) {
+ return -1;
}
// Need to remove the end index of CMD and use the start index of NAME because CMD values contain spaces.
// for example: ... CMD NAME
@@ -128,12 +121,20 @@
int endCMD = columnIndices.back();
columnIndices.pop_back();
columnIndices.push_back(line.find("NAME", endCMD) - 1);
+ // Add NAME index to complete the column list.
+ columnIndices.push_back(columnIndices.back() + 4);
continue;
}
record = parseRecordByColumns(line, columnIndices);
- if (record.size() != header.size()) {
- fprintf(stderr, "[%s]Line %d has missing fields:\n%s\n", this->name.string(), nline, line.c_str());
+ diff = record.size() - header.size();
+ if (diff < 0) {
+ fprintf(stderr, "[%s]Line %d has %d missing fields\n%s\n", this->name.string(), nline, -diff, line.c_str());
+ printRecord(record);
+ continue;
+ } else if (diff > 0) {
+ fprintf(stderr, "[%s]Line %d has %d extra fields\n%s\n", this->name.string(), nline, diff, line.c_str());
+ printRecord(record);
continue;
}
diff --git a/cmds/incident_helper/src/parsers/KernelWakesParser.cpp b/cmds/incident_helper/src/parsers/KernelWakesParser.cpp
index ada4a5d..cae51ab 100644
--- a/cmds/incident_helper/src/parsers/KernelWakesParser.cpp
+++ b/cmds/incident_helper/src/parsers/KernelWakesParser.cpp
@@ -47,10 +47,14 @@
// parse for each record, the line delimiter is \t only!
record = parseRecord(line, TAB_DELIMITER);
- if (record.size() != header.size()) {
+ if (record.size() < header.size()) {
// TODO: log this to incident report!
fprintf(stderr, "[%s]Line %d has missing fields\n%s\n", this->name.string(), nline, line.c_str());
continue;
+ } else if (record.size() > header.size()) {
+ // TODO: log this to incident report!
+ fprintf(stderr, "[%s]Line %d has extra fields\n%s\n", this->name.string(), nline, line.c_str());
+ continue;
}
long long token = proto.start(KernelWakeSources::WAKEUP_SOURCES);
diff --git a/cmds/incident_helper/src/parsers/PsParser.cpp b/cmds/incident_helper/src/parsers/PsParser.cpp
new file mode 100644
index 0000000..e9014ca
--- /dev/null
+++ b/cmds/incident_helper/src/parsers/PsParser.cpp
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#define LOG_TAG "incident_helper"
+
+#include <android/util/ProtoOutputStream.h>
+
+#include "frameworks/base/core/proto/android/os/ps.proto.h"
+#include "ih_util.h"
+#include "PsParser.h"
+
+using namespace android::os;
+
+status_t PsParser::Parse(const int in, const int out) const {
+ Reader reader(in);
+ string line;
+ header_t header; // the header of /d/wakeup_sources
+ vector<int> columnIndices; // task table can't be split by purely delimiter, needs column positions.
+ record_t record; // retain each record
+ int nline = 0;
+ int diff = 0;
+
+ ProtoOutputStream proto;
+ Table table(PsDumpProto::Process::_FIELD_NAMES, PsDumpProto::Process::_FIELD_IDS, PsDumpProto::Process::_FIELD_COUNT);
+ const char* pcyNames[] = { "fg", "bg", "ta" };
+ const int pcyValues[] = {PsDumpProto::Process::POLICY_FG, PsDumpProto::Process::POLICY_BG, PsDumpProto::Process::POLICY_TA};
+ table.addEnumTypeMap("pcy", pcyNames, pcyValues, 3);
+ const char* sNames[] = { "D", "R", "S", "T", "t", "X", "Z" };
+ const int sValues[] = {PsDumpProto::Process::STATE_D, PsDumpProto::Process::STATE_R, PsDumpProto::Process::STATE_S, PsDumpProto::Process::STATE_T, PsDumpProto::Process::STATE_TRACING, PsDumpProto::Process::STATE_X, PsDumpProto::Process::STATE_Z};
+ table.addEnumTypeMap("s", sNames, sValues, 7);
+
+ // Parse line by line
+ while (reader.readLine(&line)) {
+ if (line.empty()) continue;
+
+ if (nline++ == 0) {
+ header = parseHeader(line, DEFAULT_WHITESPACE);
+
+ const char* headerNames[] = { "LABEL", "USER", "PID", "TID", "PPID", "VSZ", "RSS", "WCHAN", "ADDR", "S", "PRI", "NI", "RTPRIO", "SCH", "PCY", "TIME", "CMD", NULL };
+ if (!getColumnIndices(columnIndices, headerNames, line)) {
+ return -1;
+ }
+
+ continue;
+ }
+
+ record = parseRecordByColumns(line, columnIndices);
+
+ diff = record.size() - header.size();
+ if (diff < 0) {
+ // TODO: log this to incident report!
+ fprintf(stderr, "[%s]Line %d has %d missing fields\n%s\n", this->name.string(), nline, -diff, line.c_str());
+ printRecord(record);
+ continue;
+ } else if (diff > 0) {
+ // TODO: log this to incident report!
+ fprintf(stderr, "[%s]Line %d has %d extra fields\n%s\n", this->name.string(), nline, diff, line.c_str());
+ printRecord(record);
+ continue;
+ }
+
+ long long token = proto.start(PsDumpProto::PROCESSES);
+ for (int i=0; i<(int)record.size(); i++) {
+ if (!table.insertField(&proto, header[i], record[i])) {
+ fprintf(stderr, "[%s]Line %d has bad value %s of %s\n",
+ this->name.string(), nline, header[i].c_str(), record[i].c_str());
+ }
+ }
+ proto.end(token);
+ }
+
+ if (!reader.ok(&line)) {
+ fprintf(stderr, "Bad read from fd %d: %s\n", in, line.c_str());
+ return -1;
+ }
+
+ if (!proto.flush(out)) {
+ fprintf(stderr, "[%s]Error writing proto back\n", this->name.string());
+ return -1;
+ }
+ fprintf(stderr, "[%s]Proto size: %zu bytes\n", this->name.string(), proto.size());
+ return NO_ERROR;
+}
diff --git a/cmds/incident_helper/src/parsers/PsParser.h b/cmds/incident_helper/src/parsers/PsParser.h
new file mode 100644
index 0000000..9488e40
--- /dev/null
+++ b/cmds/incident_helper/src/parsers/PsParser.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef PS_PARSER_H
+#define PS_PARSER_H
+
+#include "TextParserBase.h"
+
+/**
+ * PS parser, parses output of 'ps' command to protobuf.
+ */
+class PsParser : public TextParserBase {
+public:
+ PsParser() : TextParserBase(String8("Ps")) {};
+ ~PsParser() {};
+
+ virtual status_t Parse(const int in, const int out) const;
+};
+
+#endif // PS_PARSER_H
diff --git a/cmds/incident_helper/testdata/batterytype.txt b/cmds/incident_helper/testdata/batterytype.txt
new file mode 100644
index 0000000..c763d36
--- /dev/null
+++ b/cmds/incident_helper/testdata/batterytype.txt
@@ -0,0 +1 @@
+random_battery_type_string
diff --git a/cmds/incident_helper/testdata/ps.txt b/cmds/incident_helper/testdata/ps.txt
new file mode 100644
index 0000000..72dafc2c4
--- /dev/null
+++ b/cmds/incident_helper/testdata/ps.txt
@@ -0,0 +1,9 @@
+LABEL USER PID TID PPID VSZ RSS WCHAN ADDR S PRI NI RTPRIO SCH PCY TIME CMD
+u:r:init:s0 root 1 1 0 15816 2636 SyS_epoll_wait 0 S 19 0 - 0 fg 00:00:01 init
+u:r:kernel:s0 root 2 2 0 0 0 kthreadd 0 S 19 0 - 0 fg 00:00:00 kthreadd
+u:r:surfaceflinger:s0 system 499 534 1 73940 22024 futex_wait_queue_me 0 S 42 -9 2 1 fg 00:00:00 EventThread
+u:r:hal_gnss_default:s0 gps 670 2004 1 43064 7272 poll_schedule_timeout 0 S 19 0 - 0 fg 00:00:00 Loc_hal_worker
+u:r:platform_app:s0:c512,c768 u0_a48 1660 1976 806 4468612 138328 binder_thread_read 0 S 35 -16 - 0 ta 00:00:00 HwBinder:1660_1
+u:r:perfd:s0 root 1939 1946 1 18132 2088 __skb_recv_datagram 7b9782fd14 S 19 0 - 0 00:00:00 perfd
+u:r:perfd:s0 root 1939 1955 1 18132 2088 do_sigtimedwait 7b9782ff6c S 19 0 - 0 00:00:00 POSIX timer 0
+u:r:shell:s0 shell 2645 2645 802 11664 2972 0 7f67a2f8b4 R 19 0 - 0 fg 00:00:00 ps
diff --git a/cmds/incident_helper/tests/BatteryTypeParser_test.cpp b/cmds/incident_helper/tests/BatteryTypeParser_test.cpp
new file mode 100644
index 0000000..7fbe22d
--- /dev/null
+++ b/cmds/incident_helper/tests/BatteryTypeParser_test.cpp
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "BatteryTypeParser.h"
+
+#include "frameworks/base/core/proto/android/os/batterytype.pb.h"
+
+#include <android-base/file.h>
+#include <android-base/test_utils.h>
+#include <gmock/gmock.h>
+#include <google/protobuf/message_lite.h>
+#include <gtest/gtest.h>
+#include <string.h>
+#include <fcntl.h>
+
+using namespace android::base;
+using namespace android::os;
+using namespace std;
+using ::testing::StrEq;
+using ::testing::Test;
+using ::testing::internal::CaptureStderr;
+using ::testing::internal::CaptureStdout;
+using ::testing::internal::GetCapturedStderr;
+using ::testing::internal::GetCapturedStdout;
+
+class BatteryTypeParserTest : public Test {
+public:
+ virtual void SetUp() override {
+ ASSERT_TRUE(tf.fd != -1);
+ }
+
+protected:
+ TemporaryFile tf;
+
+ const string kTestPath = GetExecutableDirectory();
+ const string kTestDataPath = kTestPath + "/testdata/";
+};
+
+TEST_F(BatteryTypeParserTest, Success) {
+ const string testFile = kTestDataPath + "batterytype.txt";
+ BatteryTypeParser parser;
+ BatteryTypeProto expected;
+
+ expected.set_type("random_battery_type_string");
+
+ int fd = open(testFile.c_str(), O_RDONLY);
+ ASSERT_TRUE(fd != -1);
+
+ CaptureStdout();
+ ASSERT_EQ(NO_ERROR, parser.Parse(fd, STDOUT_FILENO));
+ EXPECT_EQ(GetCapturedStdout(), expected.SerializeAsString());
+ close(fd);
+}
diff --git a/cmds/incident_helper/tests/PsParser_test.cpp b/cmds/incident_helper/tests/PsParser_test.cpp
new file mode 100644
index 0000000..1f03a7f
--- /dev/null
+++ b/cmds/incident_helper/tests/PsParser_test.cpp
@@ -0,0 +1,300 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "PsParser.h"
+
+#include "frameworks/base/core/proto/android/os/ps.pb.h"
+
+#include <android-base/file.h>
+#include <android-base/test_utils.h>
+#include <gmock/gmock.h>
+#include <google/protobuf/message_lite.h>
+#include <gtest/gtest.h>
+#include <string.h>
+#include <fcntl.h>
+
+using namespace android::base;
+using namespace android::os;
+using namespace std;
+using ::testing::StrEq;
+using ::testing::Test;
+using ::testing::internal::CaptureStderr;
+using ::testing::internal::CaptureStdout;
+using ::testing::internal::GetCapturedStderr;
+using ::testing::internal::GetCapturedStdout;
+
+class PsParserTest : public Test {
+public:
+ virtual void SetUp() override {
+ ASSERT_TRUE(tf.fd != -1);
+ }
+
+protected:
+ TemporaryFile tf;
+
+ const string kTestPath = GetExecutableDirectory();
+ const string kTestDataPath = kTestPath + "/testdata/";
+};
+
+TEST_F(PsParserTest, Normal) {
+ const string testFile = kTestDataPath + "ps.txt";
+ PsParser parser;
+ PsDumpProto expected;
+ PsDumpProto got;
+
+ PsDumpProto::Process* record1 = expected.add_processes();
+ record1->set_label("u:r:init:s0");
+ record1->set_user("root");
+ record1->set_pid(1);
+ record1->set_tid(1);
+ record1->set_ppid(0);
+ record1->set_vsz(15816);
+ record1->set_rss(2636);
+ record1->set_wchan("SyS_epoll_wait");
+ record1->set_addr("0");
+ record1->set_s(PsDumpProto_Process_ProcessStateCode_STATE_S);
+ record1->set_pri(19);
+ record1->set_ni(0);
+ record1->set_rtprio("-");
+ record1->set_sch(PsDumpProto_Process_SchedulingPolicy_SCH_NORMAL);
+ record1->set_pcy(PsDumpProto::Process::POLICY_FG);
+ record1->set_time("00:00:01");
+ record1->set_cmd("init");
+
+ PsDumpProto::Process* record2 = expected.add_processes();
+ record2->set_label("u:r:kernel:s0");
+ record2->set_user("root");
+ record2->set_pid(2);
+ record2->set_tid(2);
+ record2->set_ppid(0);
+ record2->set_vsz(0);
+ record2->set_rss(0);
+ record2->set_wchan("kthreadd");
+ record2->set_addr("0");
+ record2->set_s(PsDumpProto_Process_ProcessStateCode_STATE_S);
+ record2->set_pri(19);
+ record2->set_ni(0);
+ record2->set_rtprio("-");
+ record2->set_sch(PsDumpProto_Process_SchedulingPolicy_SCH_NORMAL);
+ record2->set_pcy(PsDumpProto::Process::POLICY_FG);
+ record2->set_time("00:00:00");
+ record2->set_cmd("kthreadd");
+
+ PsDumpProto::Process* record3 = expected.add_processes();
+ record3->set_label("u:r:surfaceflinger:s0");
+ record3->set_user("system");
+ record3->set_pid(499);
+ record3->set_tid(534);
+ record3->set_ppid(1);
+ record3->set_vsz(73940);
+ record3->set_rss(22024);
+ record3->set_wchan("futex_wait_queue_me");
+ record3->set_addr("0");
+ record3->set_s(PsDumpProto_Process_ProcessStateCode_STATE_S);
+ record3->set_pri(42);
+ record3->set_ni(-9);
+ record3->set_rtprio("2");
+ record3->set_sch(PsDumpProto_Process_SchedulingPolicy_SCH_FIFO);
+ record3->set_pcy(PsDumpProto::Process::POLICY_FG);
+ record3->set_time("00:00:00");
+ record3->set_cmd("EventThread");
+
+ PsDumpProto::Process* record4 = expected.add_processes();
+ record4->set_label("u:r:hal_gnss_default:s0");
+ record4->set_user("gps");
+ record4->set_pid(670);
+ record4->set_tid(2004);
+ record4->set_ppid(1);
+ record4->set_vsz(43064);
+ record4->set_rss(7272);
+ record4->set_wchan("poll_schedule_timeout");
+ record4->set_addr("0");
+ record4->set_s(PsDumpProto_Process_ProcessStateCode_STATE_S);
+ record4->set_pri(19);
+ record4->set_ni(0);
+ record4->set_rtprio("-");
+ record4->set_sch(PsDumpProto_Process_SchedulingPolicy_SCH_NORMAL);
+ record4->set_pcy(PsDumpProto::Process::POLICY_FG);
+ record4->set_time("00:00:00");
+ record4->set_cmd("Loc_hal_worker");
+
+ PsDumpProto::Process* record5 = expected.add_processes();
+ record5->set_label("u:r:platform_app:s0:c512,c768");
+ record5->set_user("u0_a48");
+ record5->set_pid(1660);
+ record5->set_tid(1976);
+ record5->set_ppid(806);
+ record5->set_vsz(4468612);
+ record5->set_rss(138328);
+ record5->set_wchan("binder_thread_read");
+ record5->set_addr("0");
+ record5->set_s(PsDumpProto_Process_ProcessStateCode_STATE_S);
+ record5->set_pri(35);
+ record5->set_ni(-16);
+ record5->set_rtprio("-");
+ record5->set_sch(PsDumpProto_Process_SchedulingPolicy_SCH_NORMAL);
+ record5->set_pcy(PsDumpProto::Process::POLICY_TA);
+ record5->set_time("00:00:00");
+ record5->set_cmd("HwBinder:1660_1");
+
+ PsDumpProto::Process* record6 = expected.add_processes();
+ record6->set_label("u:r:perfd:s0");
+ record6->set_user("root");
+ record6->set_pid(1939);
+ record6->set_tid(1946);
+ record6->set_ppid(1);
+ record6->set_vsz(18132);
+ record6->set_rss(2088);
+ record6->set_wchan("__skb_recv_datagram");
+ record6->set_addr("7b9782fd14");
+ record6->set_s(PsDumpProto_Process_ProcessStateCode_STATE_S);
+ record6->set_pri(19);
+ record6->set_ni(0);
+ record6->set_rtprio("-");
+ record6->set_sch(PsDumpProto_Process_SchedulingPolicy_SCH_NORMAL);
+ record6->set_pcy(PsDumpProto::Process::POLICY_UNKNOWN);
+ record6->set_time("00:00:00");
+ record6->set_cmd("perfd");
+
+ PsDumpProto::Process* record7 = expected.add_processes();
+ record7->set_label("u:r:perfd:s0");
+ record7->set_user("root");
+ record7->set_pid(1939);
+ record7->set_tid(1955);
+ record7->set_ppid(1);
+ record7->set_vsz(18132);
+ record7->set_rss(2088);
+ record7->set_wchan("do_sigtimedwait");
+ record7->set_addr("7b9782ff6c");
+ record7->set_s(PsDumpProto_Process_ProcessStateCode_STATE_S);
+ record7->set_pri(19);
+ record7->set_ni(0);
+ record7->set_rtprio("-");
+ record7->set_sch(PsDumpProto_Process_SchedulingPolicy_SCH_NORMAL);
+ record7->set_pcy(PsDumpProto::Process::POLICY_UNKNOWN);
+ record7->set_time("00:00:00");
+ record7->set_cmd("POSIX timer 0");
+
+ PsDumpProto::Process* record8 = expected.add_processes();
+ record8->set_label("u:r:shell:s0");
+ record8->set_user("shell");
+ record8->set_pid(2645);
+ record8->set_tid(2645);
+ record8->set_ppid(802);
+ record8->set_vsz(11664);
+ record8->set_rss(2972);
+ record8->set_wchan("0");
+ record8->set_addr("7f67a2f8b4");
+ record8->set_s(PsDumpProto_Process_ProcessStateCode_STATE_R);
+ record8->set_pri(19);
+ record8->set_ni(0);
+ record8->set_rtprio("-");
+ record8->set_sch(PsDumpProto_Process_SchedulingPolicy_SCH_NORMAL);
+ record8->set_pcy(PsDumpProto::Process::POLICY_FG);
+ record8->set_time("00:00:00");
+ record8->set_cmd("ps");
+
+ int fd = open(testFile.c_str(), O_RDONLY);
+ ASSERT_TRUE(fd != -1);
+
+ CaptureStdout();
+ ASSERT_EQ(NO_ERROR, parser.Parse(fd, STDOUT_FILENO));
+ got.ParseFromString(GetCapturedStdout());
+ bool matches = true;
+
+ if (got.processes_size() != expected.processes_size()) {
+ fprintf(stderr, "Got %d processes, want %d\n", got.processes_size(), expected.processes_size());
+ matches = false;
+ } else {
+ int n = got.processes_size();
+ for (int i = 0; i < n; i++) {
+ PsDumpProto::Process g = got.processes(i);
+ PsDumpProto::Process e = expected.processes(i);
+
+ if (g.label() != e.label()) {
+ fprintf(stderr, "prcs[%d]: Invalid label. Got %s, want %s\n", i, g.label().c_str(), e.label().c_str());
+ matches = false;
+ }
+ if (g.user() != e.user()) {
+ fprintf(stderr, "prcs[%d]: Invalid user. Got %s, want %s\n", i, g.user().c_str(), e.user().c_str());
+ matches = false;
+ }
+ if (g.pid() != e.pid()) {
+ fprintf(stderr, "prcs[%d]: Invalid pid. Got %d, want %d\n", i, g.pid(), e.pid());
+ matches = false;
+ }
+ if (g.tid() != e.tid()) {
+ fprintf(stderr, "prcs[%d]: Invalid tid. Got %d, want %d\n", i, g.tid(), e.tid());
+ matches = false;
+ }
+ if (g.ppid() != e.ppid()) {
+ fprintf(stderr, "prcs[%d]: Invalid ppid. Got %d, want %d\n", i, g.ppid(), e.ppid());
+ matches = false;
+ }
+ if (g.vsz() != e.vsz()) {
+ fprintf(stderr, "prcs[%d]: Invalid vsz. Got %d, want %d\n", i, g.vsz(), e.vsz());
+ matches = false;
+ }
+ if (g.rss() != e.rss()) {
+ fprintf(stderr, "prcs[%d]: Invalid rss. Got %d, want %d\n", i, g.rss(), e.rss());
+ matches = false;
+ }
+ if (g.wchan() != e.wchan()) {
+ fprintf(stderr, "prcs[%d]: Invalid wchan. Got %s, want %s\n", i, g.wchan().c_str(), e.wchan().c_str());
+ matches = false;
+ }
+ if (g.addr() != e.addr()) {
+ fprintf(stderr, "prcs[%d]: Invalid addr. Got %s, want %s\n", i, g.addr().c_str(), e.addr().c_str());
+ matches = false;
+ }
+ if (g.s() != e.s()) {
+ fprintf(stderr, "prcs[%d]: Invalid s. Got %u, want %u\n", i, g.s(), e.s());
+ matches = false;
+ }
+ if (g.pri() != e.pri()) {
+ fprintf(stderr, "prcs[%d]: Invalid pri. Got %d, want %d\n", i, g.pri(), e.pri());
+ matches = false;
+ }
+ if (g.ni() != e.ni()) {
+ fprintf(stderr, "prcs[%d]: Invalid ni. Got %d, want %d\n", i, g.ni(), e.ni());
+ matches = false;
+ }
+ if (g.rtprio() != e.rtprio()) {
+ fprintf(stderr, "prcs[%d]: Invalid rtprio. Got %s, want %s\n", i, g.rtprio().c_str(), e.rtprio().c_str());
+ matches = false;
+ }
+ if (g.sch() != e.sch()) {
+ fprintf(stderr, "prcs[%d]: Invalid sch. Got %u, want %u\n", i, g.sch(), e.sch());
+ matches = false;
+ }
+ if (g.pcy() != e.pcy()) {
+ fprintf(stderr, "prcs[%d]: Invalid pcy. Got %u, want %u\n", i, g.pcy(), e.pcy());
+ matches = false;
+ }
+ if (g.time() != e.time()) {
+ fprintf(stderr, "prcs[%d]: Invalid time. Got %s, want %s\n", i, g.time().c_str(), e.time().c_str());
+ matches = false;
+ }
+ if (g.cmd() != e.cmd()) {
+ fprintf(stderr, "prcs[%d]: Invalid cmd. Got %s, want %s\n", i, g.cmd().c_str(), e.cmd().c_str());
+ matches = false;
+ }
+ }
+ }
+
+ EXPECT_TRUE(matches);
+ close(fd);
+}
diff --git a/cmds/incident_helper/tests/ih_util_test.cpp b/cmds/incident_helper/tests/ih_util_test.cpp
index 5740b33..7b8cf52 100644
--- a/cmds/incident_helper/tests/ih_util_test.cpp
+++ b/cmds/incident_helper/tests/ih_util_test.cpp
@@ -71,11 +71,29 @@
EXPECT_EQ(expected, result);
result = parseRecordByColumns("abc \t2345 6789 ", indices);
- expected = { "abc", "2345", "6789" };
+ expected = { "abc", "2345 6789" };
EXPECT_EQ(expected, result);
- result = parseRecordByColumns("abc \t23456789 bob", indices);
- expected = { "abc", "23456789", "bob" };
+ std::string extraColumn1 = "abc \t23456789 bob";
+ std::string emptyMidColm = "abc \t bob";
+ std::string longFirstClm = "abcdefgt\t6789 bob";
+ std::string lngFrstEmpty = "abcdefgt\t bob";
+
+ result = parseRecordByColumns(extraColumn1, indices);
+ expected = { "abc", "23456789 bob" };
+ EXPECT_EQ(expected, result);
+
+ // 2nd column should be treated as an empty entry.
+ result = parseRecordByColumns(emptyMidColm, indices);
+ expected = { "abc", "bob" };
+ EXPECT_EQ(expected, result);
+
+ result = parseRecordByColumns(longFirstClm, indices);
+ expected = { "abcdefgt", "6789 bob" };
+ EXPECT_EQ(expected, result);
+
+ result = parseRecordByColumns(lngFrstEmpty, indices);
+ expected = { "abcdefgt", "bob" };
EXPECT_EQ(expected, result);
}
diff --git a/cmds/incidentd/src/Section.cpp b/cmds/incidentd/src/Section.cpp
index 1bf795b..22053ef 100644
--- a/cmds/incidentd/src/Section.cpp
+++ b/cmds/incidentd/src/Section.cpp
@@ -521,7 +521,7 @@
ALOGW("CommandSection '%s' failed to set up stdout: %s", this->name.string(), strerror(errno));
_exit(EXIT_FAILURE);
}
- execv(this->mCommand[0], (char *const *) this->mCommand);
+ execvp(this->mCommand[0], (char *const *) this->mCommand);
int err = errno; // record command error code
ALOGW("CommandSection '%s' failed in executing command: %s", this->name.string(), strerror(errno));
_exit(err); // exit with command error code
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 1adae7a..847f91b 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -60,6 +60,7 @@
import android.os.ServiceManager;
import android.os.SystemProperties;
import android.os.UserHandle;
+import android.os.WorkSource;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.DisplayMetrics;
@@ -3911,10 +3912,10 @@
/**
* @hide
*/
- public static void noteWakeupAlarm(PendingIntent ps, int sourceUid, String sourcePkg,
- String tag) {
+ public static void noteWakeupAlarm(PendingIntent ps, WorkSource workSource, int sourceUid,
+ String sourcePkg, String tag) {
try {
- getService().noteWakeupAlarm((ps != null) ? ps.getTarget() : null,
+ getService().noteWakeupAlarm((ps != null) ? ps.getTarget() : null, workSource,
sourceUid, sourcePkg, tag);
} catch (RemoteException ex) {
}
@@ -3923,19 +3924,24 @@
/**
* @hide
*/
- public static void noteAlarmStart(PendingIntent ps, int sourceUid, String tag) {
+ public static void noteAlarmStart(PendingIntent ps, WorkSource workSource, int sourceUid,
+ String tag) {
try {
- getService().noteAlarmStart((ps != null) ? ps.getTarget() : null, sourceUid, tag);
+ getService().noteAlarmStart((ps != null) ? ps.getTarget() : null, workSource,
+ sourceUid, tag);
} catch (RemoteException ex) {
}
}
+
/**
* @hide
*/
- public static void noteAlarmFinish(PendingIntent ps, int sourceUid, String tag) {
+ public static void noteAlarmFinish(PendingIntent ps, WorkSource workSource, int sourceUid,
+ String tag) {
try {
- getService().noteAlarmFinish((ps != null) ? ps.getTarget() : null, sourceUid, tag);
+ getService().noteAlarmFinish((ps != null) ? ps.getTarget() : null, workSource,
+ sourceUid, tag);
} catch (RemoteException ex) {
}
}
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index c09403c..4c558f3 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -75,20 +75,20 @@
*/
static public void noteWakeupAlarm(PendingIntent ps, int sourceUid, String sourcePkg,
String tag) {
- ActivityManager.noteWakeupAlarm(ps, sourceUid, sourcePkg, tag);
+ ActivityManager.noteWakeupAlarm(ps, null, sourceUid, sourcePkg, tag);
}
/**
* @deprecated use ActivityManager.noteAlarmStart instead.
*/
static public void noteAlarmStart(PendingIntent ps, int sourceUid, String tag) {
- ActivityManager.noteAlarmStart(ps, sourceUid, tag);
+ ActivityManager.noteAlarmStart(ps, null, sourceUid, tag);
}
/**
* @deprecated use ActivityManager.noteAlarmFinish instead.
*/
static public void noteAlarmFinish(PendingIntent ps, int sourceUid, String tag) {
- ActivityManager.noteAlarmFinish(ps, sourceUid, tag);
+ ActivityManager.noteAlarmFinish(ps, null, sourceUid, tag);
}
}
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index 1278f75..a9e633f 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -63,6 +63,7 @@
import android.os.ParcelFileDescriptor;
import android.os.PersistableBundle;
import android.os.StrictMode;
+import android.os.WorkSource;
import android.service.voice.IVoiceInteractionSession;
import com.android.internal.app.IVoiceInteractor;
import com.android.internal.os.IResultReceiver;
@@ -198,7 +199,7 @@
void enterSafeMode();
boolean startNextMatchingActivity(in IBinder callingActivity,
in Intent intent, in Bundle options);
- void noteWakeupAlarm(in IIntentSender sender, int sourceUid,
+ void noteWakeupAlarm(in IIntentSender sender, in WorkSource workSource, int sourceUid,
in String sourcePkg, in String tag);
void removeContentProvider(in IBinder connection, boolean stable);
void setRequestedOrientation(in IBinder token, int requestedOrientation);
@@ -468,8 +469,8 @@
void dumpHeapFinished(in String path);
void setVoiceKeepAwake(in IVoiceInteractionSession session, boolean keepAwake);
void updateLockTaskPackages(int userId, in String[] packages);
- void noteAlarmStart(in IIntentSender sender, int sourceUid, in String tag);
- void noteAlarmFinish(in IIntentSender sender, int sourceUid, in String tag);
+ void noteAlarmStart(in IIntentSender sender, in WorkSource workSource, int sourceUid, in String tag);
+ void noteAlarmFinish(in IIntentSender sender, in WorkSource workSource, int sourceUid, in String tag);
int getPackageProcessState(in String packageName, in String callingPackage);
oneway void showLockTaskEscapeMessage(in IBinder token);
void updateDeviceOwner(in String packageName);
diff --git a/core/java/android/content/pm/PackageList.java b/core/java/android/content/pm/PackageList.java
new file mode 100644
index 0000000..cfd99ab
--- /dev/null
+++ b/core/java/android/content/pm/PackageList.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.pm.PackageManagerInternal.PackageListObserver;
+
+import com.android.server.LocalServices;
+
+import java.util.List;
+
+/**
+ * All of the package name installed on the system.
+ * <p>A self observable list that automatically removes the listener when it goes out of scope.
+ *
+ * @hide Only for use within the system server.
+ */
+public class PackageList implements PackageListObserver, AutoCloseable {
+ private final PackageListObserver mWrappedObserver;
+ private final List<String> mPackageNames;
+
+ /**
+ * Create a new object.
+ * <p>Ownership of the given {@link List} transfers to this object and should not
+ * be modified by the caller.
+ */
+ public PackageList(@NonNull List<String> packageNames, @Nullable PackageListObserver observer) {
+ mPackageNames = packageNames;
+ mWrappedObserver = observer;
+ }
+
+ @Override
+ public void onPackageAdded(String packageName) {
+ if (mWrappedObserver != null) {
+ mWrappedObserver.onPackageAdded(packageName);
+ }
+ }
+
+ @Override
+ public void onPackageRemoved(String packageName) {
+ if (mWrappedObserver != null) {
+ mWrappedObserver.onPackageRemoved(packageName);
+ }
+ }
+
+ @Override
+ public void close() throws Exception {
+ LocalServices.getService(PackageManagerInternal.class).removePackageListObserver(this);
+ }
+
+ /**
+ * Returns the names of packages installed on the system.
+ * <p>The list is a copy-in-time and the actual set of installed packages may differ. Real
+ * time updates to the package list are sent via the {@link PackageListObserver} callback.
+ */
+ public @NonNull List<String> getPackageNames() {
+ return mPackageNames;
+ }
+}
diff --git a/core/java/android/content/pm/PackageManagerInternal.java b/core/java/android/content/pm/PackageManagerInternal.java
index 713cd10..8ee8e10 100644
--- a/core/java/android/content/pm/PackageManagerInternal.java
+++ b/core/java/android/content/pm/PackageManagerInternal.java
@@ -53,6 +53,14 @@
@Retention(RetentionPolicy.SOURCE)
public @interface KnownPackage {}
+ /** Observer called whenever the list of packages changes */
+ public interface PackageListObserver {
+ /** A package was added to the system. */
+ void onPackageAdded(@NonNull String packageName);
+ /** A package was removed from the system. */
+ void onPackageRemoved(@NonNull String packageName);
+ }
+
/**
* Provider for package names.
*/
@@ -435,6 +443,35 @@
public abstract @Nullable PackageParser.Package getPackage(@NonNull String packageName);
/**
+ * Returns a list without a change observer.
+ *
+ * {@see #getPackageList(PackageListObserver)}
+ */
+ public @NonNull PackageList getPackageList() {
+ return getPackageList(null);
+ }
+
+ /**
+ * Returns the list of packages installed at the time of the method call.
+ * <p>The given observer is notified when the list of installed packages
+ * changes [eg. a package was installed or uninstalled]. It will not be
+ * notified if a package is updated.
+ * <p>The package list will not be updated automatically as packages are
+ * installed / uninstalled. Any changes must be handled within the observer.
+ */
+ public abstract @NonNull PackageList getPackageList(@Nullable PackageListObserver observer);
+
+ /**
+ * Removes the observer.
+ * <p>Generally not needed. {@link #getPackageList(PackageListObserver)} will automatically
+ * remove the observer.
+ * <p>Does nothing if the observer isn't currently registered.
+ * <p>Observers are notified asynchronously and it's possible for an observer to be
+ * invoked after its been removed.
+ */
+ public abstract void removePackageListObserver(@NonNull PackageListObserver observer);
+
+ /**
* Returns a package object for the disabled system package name.
*/
public abstract @Nullable PackageParser.Package getDisabledPackage(@NonNull String packageName);
diff --git a/core/java/android/net/NetworkWatchlistManager.java b/core/java/android/net/NetworkWatchlistManager.java
index 42e43c8..5425bf5 100644
--- a/core/java/android/net/NetworkWatchlistManager.java
+++ b/core/java/android/net/NetworkWatchlistManager.java
@@ -59,8 +59,8 @@
/**
* Report network watchlist records if necessary.
*
- * Watchlist report process will run summarize records into a single report, then the
- * report will be processed by differential privacy framework and store it on disk.
+ * Watchlist report process will summarize records into a single report, then the
+ * report will be processed by differential privacy framework and stored on disk.
*
* @hide
*/
@@ -72,4 +72,18 @@
e.rethrowFromSystemServer();
}
}
+
+ /**
+ * Reload network watchlist.
+ *
+ * @hide
+ */
+ public void reloadWatchlist() {
+ try {
+ mNetworkWatchlistManager.reloadWatchlist();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Unable to reload watchlist");
+ e.rethrowFromSystemServer();
+ }
+ }
}
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index 1e847c5..d4d74f4 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -180,6 +180,11 @@
public static final int FOREGROUND_SERVICE = 22;
/**
+ * A constant indicating an aggregate wifi multicast timer
+ */
+ public static final int WIFI_AGGREGATE_MULTICAST_ENABLED = 23;
+
+ /**
* Include all of the data in the stats, including previously saved data.
*/
public static final int STATS_SINCE_CHARGED = 0;
@@ -2334,6 +2339,22 @@
};
/**
+ * Returns total time for WiFi Multicast Wakelock timer.
+ * Note that this may be different from the sum of per uid timer values.
+ *
+ * {@hide}
+ */
+ public abstract long getWifiMulticastWakelockTime(long elapsedRealtimeUs, int which);
+
+ /**
+ * Returns total time for WiFi Multicast Wakelock timer
+ * Note that this may be different from the sum of per uid timer values.
+ *
+ * {@hide}
+ */
+ public abstract int getWifiMulticastWakelockCount(int which);
+
+ /**
* Returns the time in microseconds that wifi has been on while the device was
* running on battery.
*
@@ -3442,16 +3463,13 @@
screenDozeTime / 1000);
- // Calculate both wakelock and wifi multicast wakelock times across all uids.
+ // Calculate wakelock times across all uids.
long fullWakeLockTimeTotal = 0;
long partialWakeLockTimeTotal = 0;
- long multicastWakeLockTimeTotalMicros = 0;
- int multicastWakeLockCountTotal = 0;
for (int iu = 0; iu < NU; iu++) {
final Uid u = uidStats.valueAt(iu);
- // First calculating the wakelock stats
final ArrayMap<String, ? extends BatteryStats.Uid.Wakelock> wakelocks
= u.getWakelockStats();
for (int iw=wakelocks.size()-1; iw>=0; iw--) {
@@ -3469,13 +3487,6 @@
rawRealtime, which);
}
}
-
- // Now calculating the wifi multicast wakelock stats
- final Timer mcTimer = u.getMulticastWakelockStats();
- if (mcTimer != null) {
- multicastWakeLockTimeTotalMicros += mcTimer.getTotalTimeLocked(rawRealtime, which);
- multicastWakeLockCountTotal += mcTimer.getCountLocked(which);
- }
}
// Dump network stats
@@ -3592,6 +3603,9 @@
dumpLine(pw, 0 /* uid */, category, WIFI_SIGNAL_STRENGTH_COUNT_DATA, args);
// Dump Multicast total stats
+ final long multicastWakeLockTimeTotalMicros =
+ getWifiMulticastWakelockTime(rawRealtime, which);
+ final int multicastWakeLockCountTotal = getWifiMulticastWakelockCount(which);
dumpLine(pw, 0 /* uid */, category, WIFI_MULTICAST_TOTAL_DATA,
multicastWakeLockTimeTotalMicros / 1000,
multicastWakeLockCountTotal);
@@ -4456,18 +4470,15 @@
pw.print(" Connectivity changes: "); pw.println(connChanges);
}
- // Calculate both wakelock and wifi multicast wakelock times across all uids.
+ // Calculate wakelock times across all uids.
long fullWakeLockTimeTotalMicros = 0;
long partialWakeLockTimeTotalMicros = 0;
- long multicastWakeLockTimeTotalMicros = 0;
- int multicastWakeLockCountTotal = 0;
final ArrayList<TimerEntry> timers = new ArrayList<>();
for (int iu = 0; iu < NU; iu++) {
final Uid u = uidStats.valueAt(iu);
- // First calculate wakelock statistics
final ArrayMap<String, ? extends BatteryStats.Uid.Wakelock> wakelocks
= u.getWakelockStats();
for (int iw=wakelocks.size()-1; iw>=0; iw--) {
@@ -4495,13 +4506,6 @@
}
}
}
-
- // Next calculate wifi multicast wakelock statistics
- final Timer mcTimer = u.getMulticastWakelockStats();
- if (mcTimer != null) {
- multicastWakeLockTimeTotalMicros += mcTimer.getTotalTimeLocked(rawRealtime, which);
- multicastWakeLockCountTotal += mcTimer.getCountLocked(which);
- }
}
final long mobileRxTotalBytes = getNetworkActivityBytes(NETWORK_MOBILE_RX_DATA, which);
@@ -4531,6 +4535,9 @@
pw.println(sb.toString());
}
+ final long multicastWakeLockTimeTotalMicros =
+ getWifiMulticastWakelockTime(rawRealtime, which);
+ final int multicastWakeLockCountTotal = getWifiMulticastWakelockCount(which);
if (multicastWakeLockTimeTotalMicros != 0) {
sb.setLength(0);
sb.append(prefix);
@@ -7051,6 +7058,28 @@
}
}
}
+
+ for (int procState = 0; procState < Uid.NUM_PROCESS_STATE; ++procState) {
+ final long[] timesMs = u.getCpuFreqTimes(which, procState);
+ if (timesMs != null && timesMs.length == cpuFreqs.length) {
+ long[] screenOffTimesMs = u.getScreenOffCpuFreqTimes(which, procState);
+ if (screenOffTimesMs == null) {
+ screenOffTimesMs = new long[timesMs.length];
+ }
+ final long procToken = proto.start(UidProto.Cpu.BY_PROCESS_STATE);
+ proto.write(UidProto.Cpu.ByProcessState.PROCESS_STATE, procState);
+ for (int ic = 0; ic < timesMs.length; ++ic) {
+ long cToken = proto.start(UidProto.Cpu.ByProcessState.BY_FREQUENCY);
+ proto.write(UidProto.Cpu.ByFrequency.FREQUENCY_INDEX, ic + 1);
+ proto.write(UidProto.Cpu.ByFrequency.TOTAL_DURATION_MS,
+ timesMs[ic]);
+ proto.write(UidProto.Cpu.ByFrequency.SCREEN_OFF_DURATION_MS,
+ screenOffTimesMs[ic]);
+ proto.end(cToken);
+ }
+ proto.end(procToken);
+ }
+ }
proto.end(cpuToken);
// Flashlight (FLASHLIGHT_DATA)
@@ -7535,22 +7564,9 @@
proto.end(mToken);
// Wifi multicast wakelock total stats (WIFI_MULTICAST_WAKELOCK_TOTAL_DATA)
- // Calculate multicast wakelock stats across all uids.
- long multicastWakeLockTimeTotalUs = 0;
- int multicastWakeLockCountTotal = 0;
-
- for (int iu = 0; iu < uidStats.size(); iu++) {
- final Uid u = uidStats.valueAt(iu);
-
- final Timer mcTimer = u.getMulticastWakelockStats();
-
- if (mcTimer != null) {
- multicastWakeLockTimeTotalUs +=
- mcTimer.getTotalTimeLocked(rawRealtimeUs, which);
- multicastWakeLockCountTotal += mcTimer.getCountLocked(which);
- }
- }
-
+ final long multicastWakeLockTimeTotalUs =
+ getWifiMulticastWakelockTime(rawRealtimeUs, which);
+ final int multicastWakeLockCountTotal = getWifiMulticastWakelockCount(which);
final long wmctToken = proto.start(SystemProto.WIFI_MULTICAST_WAKELOCK_TOTAL);
proto.write(SystemProto.WifiMulticastWakelockTotal.DURATION_MS,
multicastWakeLockTimeTotalUs / 1000);
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index 9833fe1..4c587a8 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -1115,12 +1115,14 @@
/** {@hide} */
public static Pair<String, Long> getPrimaryStoragePathAndSize() {
return Pair.create(null,
- FileUtils.roundStorageSize(Environment.getDataDirectory().getTotalSpace()));
+ FileUtils.roundStorageSize(Environment.getDataDirectory().getTotalSpace()
+ + Environment.getRootDirectory().getTotalSpace()));
}
/** {@hide} */
public long getPrimaryStorageSize() {
- return FileUtils.roundStorageSize(Environment.getDataDirectory().getTotalSpace());
+ return FileUtils.roundStorageSize(Environment.getDataDirectory().getTotalSpace()
+ + Environment.getRootDirectory().getTotalSpace());
}
/** {@hide} */
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index acac671..2f86514 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -5778,6 +5778,14 @@
"touch_exploration_granted_accessibility_services";
/**
+ * Uri of the slice that's presented on the keyguard.
+ * Defaults to a slice with the date and next alarm.
+ *
+ * @hide
+ */
+ public static final String KEYGUARD_SLICE_URI = "keyguard_slice_uri";
+
+ /**
* Whether to speak passwords while in accessibility mode.
*
* @deprecated The speaking of passwords is controlled by individual accessibility services.
diff --git a/core/java/android/service/euicc/EuiccService.java b/core/java/android/service/euicc/EuiccService.java
index df0842f..fb53007 100644
--- a/core/java/android/service/euicc/EuiccService.java
+++ b/core/java/android/service/euicc/EuiccService.java
@@ -23,6 +23,7 @@
import android.os.RemoteException;
import android.telephony.euicc.DownloadableSubscription;
import android.telephony.euicc.EuiccInfo;
+import android.telephony.euicc.EuiccManager.OtaStatus;
import android.util.ArraySet;
import java.util.concurrent.LinkedBlockingQueue;
@@ -203,6 +204,16 @@
public abstract String onGetEid(int slotId);
/**
+ * Return the status of OTA update.
+ *
+ * @param slotId ID of the SIM slot to use for the operation. This is currently not populated
+ * but is here to future-proof the APIs.
+ * @return The status of Euicc OTA update.
+ * @see android.telephony.euicc.EuiccManager#getOtaStatus
+ */
+ public abstract @OtaStatus int onGetOtaStatus(int slotId);
+
+ /**
* Populate {@link DownloadableSubscription} metadata for the given downloadable subscription.
*
* @param slotId ID of the SIM slot to use for the operation. This is currently not populated
@@ -385,6 +396,21 @@
}
@Override
+ public void getOtaStatus(int slotId, IGetOtaStatusCallback callback) {
+ mExecutor.execute(new Runnable() {
+ @Override
+ public void run() {
+ int status = EuiccService.this.onGetOtaStatus(slotId);
+ try {
+ callback.onSuccess(status);
+ } catch (RemoteException e) {
+ // Can't communicate with the phone process; ignore.
+ }
+ }
+ });
+ }
+
+ @Override
public void getDownloadableSubscriptionMetadata(int slotId,
DownloadableSubscription subscription,
boolean forceDeactivateSim,
diff --git a/core/java/android/service/euicc/IEuiccService.aidl b/core/java/android/service/euicc/IEuiccService.aidl
index e10dd8c..a24e5c3 100644
--- a/core/java/android/service/euicc/IEuiccService.aidl
+++ b/core/java/android/service/euicc/IEuiccService.aidl
@@ -24,6 +24,7 @@
import android.service.euicc.IGetEidCallback;
import android.service.euicc.IGetEuiccInfoCallback;
import android.service.euicc.IGetEuiccProfileInfoListCallback;
+import android.service.euicc.IGetOtaStatusCallback;
import android.service.euicc.IRetainSubscriptionsForFactoryResetCallback;
import android.service.euicc.ISwitchToSubscriptionCallback;
import android.service.euicc.IUpdateSubscriptionNicknameCallback;
@@ -37,6 +38,7 @@
void getDownloadableSubscriptionMetadata(int slotId, in DownloadableSubscription subscription,
boolean forceDeactivateSim, in IGetDownloadableSubscriptionMetadataCallback callback);
void getEid(int slotId, in IGetEidCallback callback);
+ void getOtaStatus(int slotId, in IGetOtaStatusCallback callback);
void getEuiccProfileInfoList(int slotId, in IGetEuiccProfileInfoListCallback callback);
void getDefaultDownloadableSubscriptionList(int slotId, boolean forceDeactivateSim,
in IGetDefaultDownloadableSubscriptionListCallback callback);
diff --git a/core/java/android/service/euicc/IGetOtaStatusCallback.aidl b/core/java/android/service/euicc/IGetOtaStatusCallback.aidl
new file mode 100644
index 0000000..f667888
--- /dev/null
+++ b/core/java/android/service/euicc/IGetOtaStatusCallback.aidl
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.euicc;
+
+/** @hide */
+oneway interface IGetOtaStatusCallback {
+ void onSuccess(int status);
+}
\ No newline at end of file
diff --git a/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java b/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java
index 0a54f3a..530937e 100644
--- a/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java
+++ b/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java
@@ -16,6 +16,21 @@
package android.util.apk;
+import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_DSA_WITH_SHA256;
+import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_ECDSA_WITH_SHA256;
+import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_ECDSA_WITH_SHA512;
+import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256;
+import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512;
+import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_RSA_PSS_WITH_SHA256;
+import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_RSA_PSS_WITH_SHA512;
+import static android.util.apk.ApkSigningBlockUtils.compareSignatureAlgorithm;
+import static android.util.apk.ApkSigningBlockUtils.getContentDigestAlgorithmJcaDigestAlgorithm;
+import static android.util.apk.ApkSigningBlockUtils.getLengthPrefixedSlice;
+import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmContentDigestAlgorithm;
+import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmJcaKeyAlgorithm;
+import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmJcaSignatureAlgorithm;
+import static android.util.apk.ApkSigningBlockUtils.readLengthPrefixedByteArray;
+
import android.util.ArrayMap;
import android.util.Pair;
@@ -23,56 +38,47 @@
import java.io.FileDescriptor;
import java.io.IOException;
import java.io.RandomAccessFile;
-import java.math.BigInteger;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.security.DigestException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
-import java.security.NoSuchProviderException;
-import java.security.Principal;
import java.security.PublicKey;
import java.security.Signature;
import java.security.SignatureException;
-import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
-import java.security.cert.CertificateExpiredException;
import java.security.cert.CertificateFactory;
-import java.security.cert.CertificateNotYetValidException;
import java.security.cert.X509Certificate;
import java.security.spec.AlgorithmParameterSpec;
import java.security.spec.InvalidKeySpecException;
-import java.security.spec.MGF1ParameterSpec;
-import java.security.spec.PSSParameterSpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Date;
import java.util.List;
import java.util.Map;
-import java.util.Set;
/**
* APK Signature Scheme v2 verifier.
*
+ * <p>APK Signature Scheme v2 is a whole-file signature scheme which aims to protect every single
+ * bit of the APK, as opposed to the JAR Signature Scheme which protects only the names and
+ * uncompressed contents of ZIP entries.
+ *
+ * @see <a href="https://source.android.com/security/apksigning/v2.html">APK Signature Scheme v2</a>
+ *
* @hide for internal use only.
*/
public class ApkSignatureSchemeV2Verifier {
/**
- * {@code .SF} file header section attribute indicating that the APK is signed not just with
- * JAR signature scheme but also with APK Signature Scheme v2 or newer. This attribute
- * facilitates v2 signature stripping detection.
- *
- * <p>The attribute contains a comma-separated set of signature scheme IDs.
+ * ID of this signature scheme as used in X-Android-APK-Signed header used in JAR signing.
*/
- public static final String SF_ATTRIBUTE_ANDROID_APK_SIGNED_NAME = "X-Android-APK-Signed";
public static final int SF_ATTRIBUTE_ANDROID_APK_SIGNED_ID = 2;
+ private static final int APK_SIGNATURE_SCHEME_V2_BLOCK_ID = 0x7109871a;
+
/**
* Returns {@code true} if the provided APK contains an APK Signature Scheme V2 signature.
*
@@ -103,7 +109,7 @@
/**
* Returns the certificates associated with each signer for the given APK without verification.
* This method is dangerous and should not be used, unless the caller is absolutely certain the
- * APK is trusted. Specifically, verification is only done for the APK Signature Scheme V2
+ * APK is trusted. Specifically, verification is only done for the APK Signature Scheme v2
* Block while gathering signer information. The APK contents are not verified.
*
* @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v2.
@@ -120,6 +126,7 @@
return verify(apk, verifyIntegrity);
}
}
+
/**
* Verifies APK Signature Scheme v2 signatures of the provided APK and returns the certificates
* associated with each signer.
@@ -144,30 +151,7 @@
*/
private static SignatureInfo findSignature(RandomAccessFile apk)
throws IOException, SignatureNotFoundException {
- // Find the ZIP End of Central Directory (EoCD) record.
- Pair<ByteBuffer, Long> eocdAndOffsetInFile = getEocd(apk);
- ByteBuffer eocd = eocdAndOffsetInFile.first;
- long eocdOffset = eocdAndOffsetInFile.second;
- if (ZipUtils.isZip64EndOfCentralDirectoryLocatorPresent(apk, eocdOffset)) {
- throw new SignatureNotFoundException("ZIP64 APK not supported");
- }
-
- // Find the APK Signing Block. The block immediately precedes the Central Directory.
- long centralDirOffset = getCentralDirOffset(eocd, eocdOffset);
- Pair<ByteBuffer, Long> apkSigningBlockAndOffsetInFile =
- findApkSigningBlock(apk, centralDirOffset);
- ByteBuffer apkSigningBlock = apkSigningBlockAndOffsetInFile.first;
- long apkSigningBlockOffset = apkSigningBlockAndOffsetInFile.second;
-
- // Find the APK Signature Scheme v2 Block inside the APK Signing Block.
- ByteBuffer apkSignatureSchemeV2Block = findApkSignatureSchemeV2Block(apkSigningBlock);
-
- return new SignatureInfo(
- apkSignatureSchemeV2Block,
- apkSigningBlockOffset,
- centralDirOffset,
- eocdOffset,
- eocd);
+ return ApkSigningBlockUtils.findSignature(apk, APK_SIGNATURE_SCHEME_V2_BLOCK_ID);
}
/**
@@ -218,7 +202,7 @@
}
if (doVerifyIntegrity) {
- verifyIntegrity(
+ ApkSigningBlockUtils.verifyIntegrity(
contentDigests,
apkFileDescriptor,
signatureInfo.apkSigningBlockOffset,
@@ -349,7 +333,8 @@
} catch (CertificateException e) {
throw new SecurityException("Failed to decode certificate #" + certificateCount, e);
}
- certificate = new VerbatimX509Certificate(certificate, encodedCert);
+ certificate = new VerbatimX509Certificate(
+ certificate, encodedCert);
certs.add(certificate);
}
@@ -363,235 +348,44 @@
"Public key mismatch between certificate and signature record");
}
+ ByteBuffer additionalAttrs = getLengthPrefixedSlice(signedData);
+ verifyAdditionalAttributes(additionalAttrs);
+
return certs.toArray(new X509Certificate[certs.size()]);
}
- private static void verifyIntegrity(
- Map<Integer, byte[]> expectedDigests,
- FileDescriptor apkFileDescriptor,
- long apkSigningBlockOffset,
- long centralDirOffset,
- long eocdOffset,
- ByteBuffer eocdBuf) throws SecurityException {
+ // Attribute to check whether a newer APK Signature Scheme signature was stripped
+ private static final int STRIPPING_PROTECTION_ATTR_ID = 0xbeeff00d;
- if (expectedDigests.isEmpty()) {
- throw new SecurityException("No digests provided");
- }
-
- // We need to verify the integrity of the following three sections of the file:
- // 1. Everything up to the start of the APK Signing Block.
- // 2. ZIP Central Directory.
- // 3. ZIP End of Central Directory (EoCD).
- // Each of these sections is represented as a separate DataSource instance below.
-
- // To handle large APKs, these sections are read in 1 MB chunks using memory-mapped I/O to
- // avoid wasting physical memory. In most APK verification scenarios, the contents of the
- // APK are already there in the OS's page cache and thus mmap does not use additional
- // physical memory.
- DataSource beforeApkSigningBlock =
- new MemoryMappedFileDataSource(apkFileDescriptor, 0, apkSigningBlockOffset);
- DataSource centralDir =
- new MemoryMappedFileDataSource(
- apkFileDescriptor, centralDirOffset, eocdOffset - centralDirOffset);
-
- // For the purposes of integrity verification, ZIP End of Central Directory's field Start of
- // Central Directory must be considered to point to the offset of the APK Signing Block.
- eocdBuf = eocdBuf.duplicate();
- eocdBuf.order(ByteOrder.LITTLE_ENDIAN);
- ZipUtils.setZipEocdCentralDirectoryOffset(eocdBuf, apkSigningBlockOffset);
- DataSource eocd = new ByteBufferDataSource(eocdBuf);
-
- int[] digestAlgorithms = new int[expectedDigests.size()];
- int digestAlgorithmCount = 0;
- for (int digestAlgorithm : expectedDigests.keySet()) {
- digestAlgorithms[digestAlgorithmCount] = digestAlgorithm;
- digestAlgorithmCount++;
- }
- byte[][] actualDigests;
- try {
- actualDigests =
- computeContentDigests(
- digestAlgorithms,
- new DataSource[] {beforeApkSigningBlock, centralDir, eocd});
- } catch (DigestException e) {
- throw new SecurityException("Failed to compute digest(s) of contents", e);
- }
- for (int i = 0; i < digestAlgorithms.length; i++) {
- int digestAlgorithm = digestAlgorithms[i];
- byte[] expectedDigest = expectedDigests.get(digestAlgorithm);
- byte[] actualDigest = actualDigests[i];
- if (!MessageDigest.isEqual(expectedDigest, actualDigest)) {
- throw new SecurityException(
- getContentDigestAlgorithmJcaDigestAlgorithm(digestAlgorithm)
- + " digest of contents did not verify");
+ private static void verifyAdditionalAttributes(ByteBuffer attrs)
+ throws SecurityException, IOException {
+ while (attrs.hasRemaining()) {
+ ByteBuffer attr = getLengthPrefixedSlice(attrs);
+ if (attr.remaining() < 4) {
+ throw new IOException("Remaining buffer too short to contain additional attribute "
+ + "ID. Remaining: " + attr.remaining());
}
- }
- }
-
- private static byte[][] computeContentDigests(
- int[] digestAlgorithms,
- DataSource[] contents) throws DigestException {
- // For each digest algorithm the result is computed as follows:
- // 1. Each segment of contents is split into consecutive chunks of 1 MB in size.
- // The final chunk will be shorter iff the length of segment is not a multiple of 1 MB.
- // No chunks are produced for empty (zero length) segments.
- // 2. The digest of each chunk is computed over the concatenation of byte 0xa5, the chunk's
- // length in bytes (uint32 little-endian) and the chunk's contents.
- // 3. The output digest is computed over the concatenation of the byte 0x5a, the number of
- // chunks (uint32 little-endian) and the concatenation of digests of chunks of all
- // segments in-order.
-
- long totalChunkCountLong = 0;
- for (DataSource input : contents) {
- totalChunkCountLong += getChunkCount(input.size());
- }
- if (totalChunkCountLong >= Integer.MAX_VALUE / 1024) {
- throw new DigestException("Too many chunks: " + totalChunkCountLong);
- }
- int totalChunkCount = (int) totalChunkCountLong;
-
- byte[][] digestsOfChunks = new byte[digestAlgorithms.length][];
- for (int i = 0; i < digestAlgorithms.length; i++) {
- int digestAlgorithm = digestAlgorithms[i];
- int digestOutputSizeBytes = getContentDigestAlgorithmOutputSizeBytes(digestAlgorithm);
- byte[] concatenationOfChunkCountAndChunkDigests =
- new byte[5 + totalChunkCount * digestOutputSizeBytes];
- concatenationOfChunkCountAndChunkDigests[0] = 0x5a;
- setUnsignedInt32LittleEndian(
- totalChunkCount,
- concatenationOfChunkCountAndChunkDigests,
- 1);
- digestsOfChunks[i] = concatenationOfChunkCountAndChunkDigests;
- }
-
- byte[] chunkContentPrefix = new byte[5];
- chunkContentPrefix[0] = (byte) 0xa5;
- int chunkIndex = 0;
- MessageDigest[] mds = new MessageDigest[digestAlgorithms.length];
- for (int i = 0; i < digestAlgorithms.length; i++) {
- String jcaAlgorithmName =
- getContentDigestAlgorithmJcaDigestAlgorithm(digestAlgorithms[i]);
- try {
- mds[i] = MessageDigest.getInstance(jcaAlgorithmName);
- } catch (NoSuchAlgorithmException e) {
- throw new RuntimeException(jcaAlgorithmName + " digest not supported", e);
- }
- }
- // TODO: Compute digests of chunks in parallel when beneficial. This requires some research
- // into how to parallelize (if at all) based on the capabilities of the hardware on which
- // this code is running and based on the size of input.
- DataDigester digester = new MultipleDigestDataDigester(mds);
- int dataSourceIndex = 0;
- for (DataSource input : contents) {
- long inputOffset = 0;
- long inputRemaining = input.size();
- while (inputRemaining > 0) {
- int chunkSize = (int) Math.min(inputRemaining, CHUNK_SIZE_BYTES);
- setUnsignedInt32LittleEndian(chunkSize, chunkContentPrefix, 1);
- for (int i = 0; i < mds.length; i++) {
- mds[i].update(chunkContentPrefix);
- }
- try {
- input.feedIntoDataDigester(digester, inputOffset, chunkSize);
- } catch (IOException e) {
- throw new DigestException(
- "Failed to digest chunk #" + chunkIndex + " of section #"
- + dataSourceIndex,
- e);
- }
- for (int i = 0; i < digestAlgorithms.length; i++) {
- int digestAlgorithm = digestAlgorithms[i];
- byte[] concatenationOfChunkCountAndChunkDigests = digestsOfChunks[i];
- int expectedDigestSizeBytes =
- getContentDigestAlgorithmOutputSizeBytes(digestAlgorithm);
- MessageDigest md = mds[i];
- int actualDigestSizeBytes =
- md.digest(
- concatenationOfChunkCountAndChunkDigests,
- 5 + chunkIndex * expectedDigestSizeBytes,
- expectedDigestSizeBytes);
- if (actualDigestSizeBytes != expectedDigestSizeBytes) {
- throw new RuntimeException(
- "Unexpected output size of " + md.getAlgorithm() + " digest: "
- + actualDigestSizeBytes);
+ int id = attr.getInt();
+ switch (id) {
+ case STRIPPING_PROTECTION_ATTR_ID:
+ if (attr.remaining() < 4) {
+ throw new IOException("V2 Signature Scheme Stripping Protection Attribute "
+ + " value too small. Expected 4 bytes, but found "
+ + attr.remaining());
}
- }
- inputOffset += chunkSize;
- inputRemaining -= chunkSize;
- chunkIndex++;
+ int vers = attr.getInt();
+ if (vers == ApkSignatureSchemeV3Verifier.SF_ATTRIBUTE_ANDROID_APK_SIGNED_ID) {
+ throw new SecurityException("V2 signature indicates APK is signed using APK"
+ + " Signature Scheme v3, but none was found. Signature stripped?");
+ }
+ break;
+ default:
+ // not the droid we're looking for, move along, move along.
+ break;
}
- dataSourceIndex++;
}
-
- byte[][] result = new byte[digestAlgorithms.length][];
- for (int i = 0; i < digestAlgorithms.length; i++) {
- int digestAlgorithm = digestAlgorithms[i];
- byte[] input = digestsOfChunks[i];
- String jcaAlgorithmName = getContentDigestAlgorithmJcaDigestAlgorithm(digestAlgorithm);
- MessageDigest md;
- try {
- md = MessageDigest.getInstance(jcaAlgorithmName);
- } catch (NoSuchAlgorithmException e) {
- throw new RuntimeException(jcaAlgorithmName + " digest not supported", e);
- }
- byte[] output = md.digest(input);
- result[i] = output;
- }
- return result;
+ return;
}
-
- /**
- * Returns the ZIP End of Central Directory (EoCD) and its offset in the file.
- *
- * @throws IOException if an I/O error occurs while reading the file.
- * @throws SignatureNotFoundException if the EoCD could not be found.
- */
- private static Pair<ByteBuffer, Long> getEocd(RandomAccessFile apk)
- throws IOException, SignatureNotFoundException {
- Pair<ByteBuffer, Long> eocdAndOffsetInFile =
- ZipUtils.findZipEndOfCentralDirectoryRecord(apk);
- if (eocdAndOffsetInFile == null) {
- throw new SignatureNotFoundException(
- "Not an APK file: ZIP End of Central Directory record not found");
- }
- return eocdAndOffsetInFile;
- }
-
- private static long getCentralDirOffset(ByteBuffer eocd, long eocdOffset)
- throws SignatureNotFoundException {
- // Look up the offset of ZIP Central Directory.
- long centralDirOffset = ZipUtils.getZipEocdCentralDirectoryOffset(eocd);
- if (centralDirOffset > eocdOffset) {
- throw new SignatureNotFoundException(
- "ZIP Central Directory offset out of range: " + centralDirOffset
- + ". ZIP End of Central Directory offset: " + eocdOffset);
- }
- long centralDirSize = ZipUtils.getZipEocdCentralDirectorySizeBytes(eocd);
- if (centralDirOffset + centralDirSize != eocdOffset) {
- throw new SignatureNotFoundException(
- "ZIP Central Directory is not immediately followed by End of Central"
- + " Directory");
- }
- return centralDirOffset;
- }
-
- private static final long getChunkCount(long inputSizeBytes) {
- return (inputSizeBytes + CHUNK_SIZE_BYTES - 1) / CHUNK_SIZE_BYTES;
- }
-
- private static final int CHUNK_SIZE_BYTES = 1024 * 1024;
-
- private static final int SIGNATURE_RSA_PSS_WITH_SHA256 = 0x0101;
- private static final int SIGNATURE_RSA_PSS_WITH_SHA512 = 0x0102;
- private static final int SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256 = 0x0103;
- private static final int SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512 = 0x0104;
- private static final int SIGNATURE_ECDSA_WITH_SHA256 = 0x0201;
- private static final int SIGNATURE_ECDSA_WITH_SHA512 = 0x0202;
- private static final int SIGNATURE_DSA_WITH_SHA256 = 0x0301;
-
- private static final int CONTENT_DIGEST_CHUNKED_SHA256 = 1;
- private static final int CONTENT_DIGEST_CHUNKED_SHA512 = 2;
-
private static boolean isSupportedSignatureAlgorithm(int sigAlgorithm) {
switch (sigAlgorithm) {
case SIGNATURE_RSA_PSS_WITH_SHA256:
@@ -606,519 +400,4 @@
return false;
}
}
-
- private static int compareSignatureAlgorithm(int sigAlgorithm1, int sigAlgorithm2) {
- int digestAlgorithm1 = getSignatureAlgorithmContentDigestAlgorithm(sigAlgorithm1);
- int digestAlgorithm2 = getSignatureAlgorithmContentDigestAlgorithm(sigAlgorithm2);
- return compareContentDigestAlgorithm(digestAlgorithm1, digestAlgorithm2);
- }
-
- private static int compareContentDigestAlgorithm(int digestAlgorithm1, int digestAlgorithm2) {
- switch (digestAlgorithm1) {
- case CONTENT_DIGEST_CHUNKED_SHA256:
- switch (digestAlgorithm2) {
- case CONTENT_DIGEST_CHUNKED_SHA256:
- return 0;
- case CONTENT_DIGEST_CHUNKED_SHA512:
- return -1;
- default:
- throw new IllegalArgumentException(
- "Unknown digestAlgorithm2: " + digestAlgorithm2);
- }
- case CONTENT_DIGEST_CHUNKED_SHA512:
- switch (digestAlgorithm2) {
- case CONTENT_DIGEST_CHUNKED_SHA256:
- return 1;
- case CONTENT_DIGEST_CHUNKED_SHA512:
- return 0;
- default:
- throw new IllegalArgumentException(
- "Unknown digestAlgorithm2: " + digestAlgorithm2);
- }
- default:
- throw new IllegalArgumentException("Unknown digestAlgorithm1: " + digestAlgorithm1);
- }
- }
-
- private static int getSignatureAlgorithmContentDigestAlgorithm(int sigAlgorithm) {
- switch (sigAlgorithm) {
- case SIGNATURE_RSA_PSS_WITH_SHA256:
- case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256:
- case SIGNATURE_ECDSA_WITH_SHA256:
- case SIGNATURE_DSA_WITH_SHA256:
- return CONTENT_DIGEST_CHUNKED_SHA256;
- case SIGNATURE_RSA_PSS_WITH_SHA512:
- case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512:
- case SIGNATURE_ECDSA_WITH_SHA512:
- return CONTENT_DIGEST_CHUNKED_SHA512;
- default:
- throw new IllegalArgumentException(
- "Unknown signature algorithm: 0x"
- + Long.toHexString(sigAlgorithm & 0xffffffff));
- }
- }
-
- private static String getContentDigestAlgorithmJcaDigestAlgorithm(int digestAlgorithm) {
- switch (digestAlgorithm) {
- case CONTENT_DIGEST_CHUNKED_SHA256:
- return "SHA-256";
- case CONTENT_DIGEST_CHUNKED_SHA512:
- return "SHA-512";
- default:
- throw new IllegalArgumentException(
- "Unknown content digest algorthm: " + digestAlgorithm);
- }
- }
-
- private static int getContentDigestAlgorithmOutputSizeBytes(int digestAlgorithm) {
- switch (digestAlgorithm) {
- case CONTENT_DIGEST_CHUNKED_SHA256:
- return 256 / 8;
- case CONTENT_DIGEST_CHUNKED_SHA512:
- return 512 / 8;
- default:
- throw new IllegalArgumentException(
- "Unknown content digest algorthm: " + digestAlgorithm);
- }
- }
-
- private static String getSignatureAlgorithmJcaKeyAlgorithm(int sigAlgorithm) {
- switch (sigAlgorithm) {
- case SIGNATURE_RSA_PSS_WITH_SHA256:
- case SIGNATURE_RSA_PSS_WITH_SHA512:
- case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256:
- case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512:
- return "RSA";
- case SIGNATURE_ECDSA_WITH_SHA256:
- case SIGNATURE_ECDSA_WITH_SHA512:
- return "EC";
- case SIGNATURE_DSA_WITH_SHA256:
- return "DSA";
- default:
- throw new IllegalArgumentException(
- "Unknown signature algorithm: 0x"
- + Long.toHexString(sigAlgorithm & 0xffffffff));
- }
- }
-
- private static Pair<String, ? extends AlgorithmParameterSpec>
- getSignatureAlgorithmJcaSignatureAlgorithm(int sigAlgorithm) {
- switch (sigAlgorithm) {
- case SIGNATURE_RSA_PSS_WITH_SHA256:
- return Pair.create(
- "SHA256withRSA/PSS",
- new PSSParameterSpec(
- "SHA-256", "MGF1", MGF1ParameterSpec.SHA256, 256 / 8, 1));
- case SIGNATURE_RSA_PSS_WITH_SHA512:
- return Pair.create(
- "SHA512withRSA/PSS",
- new PSSParameterSpec(
- "SHA-512", "MGF1", MGF1ParameterSpec.SHA512, 512 / 8, 1));
- case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256:
- return Pair.create("SHA256withRSA", null);
- case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512:
- return Pair.create("SHA512withRSA", null);
- case SIGNATURE_ECDSA_WITH_SHA256:
- return Pair.create("SHA256withECDSA", null);
- case SIGNATURE_ECDSA_WITH_SHA512:
- return Pair.create("SHA512withECDSA", null);
- case SIGNATURE_DSA_WITH_SHA256:
- return Pair.create("SHA256withDSA", null);
- default:
- throw new IllegalArgumentException(
- "Unknown signature algorithm: 0x"
- + Long.toHexString(sigAlgorithm & 0xffffffff));
- }
- }
-
- /**
- * Returns new byte buffer whose content is a shared subsequence of this buffer's content
- * between the specified start (inclusive) and end (exclusive) positions. As opposed to
- * {@link ByteBuffer#slice()}, the returned buffer's byte order is the same as the source
- * buffer's byte order.
- */
- private static ByteBuffer sliceFromTo(ByteBuffer source, int start, int end) {
- if (start < 0) {
- throw new IllegalArgumentException("start: " + start);
- }
- if (end < start) {
- throw new IllegalArgumentException("end < start: " + end + " < " + start);
- }
- int capacity = source.capacity();
- if (end > source.capacity()) {
- throw new IllegalArgumentException("end > capacity: " + end + " > " + capacity);
- }
- int originalLimit = source.limit();
- int originalPosition = source.position();
- try {
- source.position(0);
- source.limit(end);
- source.position(start);
- ByteBuffer result = source.slice();
- result.order(source.order());
- return result;
- } finally {
- source.position(0);
- source.limit(originalLimit);
- source.position(originalPosition);
- }
- }
-
- /**
- * Relative <em>get</em> method for reading {@code size} number of bytes from the current
- * position of this buffer.
- *
- * <p>This method reads the next {@code size} bytes at this buffer's current position,
- * returning them as a {@code ByteBuffer} with start set to 0, limit and capacity set to
- * {@code size}, byte order set to this buffer's byte order; and then increments the position by
- * {@code size}.
- */
- private static ByteBuffer getByteBuffer(ByteBuffer source, int size)
- throws BufferUnderflowException {
- if (size < 0) {
- throw new IllegalArgumentException("size: " + size);
- }
- int originalLimit = source.limit();
- int position = source.position();
- int limit = position + size;
- if ((limit < position) || (limit > originalLimit)) {
- throw new BufferUnderflowException();
- }
- source.limit(limit);
- try {
- ByteBuffer result = source.slice();
- result.order(source.order());
- source.position(limit);
- return result;
- } finally {
- source.limit(originalLimit);
- }
- }
-
- private static ByteBuffer getLengthPrefixedSlice(ByteBuffer source) throws IOException {
- if (source.remaining() < 4) {
- throw new IOException(
- "Remaining buffer too short to contain length of length-prefixed field."
- + " Remaining: " + source.remaining());
- }
- int len = source.getInt();
- if (len < 0) {
- throw new IllegalArgumentException("Negative length");
- } else if (len > source.remaining()) {
- throw new IOException("Length-prefixed field longer than remaining buffer."
- + " Field length: " + len + ", remaining: " + source.remaining());
- }
- return getByteBuffer(source, len);
- }
-
- private static byte[] readLengthPrefixedByteArray(ByteBuffer buf) throws IOException {
- int len = buf.getInt();
- if (len < 0) {
- throw new IOException("Negative length");
- } else if (len > buf.remaining()) {
- throw new IOException("Underflow while reading length-prefixed value. Length: " + len
- + ", available: " + buf.remaining());
- }
- byte[] result = new byte[len];
- buf.get(result);
- return result;
- }
-
- private static void setUnsignedInt32LittleEndian(int value, byte[] result, int offset) {
- result[offset] = (byte) (value & 0xff);
- result[offset + 1] = (byte) ((value >>> 8) & 0xff);
- result[offset + 2] = (byte) ((value >>> 16) & 0xff);
- result[offset + 3] = (byte) ((value >>> 24) & 0xff);
- }
-
- private static final long APK_SIG_BLOCK_MAGIC_HI = 0x3234206b636f6c42L;
- private static final long APK_SIG_BLOCK_MAGIC_LO = 0x20676953204b5041L;
- private static final int APK_SIG_BLOCK_MIN_SIZE = 32;
-
- private static final int APK_SIGNATURE_SCHEME_V2_BLOCK_ID = 0x7109871a;
-
- private static Pair<ByteBuffer, Long> findApkSigningBlock(
- RandomAccessFile apk, long centralDirOffset)
- throws IOException, SignatureNotFoundException {
- // FORMAT:
- // OFFSET DATA TYPE DESCRIPTION
- // * @+0 bytes uint64: size in bytes (excluding this field)
- // * @+8 bytes payload
- // * @-24 bytes uint64: size in bytes (same as the one above)
- // * @-16 bytes uint128: magic
-
- if (centralDirOffset < APK_SIG_BLOCK_MIN_SIZE) {
- throw new SignatureNotFoundException(
- "APK too small for APK Signing Block. ZIP Central Directory offset: "
- + centralDirOffset);
- }
- // Read the magic and offset in file from the footer section of the block:
- // * uint64: size of block
- // * 16 bytes: magic
- ByteBuffer footer = ByteBuffer.allocate(24);
- footer.order(ByteOrder.LITTLE_ENDIAN);
- apk.seek(centralDirOffset - footer.capacity());
- apk.readFully(footer.array(), footer.arrayOffset(), footer.capacity());
- if ((footer.getLong(8) != APK_SIG_BLOCK_MAGIC_LO)
- || (footer.getLong(16) != APK_SIG_BLOCK_MAGIC_HI)) {
- throw new SignatureNotFoundException(
- "No APK Signing Block before ZIP Central Directory");
- }
- // Read and compare size fields
- long apkSigBlockSizeInFooter = footer.getLong(0);
- if ((apkSigBlockSizeInFooter < footer.capacity())
- || (apkSigBlockSizeInFooter > Integer.MAX_VALUE - 8)) {
- throw new SignatureNotFoundException(
- "APK Signing Block size out of range: " + apkSigBlockSizeInFooter);
- }
- int totalSize = (int) (apkSigBlockSizeInFooter + 8);
- long apkSigBlockOffset = centralDirOffset - totalSize;
- if (apkSigBlockOffset < 0) {
- throw new SignatureNotFoundException(
- "APK Signing Block offset out of range: " + apkSigBlockOffset);
- }
- ByteBuffer apkSigBlock = ByteBuffer.allocate(totalSize);
- apkSigBlock.order(ByteOrder.LITTLE_ENDIAN);
- apk.seek(apkSigBlockOffset);
- apk.readFully(apkSigBlock.array(), apkSigBlock.arrayOffset(), apkSigBlock.capacity());
- long apkSigBlockSizeInHeader = apkSigBlock.getLong(0);
- if (apkSigBlockSizeInHeader != apkSigBlockSizeInFooter) {
- throw new SignatureNotFoundException(
- "APK Signing Block sizes in header and footer do not match: "
- + apkSigBlockSizeInHeader + " vs " + apkSigBlockSizeInFooter);
- }
- return Pair.create(apkSigBlock, apkSigBlockOffset);
- }
-
- private static ByteBuffer findApkSignatureSchemeV2Block(ByteBuffer apkSigningBlock)
- throws SignatureNotFoundException {
- checkByteOrderLittleEndian(apkSigningBlock);
- // FORMAT:
- // OFFSET DATA TYPE DESCRIPTION
- // * @+0 bytes uint64: size in bytes (excluding this field)
- // * @+8 bytes pairs
- // * @-24 bytes uint64: size in bytes (same as the one above)
- // * @-16 bytes uint128: magic
- ByteBuffer pairs = sliceFromTo(apkSigningBlock, 8, apkSigningBlock.capacity() - 24);
-
- int entryCount = 0;
- while (pairs.hasRemaining()) {
- entryCount++;
- if (pairs.remaining() < 8) {
- throw new SignatureNotFoundException(
- "Insufficient data to read size of APK Signing Block entry #" + entryCount);
- }
- long lenLong = pairs.getLong();
- if ((lenLong < 4) || (lenLong > Integer.MAX_VALUE)) {
- throw new SignatureNotFoundException(
- "APK Signing Block entry #" + entryCount
- + " size out of range: " + lenLong);
- }
- int len = (int) lenLong;
- int nextEntryPos = pairs.position() + len;
- if (len > pairs.remaining()) {
- throw new SignatureNotFoundException(
- "APK Signing Block entry #" + entryCount + " size out of range: " + len
- + ", available: " + pairs.remaining());
- }
- int id = pairs.getInt();
- if (id == APK_SIGNATURE_SCHEME_V2_BLOCK_ID) {
- return getByteBuffer(pairs, len - 4);
- }
- pairs.position(nextEntryPos);
- }
-
- throw new SignatureNotFoundException(
- "No APK Signature Scheme v2 block in APK Signing Block");
- }
-
- private static void checkByteOrderLittleEndian(ByteBuffer buffer) {
- if (buffer.order() != ByteOrder.LITTLE_ENDIAN) {
- throw new IllegalArgumentException("ByteBuffer byte order must be little endian");
- }
- }
-
- /**
- * {@link DataDigester} that updates multiple {@link MessageDigest}s whenever data is feeded.
- */
- private static class MultipleDigestDataDigester implements DataDigester {
- private final MessageDigest[] mMds;
-
- MultipleDigestDataDigester(MessageDigest[] mds) {
- mMds = mds;
- }
-
- @Override
- public void consume(ByteBuffer buffer) {
- buffer = buffer.slice();
- for (MessageDigest md : mMds) {
- buffer.position(0);
- md.update(buffer);
- }
- }
-
- @Override
- public void finish() {}
- }
-
- /**
- * For legacy reasons we need to return exactly the original encoded certificate bytes, instead
- * of letting the underlying implementation have a shot at re-encoding the data.
- */
- private static class VerbatimX509Certificate extends WrappedX509Certificate {
- private byte[] encodedVerbatim;
-
- public VerbatimX509Certificate(X509Certificate wrapped, byte[] encodedVerbatim) {
- super(wrapped);
- this.encodedVerbatim = encodedVerbatim;
- }
-
- @Override
- public byte[] getEncoded() throws CertificateEncodingException {
- return encodedVerbatim;
- }
- }
-
- private static class WrappedX509Certificate extends X509Certificate {
- private final X509Certificate wrapped;
-
- public WrappedX509Certificate(X509Certificate wrapped) {
- this.wrapped = wrapped;
- }
-
- @Override
- public Set<String> getCriticalExtensionOIDs() {
- return wrapped.getCriticalExtensionOIDs();
- }
-
- @Override
- public byte[] getExtensionValue(String oid) {
- return wrapped.getExtensionValue(oid);
- }
-
- @Override
- public Set<String> getNonCriticalExtensionOIDs() {
- return wrapped.getNonCriticalExtensionOIDs();
- }
-
- @Override
- public boolean hasUnsupportedCriticalExtension() {
- return wrapped.hasUnsupportedCriticalExtension();
- }
-
- @Override
- public void checkValidity()
- throws CertificateExpiredException, CertificateNotYetValidException {
- wrapped.checkValidity();
- }
-
- @Override
- public void checkValidity(Date date)
- throws CertificateExpiredException, CertificateNotYetValidException {
- wrapped.checkValidity(date);
- }
-
- @Override
- public int getVersion() {
- return wrapped.getVersion();
- }
-
- @Override
- public BigInteger getSerialNumber() {
- return wrapped.getSerialNumber();
- }
-
- @Override
- public Principal getIssuerDN() {
- return wrapped.getIssuerDN();
- }
-
- @Override
- public Principal getSubjectDN() {
- return wrapped.getSubjectDN();
- }
-
- @Override
- public Date getNotBefore() {
- return wrapped.getNotBefore();
- }
-
- @Override
- public Date getNotAfter() {
- return wrapped.getNotAfter();
- }
-
- @Override
- public byte[] getTBSCertificate() throws CertificateEncodingException {
- return wrapped.getTBSCertificate();
- }
-
- @Override
- public byte[] getSignature() {
- return wrapped.getSignature();
- }
-
- @Override
- public String getSigAlgName() {
- return wrapped.getSigAlgName();
- }
-
- @Override
- public String getSigAlgOID() {
- return wrapped.getSigAlgOID();
- }
-
- @Override
- public byte[] getSigAlgParams() {
- return wrapped.getSigAlgParams();
- }
-
- @Override
- public boolean[] getIssuerUniqueID() {
- return wrapped.getIssuerUniqueID();
- }
-
- @Override
- public boolean[] getSubjectUniqueID() {
- return wrapped.getSubjectUniqueID();
- }
-
- @Override
- public boolean[] getKeyUsage() {
- return wrapped.getKeyUsage();
- }
-
- @Override
- public int getBasicConstraints() {
- return wrapped.getBasicConstraints();
- }
-
- @Override
- public byte[] getEncoded() throws CertificateEncodingException {
- return wrapped.getEncoded();
- }
-
- @Override
- public void verify(PublicKey key) throws CertificateException, NoSuchAlgorithmException,
- InvalidKeyException, NoSuchProviderException, SignatureException {
- wrapped.verify(key);
- }
-
- @Override
- public void verify(PublicKey key, String sigProvider)
- throws CertificateException, NoSuchAlgorithmException, InvalidKeyException,
- NoSuchProviderException, SignatureException {
- wrapped.verify(key, sigProvider);
- }
-
- @Override
- public String toString() {
- return wrapped.toString();
- }
-
- @Override
- public PublicKey getPublicKey() {
- return wrapped.getPublicKey();
- }
- }
}
diff --git a/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java b/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java
new file mode 100644
index 0000000..e43dee3
--- /dev/null
+++ b/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java
@@ -0,0 +1,558 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.util.apk;
+
+import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_DSA_WITH_SHA256;
+import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_ECDSA_WITH_SHA256;
+import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_ECDSA_WITH_SHA512;
+import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256;
+import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512;
+import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_RSA_PSS_WITH_SHA256;
+import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_RSA_PSS_WITH_SHA512;
+import static android.util.apk.ApkSigningBlockUtils.compareSignatureAlgorithm;
+import static android.util.apk.ApkSigningBlockUtils.getContentDigestAlgorithmJcaDigestAlgorithm;
+import static android.util.apk.ApkSigningBlockUtils.getLengthPrefixedSlice;
+import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmContentDigestAlgorithm;
+import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmJcaKeyAlgorithm;
+import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmJcaSignatureAlgorithm;
+import static android.util.apk.ApkSigningBlockUtils.readLengthPrefixedByteArray;
+
+import android.os.Build;
+import android.util.ArrayMap;
+import android.util.Pair;
+
+import java.io.ByteArrayInputStream;
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.KeyFactory;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.PublicKey;
+import java.security.Signature;
+import java.security.SignatureException;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.security.spec.AlgorithmParameterSpec;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.X509EncodedKeySpec;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * APK Signature Scheme v3 verifier.
+ *
+ * @hide for internal use only.
+ */
+public class ApkSignatureSchemeV3Verifier {
+
+ /**
+ * ID of this signature scheme as used in X-Android-APK-Signed header used in JAR signing.
+ */
+ public static final int SF_ATTRIBUTE_ANDROID_APK_SIGNED_ID = 3;
+
+ private static final int APK_SIGNATURE_SCHEME_V3_BLOCK_ID = 0xf05368c0;
+
+ /**
+ * Returns {@code true} if the provided APK contains an APK Signature Scheme V3 signature.
+ *
+ * <p><b>NOTE: This method does not verify the signature.</b>
+ */
+ public static boolean hasSignature(String apkFile) throws IOException {
+ try (RandomAccessFile apk = new RandomAccessFile(apkFile, "r")) {
+ findSignature(apk);
+ return true;
+ } catch (SignatureNotFoundException e) {
+ return false;
+ }
+ }
+
+ /**
+ * Verifies APK Signature Scheme v3 signatures of the provided APK and returns the certificates
+ * associated with each signer.
+ *
+ * @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v3.
+ * @throws SecurityException if the APK Signature Scheme v3 signature of this APK does not
+ * verify.
+ * @throws IOException if an I/O error occurs while reading the APK file.
+ */
+ public static VerifiedSigner verify(String apkFile)
+ throws SignatureNotFoundException, SecurityException, IOException {
+ return verify(apkFile, true);
+ }
+
+ /**
+ * Returns the certificates associated with each signer for the given APK without verification.
+ * This method is dangerous and should not be used, unless the caller is absolutely certain the
+ * APK is trusted. Specifically, verification is only done for the APK Signature Scheme v3
+ * Block while gathering signer information. The APK contents are not verified.
+ *
+ * @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v3.
+ * @throws IOException if an I/O error occurs while reading the APK file.
+ */
+ public static VerifiedSigner plsCertsNoVerifyOnlyCerts(String apkFile)
+ throws SignatureNotFoundException, SecurityException, IOException {
+ return verify(apkFile, false);
+ }
+
+ private static VerifiedSigner verify(String apkFile, boolean verifyIntegrity)
+ throws SignatureNotFoundException, SecurityException, IOException {
+ try (RandomAccessFile apk = new RandomAccessFile(apkFile, "r")) {
+ return verify(apk, verifyIntegrity);
+ }
+ }
+
+ /**
+ * Verifies APK Signature Scheme v3 signatures of the provided APK and returns the certificates
+ * associated with each signer.
+ *
+ * @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v3.
+ * @throws SecurityException if an APK Signature Scheme v3 signature of this APK does not
+ * verify.
+ * @throws IOException if an I/O error occurs while reading the APK file.
+ */
+ private static VerifiedSigner verify(RandomAccessFile apk, boolean verifyIntegrity)
+ throws SignatureNotFoundException, SecurityException, IOException {
+ SignatureInfo signatureInfo = findSignature(apk);
+ return verify(apk.getFD(), signatureInfo, verifyIntegrity);
+ }
+
+ /**
+ * Returns the APK Signature Scheme v3 block contained in the provided APK file and the
+ * additional information relevant for verifying the block against the file.
+ *
+ * @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v3.
+ * @throws IOException if an I/O error occurs while reading the APK file.
+ */
+ private static SignatureInfo findSignature(RandomAccessFile apk)
+ throws IOException, SignatureNotFoundException {
+ return ApkSigningBlockUtils.findSignature(apk, APK_SIGNATURE_SCHEME_V3_BLOCK_ID);
+ }
+
+ /**
+ * Verifies the contents of the provided APK file against the provided APK Signature Scheme v3
+ * Block.
+ *
+ * @param signatureInfo APK Signature Scheme v3 Block and information relevant for verifying it
+ * against the APK file.
+ */
+ private static VerifiedSigner verify(
+ FileDescriptor apkFileDescriptor,
+ SignatureInfo signatureInfo,
+ boolean doVerifyIntegrity) throws SecurityException {
+ int signerCount = 0;
+ Map<Integer, byte[]> contentDigests = new ArrayMap<>();
+ VerifiedSigner result = null;
+ CertificateFactory certFactory;
+ try {
+ certFactory = CertificateFactory.getInstance("X.509");
+ } catch (CertificateException e) {
+ throw new RuntimeException("Failed to obtain X.509 CertificateFactory", e);
+ }
+ ByteBuffer signers;
+ try {
+ signers = getLengthPrefixedSlice(signatureInfo.signatureBlock);
+ } catch (IOException e) {
+ throw new SecurityException("Failed to read list of signers", e);
+ }
+ while (signers.hasRemaining()) {
+ try {
+ ByteBuffer signer = getLengthPrefixedSlice(signers);
+ result = verifySigner(signer, contentDigests, certFactory);
+ signerCount++;
+ } catch (PlatformNotSupportedException e) {
+ // this signer is for a different platform, ignore it.
+ continue;
+ } catch (IOException | BufferUnderflowException | SecurityException e) {
+ throw new SecurityException(
+ "Failed to parse/verify signer #" + signerCount + " block",
+ e);
+ }
+ }
+
+ if (signerCount < 1 || result == null) {
+ throw new SecurityException("No signers found");
+ }
+
+ if (signerCount != 1) {
+ throw new SecurityException("APK Signature Scheme V3 only supports one signer: "
+ + "multiple signers found.");
+ }
+
+ if (contentDigests.isEmpty()) {
+ throw new SecurityException("No content digests found");
+ }
+
+ if (doVerifyIntegrity) {
+ ApkSigningBlockUtils.verifyIntegrity(
+ contentDigests,
+ apkFileDescriptor,
+ signatureInfo.apkSigningBlockOffset,
+ signatureInfo.centralDirOffset,
+ signatureInfo.eocdOffset,
+ signatureInfo.eocd);
+ }
+
+ return result;
+ }
+
+ private static VerifiedSigner verifySigner(
+ ByteBuffer signerBlock,
+ Map<Integer, byte[]> contentDigests,
+ CertificateFactory certFactory)
+ throws SecurityException, IOException, PlatformNotSupportedException {
+ ByteBuffer signedData = getLengthPrefixedSlice(signerBlock);
+ int minSdkVersion = signerBlock.getInt();
+ int maxSdkVersion = signerBlock.getInt();
+
+ if (Build.VERSION.SDK_INT < minSdkVersion || Build.VERSION.SDK_INT > maxSdkVersion) {
+ // this signature isn't meant to be used with this platform, skip it.
+ throw new PlatformNotSupportedException(
+ "Signer not supported by this platform "
+ + "version. This platform: " + Build.VERSION.SDK_INT
+ + ", signer minSdkVersion: " + minSdkVersion
+ + ", maxSdkVersion: " + maxSdkVersion);
+ }
+
+ ByteBuffer signatures = getLengthPrefixedSlice(signerBlock);
+ byte[] publicKeyBytes = readLengthPrefixedByteArray(signerBlock);
+
+ int signatureCount = 0;
+ int bestSigAlgorithm = -1;
+ byte[] bestSigAlgorithmSignatureBytes = null;
+ List<Integer> signaturesSigAlgorithms = new ArrayList<>();
+ while (signatures.hasRemaining()) {
+ signatureCount++;
+ try {
+ ByteBuffer signature = getLengthPrefixedSlice(signatures);
+ if (signature.remaining() < 8) {
+ throw new SecurityException("Signature record too short");
+ }
+ int sigAlgorithm = signature.getInt();
+ signaturesSigAlgorithms.add(sigAlgorithm);
+ if (!isSupportedSignatureAlgorithm(sigAlgorithm)) {
+ continue;
+ }
+ if ((bestSigAlgorithm == -1)
+ || (compareSignatureAlgorithm(sigAlgorithm, bestSigAlgorithm) > 0)) {
+ bestSigAlgorithm = sigAlgorithm;
+ bestSigAlgorithmSignatureBytes = readLengthPrefixedByteArray(signature);
+ }
+ } catch (IOException | BufferUnderflowException e) {
+ throw new SecurityException(
+ "Failed to parse signature record #" + signatureCount,
+ e);
+ }
+ }
+ if (bestSigAlgorithm == -1) {
+ if (signatureCount == 0) {
+ throw new SecurityException("No signatures found");
+ } else {
+ throw new SecurityException("No supported signatures found");
+ }
+ }
+
+ String keyAlgorithm = getSignatureAlgorithmJcaKeyAlgorithm(bestSigAlgorithm);
+ Pair<String, ? extends AlgorithmParameterSpec> signatureAlgorithmParams =
+ getSignatureAlgorithmJcaSignatureAlgorithm(bestSigAlgorithm);
+ String jcaSignatureAlgorithm = signatureAlgorithmParams.first;
+ AlgorithmParameterSpec jcaSignatureAlgorithmParams = signatureAlgorithmParams.second;
+ boolean sigVerified;
+ try {
+ PublicKey publicKey =
+ KeyFactory.getInstance(keyAlgorithm)
+ .generatePublic(new X509EncodedKeySpec(publicKeyBytes));
+ Signature sig = Signature.getInstance(jcaSignatureAlgorithm);
+ sig.initVerify(publicKey);
+ if (jcaSignatureAlgorithmParams != null) {
+ sig.setParameter(jcaSignatureAlgorithmParams);
+ }
+ sig.update(signedData);
+ sigVerified = sig.verify(bestSigAlgorithmSignatureBytes);
+ } catch (NoSuchAlgorithmException | InvalidKeySpecException | InvalidKeyException
+ | InvalidAlgorithmParameterException | SignatureException e) {
+ throw new SecurityException(
+ "Failed to verify " + jcaSignatureAlgorithm + " signature", e);
+ }
+ if (!sigVerified) {
+ throw new SecurityException(jcaSignatureAlgorithm + " signature did not verify");
+ }
+
+ // Signature over signedData has verified.
+
+ byte[] contentDigest = null;
+ signedData.clear();
+ ByteBuffer digests = getLengthPrefixedSlice(signedData);
+ List<Integer> digestsSigAlgorithms = new ArrayList<>();
+ int digestCount = 0;
+ while (digests.hasRemaining()) {
+ digestCount++;
+ try {
+ ByteBuffer digest = getLengthPrefixedSlice(digests);
+ if (digest.remaining() < 8) {
+ throw new IOException("Record too short");
+ }
+ int sigAlgorithm = digest.getInt();
+ digestsSigAlgorithms.add(sigAlgorithm);
+ if (sigAlgorithm == bestSigAlgorithm) {
+ contentDigest = readLengthPrefixedByteArray(digest);
+ }
+ } catch (IOException | BufferUnderflowException e) {
+ throw new IOException("Failed to parse digest record #" + digestCount, e);
+ }
+ }
+
+ if (!signaturesSigAlgorithms.equals(digestsSigAlgorithms)) {
+ throw new SecurityException(
+ "Signature algorithms don't match between digests and signatures records");
+ }
+ int digestAlgorithm = getSignatureAlgorithmContentDigestAlgorithm(bestSigAlgorithm);
+ byte[] previousSignerDigest = contentDigests.put(digestAlgorithm, contentDigest);
+ if ((previousSignerDigest != null)
+ && (!MessageDigest.isEqual(previousSignerDigest, contentDigest))) {
+ throw new SecurityException(
+ getContentDigestAlgorithmJcaDigestAlgorithm(digestAlgorithm)
+ + " contents digest does not match the digest specified by a preceding signer");
+ }
+
+ ByteBuffer certificates = getLengthPrefixedSlice(signedData);
+ List<X509Certificate> certs = new ArrayList<>();
+ int certificateCount = 0;
+ while (certificates.hasRemaining()) {
+ certificateCount++;
+ byte[] encodedCert = readLengthPrefixedByteArray(certificates);
+ X509Certificate certificate;
+ try {
+ certificate = (X509Certificate)
+ certFactory.generateCertificate(new ByteArrayInputStream(encodedCert));
+ } catch (CertificateException e) {
+ throw new SecurityException("Failed to decode certificate #" + certificateCount, e);
+ }
+ certificate = new VerbatimX509Certificate(
+ certificate, encodedCert);
+ certs.add(certificate);
+ }
+
+ if (certs.isEmpty()) {
+ throw new SecurityException("No certificates listed");
+ }
+ X509Certificate mainCertificate = certs.get(0);
+ byte[] certificatePublicKeyBytes = mainCertificate.getPublicKey().getEncoded();
+ if (!Arrays.equals(publicKeyBytes, certificatePublicKeyBytes)) {
+ throw new SecurityException(
+ "Public key mismatch between certificate and signature record");
+ }
+
+ int signedMinSDK = signedData.getInt();
+ if (signedMinSDK != minSdkVersion) {
+ throw new SecurityException(
+ "minSdkVersion mismatch between signed and unsigned in v3 signer block.");
+ }
+
+ int signedMaxSDK = signedData.getInt();
+ if (signedMaxSDK != maxSdkVersion) {
+ throw new SecurityException(
+ "maxSdkVersion mismatch between signed and unsigned in v3 signer block.");
+ }
+
+ ByteBuffer additionalAttrs = getLengthPrefixedSlice(signedData);
+ return verifyAdditionalAttributes(additionalAttrs, certs, certFactory);
+ }
+
+ private static final int PROOF_OF_ROTATION_ATTR_ID = 0x3ba06f8c;
+
+ private static VerifiedSigner verifyAdditionalAttributes(ByteBuffer attrs,
+ List<X509Certificate> certs, CertificateFactory certFactory) throws IOException {
+ X509Certificate[] certChain = certs.toArray(new X509Certificate[certs.size()]);
+ VerifiedProofOfRotation por = null;
+
+ while (attrs.hasRemaining()) {
+ ByteBuffer attr = getLengthPrefixedSlice(attrs);
+ if (attr.remaining() < 4) {
+ throw new IOException("Remaining buffer too short to contain additional attribute "
+ + "ID. Remaining: " + attr.remaining());
+ }
+ int id = attr.getInt();
+ switch(id) {
+ case PROOF_OF_ROTATION_ATTR_ID:
+ if (por != null) {
+ throw new SecurityException("Encountered multiple Proof-of-rotation records"
+ + " when verifying APK Signature Scheme v3 signature");
+ }
+ por = verifyProofOfRotationStruct(attr, certFactory);
+ // make sure that the last certificate in the Proof-of-rotation record matches
+ // the one used to sign this APK.
+ try {
+ if (por.certs.size() > 0
+ && !Arrays.equals(por.certs.get(por.certs.size() - 1).getEncoded(),
+ certChain[0].getEncoded())) {
+ throw new SecurityException("Terminal certificate in Proof-of-rotation"
+ + " record does not match APK signing certificate");
+ }
+ } catch (CertificateEncodingException e) {
+ throw new SecurityException("Failed to encode certificate when comparing"
+ + " Proof-of-rotation record and signing certificate", e);
+ }
+
+ break;
+ default:
+ // not the droid we're looking for, move along, move along.
+ break;
+ }
+ }
+ return new VerifiedSigner(certChain, por);
+ }
+
+ private static VerifiedProofOfRotation verifyProofOfRotationStruct(
+ ByteBuffer porBuf,
+ CertificateFactory certFactory)
+ throws SecurityException, IOException {
+ int levelCount = 0;
+ int lastSigAlgorithm = -1;
+ X509Certificate lastCert = null;
+ List<X509Certificate> certs = new ArrayList<>();
+ List<Integer> flagsList = new ArrayList<>();
+
+ // Proof-of-rotation struct:
+ // is basically a singly linked list of nodes, called levels here, each of which have the
+ // following structure:
+ // * length-prefix for the entire level
+ // - length-prefixed signed data (if previous level exists)
+ // * length-prefixed X509 Certificate
+ // * uint32 signature algorithm ID describing how this signed data was signed
+ // - uint32 flags describing how to treat the cert contained in this level
+ // - uint32 signature algorithm ID to use to verify the signature of the next level. The
+ // algorithm here must match the one in the signed data section of the next level.
+ // - length-prefixed signature over the signed data in this level. The signature here
+ // is verified using the certificate from the previous level.
+ // The linking is provided by the certificate of each level signing the one of the next.
+ while (porBuf.hasRemaining()) {
+ levelCount++;
+ try {
+ ByteBuffer level = getLengthPrefixedSlice(porBuf);
+ ByteBuffer signedData = getLengthPrefixedSlice(level);
+ int flags = level.getInt();
+ int sigAlgorithm = level.getInt();
+ byte[] signature = readLengthPrefixedByteArray(level);
+
+ if (lastCert != null) {
+ // Use previous level cert to verify current level
+ Pair<String, ? extends AlgorithmParameterSpec> sigAlgParams =
+ getSignatureAlgorithmJcaSignatureAlgorithm(lastSigAlgorithm);
+ PublicKey publicKey = lastCert.getPublicKey();
+ Signature sig = Signature.getInstance(sigAlgParams.first);
+ sig.initVerify(publicKey);
+ if (sigAlgParams.second != null) {
+ sig.setParameter(sigAlgParams.second);
+ }
+ sig.update(signedData);
+ if (!sig.verify(signature)) {
+ throw new SecurityException("Unable to verify signature of certificate #"
+ + levelCount + " using " + sigAlgParams.first + " when verifying"
+ + " Proof-of-rotation record");
+ }
+ }
+
+ byte[] encodedCert = readLengthPrefixedByteArray(signedData);
+ int signedSigAlgorithm = signedData.getInt();
+ if (lastCert != null && lastSigAlgorithm != signedSigAlgorithm) {
+ throw new SecurityException("Signing algorithm ID mismatch for certificate #"
+ + levelCount + " when verifying Proof-of-rotation record");
+ }
+ lastCert = (X509Certificate)
+ certFactory.generateCertificate(new ByteArrayInputStream(encodedCert));
+ lastCert = new VerbatimX509Certificate(lastCert, encodedCert);
+
+ lastSigAlgorithm = sigAlgorithm;
+ certs.add(lastCert);
+ flagsList.add(flags);
+ } catch (IOException | BufferUnderflowException e) {
+ throw new IOException("Failed to parse Proof-of-rotation record", e);
+ } catch (NoSuchAlgorithmException | InvalidKeyException
+ | InvalidAlgorithmParameterException | SignatureException e) {
+ throw new SecurityException(
+ "Failed to verify signature over signed data for certificate #"
+ + levelCount + " when verifying Proof-of-rotation record", e);
+ } catch (CertificateException e) {
+ throw new SecurityException("Failed to decode certificate #" + levelCount
+ + " when verifying Proof-of-rotation record", e);
+ }
+ }
+ return new VerifiedProofOfRotation(certs, flagsList);
+ }
+
+ private static boolean isSupportedSignatureAlgorithm(int sigAlgorithm) {
+ switch (sigAlgorithm) {
+ case SIGNATURE_RSA_PSS_WITH_SHA256:
+ case SIGNATURE_RSA_PSS_WITH_SHA512:
+ case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256:
+ case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512:
+ case SIGNATURE_ECDSA_WITH_SHA256:
+ case SIGNATURE_ECDSA_WITH_SHA512:
+ case SIGNATURE_DSA_WITH_SHA256:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * Verified processed proof of rotation.
+ *
+ * @hide for internal use only.
+ */
+ public static class VerifiedProofOfRotation {
+ public final List<X509Certificate> certs;
+ public final List<Integer> flagsList;
+
+ public VerifiedProofOfRotation(List<X509Certificate> certs, List<Integer> flagsList) {
+ this.certs = certs;
+ this.flagsList = flagsList;
+ }
+ }
+
+ /**
+ * Verified APK Signature Scheme v3 signer, including the proof of rotation structure.
+ *
+ * @hide for internal use only.
+ */
+ public static class VerifiedSigner {
+ public final X509Certificate[] certs;
+ public final VerifiedProofOfRotation por;
+
+ public VerifiedSigner(X509Certificate[] certs, VerifiedProofOfRotation por) {
+ this.certs = certs;
+ this.por = por;
+ }
+
+ }
+
+ private static class PlatformNotSupportedException extends Exception {
+
+ PlatformNotSupportedException(String s) {
+ super(s);
+ }
+ }
+}
diff --git a/core/java/android/util/apk/ApkSignatureVerifier.java b/core/java/android/util/apk/ApkSignatureVerifier.java
index 17b11a9..8146729 100644
--- a/core/java/android/util/apk/ApkSignatureVerifier.java
+++ b/core/java/android/util/apk/ApkSignatureVerifier.java
@@ -54,6 +54,7 @@
public static final int VERSION_JAR_SIGNATURE_SCHEME = 1;
public static final int VERSION_APK_SIGNATURE_SCHEME_V2 = 2;
+ public static final int VERSION_APK_SIGNATURE_SCHEME_V3 = 3;
private static final AtomicReference<byte[]> sBuffer = new AtomicReference<>();
@@ -65,7 +66,45 @@
public static Result verify(String apkPath, int minSignatureSchemeVersion)
throws PackageParserException {
- // first try v2
+ if (minSignatureSchemeVersion > VERSION_APK_SIGNATURE_SCHEME_V3) {
+ // V3 and before are older than the requested minimum signing version
+ throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
+ "No signature found in package of version " + minSignatureSchemeVersion
+ + " or newer for package " + apkPath);
+ }
+
+ // first try v3
+ Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "verifyV3");
+ try {
+ ApkSignatureSchemeV3Verifier.VerifiedSigner vSigner =
+ ApkSignatureSchemeV3Verifier.verify(apkPath);
+ Certificate[][] signerCerts = new Certificate[][] { vSigner.certs };
+ Signature[] signerSigs = convertToSignatures(signerCerts);
+ return new Result(signerCerts, signerSigs, VERSION_APK_SIGNATURE_SCHEME_V3);
+ } catch (SignatureNotFoundException e) {
+ // not signed with v2, try older if allowed
+ if (minSignatureSchemeVersion >= VERSION_APK_SIGNATURE_SCHEME_V3) {
+ throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
+ "No APK Signature Scheme v3 signature in package " + apkPath, e);
+ }
+ } catch (Exception e) {
+ // APK Signature Scheme v2 signature found but did not verify
+ throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
+ "Failed to collect certificates from " + apkPath
+ + " using APK Signature Scheme v2", e);
+ } finally {
+ Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+ }
+
+ // redundant, protective version check
+ if (minSignatureSchemeVersion > VERSION_APK_SIGNATURE_SCHEME_V2) {
+ // V2 and before are older than the requested minimum signing version
+ throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
+ "No signature found in package of version " + minSignatureSchemeVersion
+ + " or newer for package " + apkPath);
+ }
+
+ // try v2
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "verifyV2");
try {
Certificate[][] signerCerts = ApkSignatureSchemeV2Verifier.verify(apkPath);
@@ -87,6 +126,14 @@
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
+ // redundant, protective version check
+ if (minSignatureSchemeVersion > VERSION_JAR_SIGNATURE_SCHEME) {
+ // V1 and is older than the requested minimum signing version
+ throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
+ "No signature found in package of version " + minSignatureSchemeVersion
+ + " or newer for package " + apkPath);
+ }
+
// v2 didn't work, try jarsigner
return verifyV1Signature(apkPath, true);
}
@@ -245,6 +292,44 @@
public static Result plsCertsNoVerifyOnlyCerts(String apkPath, int minSignatureSchemeVersion)
throws PackageParserException {
+ if (minSignatureSchemeVersion > VERSION_APK_SIGNATURE_SCHEME_V3) {
+ // V3 and before are older than the requested minimum signing version
+ throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
+ "No signature found in package of version " + minSignatureSchemeVersion
+ + " or newer for package " + apkPath);
+ }
+
+ // first try v3
+ Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "verifyV3");
+ try {
+ ApkSignatureSchemeV3Verifier.VerifiedSigner vSigner =
+ ApkSignatureSchemeV3Verifier.plsCertsNoVerifyOnlyCerts(apkPath);
+ Certificate[][] signerCerts = new Certificate[][] { vSigner.certs };
+ Signature[] signerSigs = convertToSignatures(signerCerts);
+ return new Result(signerCerts, signerSigs, VERSION_APK_SIGNATURE_SCHEME_V3);
+ } catch (SignatureNotFoundException e) {
+ // not signed with v2, try older if allowed
+ if (minSignatureSchemeVersion >= VERSION_APK_SIGNATURE_SCHEME_V3) {
+ throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
+ "No APK Signature Scheme v3 signature in package " + apkPath, e);
+ }
+ } catch (Exception e) {
+ // APK Signature Scheme v2 signature found but did not verify
+ throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
+ "Failed to collect certificates from " + apkPath
+ + " using APK Signature Scheme v2", e);
+ } finally {
+ Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+ }
+
+ // redundant, protective version check
+ if (minSignatureSchemeVersion > VERSION_APK_SIGNATURE_SCHEME_V2) {
+ // V2 and before are older than the requested minimum signing version
+ throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
+ "No signature found in package of version " + minSignatureSchemeVersion
+ + " or newer for package " + apkPath);
+ }
+
// first try v2
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "certsOnlyV2");
try {
@@ -267,6 +352,14 @@
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
+ // redundant, protective version check
+ if (minSignatureSchemeVersion > VERSION_JAR_SIGNATURE_SCHEME) {
+ // V1 and is older than the requested minimum signing version
+ throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
+ "No signature found in package of version " + minSignatureSchemeVersion
+ + " or newer for package " + apkPath);
+ }
+
// v2 didn't work, try jarsigner
return verifyV1Signature(apkPath, false);
}
diff --git a/core/java/android/util/apk/ApkSigningBlockUtils.java b/core/java/android/util/apk/ApkSigningBlockUtils.java
new file mode 100644
index 0000000..9279510
--- /dev/null
+++ b/core/java/android/util/apk/ApkSigningBlockUtils.java
@@ -0,0 +1,663 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.util.apk;
+
+import android.util.Pair;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.security.DigestException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.spec.AlgorithmParameterSpec;
+import java.security.spec.MGF1ParameterSpec;
+import java.security.spec.PSSParameterSpec;
+import java.util.Map;
+
+/**
+ * Utility class for an APK Signature Scheme using the APK Signing Block.
+ *
+ * @hide for internal use only.
+ */
+final class ApkSigningBlockUtils {
+
+ private ApkSigningBlockUtils() {
+ }
+
+ /**
+ * Returns the APK Signature Scheme block contained in the provided APK file and the
+ * additional information relevant for verifying the block against the file.
+ *
+ * @param blockId the ID value in the APK Signing Block's sequence of ID-value pairs
+ * identifying the appropriate block to find, e.g. the APK Signature Scheme v2
+ * block ID.
+ *
+ * @throws SignatureNotFoundException if the APK is not signed using this scheme.
+ * @throws IOException if an I/O error occurs while reading the APK file.
+ */
+ static SignatureInfo findSignature(RandomAccessFile apk, int blockId)
+ throws IOException, SignatureNotFoundException {
+ // Find the ZIP End of Central Directory (EoCD) record.
+ Pair<ByteBuffer, Long> eocdAndOffsetInFile = getEocd(apk);
+ ByteBuffer eocd = eocdAndOffsetInFile.first;
+ long eocdOffset = eocdAndOffsetInFile.second;
+ if (ZipUtils.isZip64EndOfCentralDirectoryLocatorPresent(apk, eocdOffset)) {
+ throw new SignatureNotFoundException("ZIP64 APK not supported");
+ }
+
+ // Find the APK Signing Block. The block immediately precedes the Central Directory.
+ long centralDirOffset = getCentralDirOffset(eocd, eocdOffset);
+ Pair<ByteBuffer, Long> apkSigningBlockAndOffsetInFile =
+ findApkSigningBlock(apk, centralDirOffset);
+ ByteBuffer apkSigningBlock = apkSigningBlockAndOffsetInFile.first;
+ long apkSigningBlockOffset = apkSigningBlockAndOffsetInFile.second;
+
+ // Find the APK Signature Scheme Block inside the APK Signing Block.
+ ByteBuffer apkSignatureSchemeBlock = findApkSignatureSchemeBlock(apkSigningBlock,
+ blockId);
+
+ return new SignatureInfo(
+ apkSignatureSchemeBlock,
+ apkSigningBlockOffset,
+ centralDirOffset,
+ eocdOffset,
+ eocd);
+ }
+
+ static void verifyIntegrity(
+ Map<Integer, byte[]> expectedDigests,
+ FileDescriptor apkFileDescriptor,
+ long apkSigningBlockOffset,
+ long centralDirOffset,
+ long eocdOffset,
+ ByteBuffer eocdBuf) throws SecurityException {
+
+ if (expectedDigests.isEmpty()) {
+ throw new SecurityException("No digests provided");
+ }
+
+ // We need to verify the integrity of the following three sections of the file:
+ // 1. Everything up to the start of the APK Signing Block.
+ // 2. ZIP Central Directory.
+ // 3. ZIP End of Central Directory (EoCD).
+ // Each of these sections is represented as a separate DataSource instance below.
+
+ // To handle large APKs, these sections are read in 1 MB chunks using memory-mapped I/O to
+ // avoid wasting physical memory. In most APK verification scenarios, the contents of the
+ // APK are already there in the OS's page cache and thus mmap does not use additional
+ // physical memory.
+ DataSource beforeApkSigningBlock =
+ new MemoryMappedFileDataSource(apkFileDescriptor, 0, apkSigningBlockOffset);
+ DataSource centralDir =
+ new MemoryMappedFileDataSource(
+ apkFileDescriptor, centralDirOffset, eocdOffset - centralDirOffset);
+
+ // For the purposes of integrity verification, ZIP End of Central Directory's field Start of
+ // Central Directory must be considered to point to the offset of the APK Signing Block.
+ eocdBuf = eocdBuf.duplicate();
+ eocdBuf.order(ByteOrder.LITTLE_ENDIAN);
+ ZipUtils.setZipEocdCentralDirectoryOffset(eocdBuf, apkSigningBlockOffset);
+ DataSource eocd = new ByteBufferDataSource(eocdBuf);
+
+ int[] digestAlgorithms = new int[expectedDigests.size()];
+ int digestAlgorithmCount = 0;
+ for (int digestAlgorithm : expectedDigests.keySet()) {
+ digestAlgorithms[digestAlgorithmCount] = digestAlgorithm;
+ digestAlgorithmCount++;
+ }
+ byte[][] actualDigests;
+ try {
+ actualDigests =
+ computeContentDigests(
+ digestAlgorithms,
+ new DataSource[] {beforeApkSigningBlock, centralDir, eocd});
+ } catch (DigestException e) {
+ throw new SecurityException("Failed to compute digest(s) of contents", e);
+ }
+ for (int i = 0; i < digestAlgorithms.length; i++) {
+ int digestAlgorithm = digestAlgorithms[i];
+ byte[] expectedDigest = expectedDigests.get(digestAlgorithm);
+ byte[] actualDigest = actualDigests[i];
+ if (!MessageDigest.isEqual(expectedDigest, actualDigest)) {
+ throw new SecurityException(
+ getContentDigestAlgorithmJcaDigestAlgorithm(digestAlgorithm)
+ + " digest of contents did not verify");
+ }
+ }
+ }
+
+ private static byte[][] computeContentDigests(
+ int[] digestAlgorithms,
+ DataSource[] contents) throws DigestException {
+ // For each digest algorithm the result is computed as follows:
+ // 1. Each segment of contents is split into consecutive chunks of 1 MB in size.
+ // The final chunk will be shorter iff the length of segment is not a multiple of 1 MB.
+ // No chunks are produced for empty (zero length) segments.
+ // 2. The digest of each chunk is computed over the concatenation of byte 0xa5, the chunk's
+ // length in bytes (uint32 little-endian) and the chunk's contents.
+ // 3. The output digest is computed over the concatenation of the byte 0x5a, the number of
+ // chunks (uint32 little-endian) and the concatenation of digests of chunks of all
+ // segments in-order.
+
+ long totalChunkCountLong = 0;
+ for (DataSource input : contents) {
+ totalChunkCountLong += getChunkCount(input.size());
+ }
+ if (totalChunkCountLong >= Integer.MAX_VALUE / 1024) {
+ throw new DigestException("Too many chunks: " + totalChunkCountLong);
+ }
+ int totalChunkCount = (int) totalChunkCountLong;
+
+ byte[][] digestsOfChunks = new byte[digestAlgorithms.length][];
+ for (int i = 0; i < digestAlgorithms.length; i++) {
+ int digestAlgorithm = digestAlgorithms[i];
+ int digestOutputSizeBytes = getContentDigestAlgorithmOutputSizeBytes(digestAlgorithm);
+ byte[] concatenationOfChunkCountAndChunkDigests =
+ new byte[5 + totalChunkCount * digestOutputSizeBytes];
+ concatenationOfChunkCountAndChunkDigests[0] = 0x5a;
+ setUnsignedInt32LittleEndian(
+ totalChunkCount,
+ concatenationOfChunkCountAndChunkDigests,
+ 1);
+ digestsOfChunks[i] = concatenationOfChunkCountAndChunkDigests;
+ }
+
+ byte[] chunkContentPrefix = new byte[5];
+ chunkContentPrefix[0] = (byte) 0xa5;
+ int chunkIndex = 0;
+ MessageDigest[] mds = new MessageDigest[digestAlgorithms.length];
+ for (int i = 0; i < digestAlgorithms.length; i++) {
+ String jcaAlgorithmName =
+ getContentDigestAlgorithmJcaDigestAlgorithm(digestAlgorithms[i]);
+ try {
+ mds[i] = MessageDigest.getInstance(jcaAlgorithmName);
+ } catch (NoSuchAlgorithmException e) {
+ throw new RuntimeException(jcaAlgorithmName + " digest not supported", e);
+ }
+ }
+ // TODO: Compute digests of chunks in parallel when beneficial. This requires some research
+ // into how to parallelize (if at all) based on the capabilities of the hardware on which
+ // this code is running and based on the size of input.
+ DataDigester digester = new MultipleDigestDataDigester(mds);
+ int dataSourceIndex = 0;
+ for (DataSource input : contents) {
+ long inputOffset = 0;
+ long inputRemaining = input.size();
+ while (inputRemaining > 0) {
+ int chunkSize = (int) Math.min(inputRemaining, CHUNK_SIZE_BYTES);
+ setUnsignedInt32LittleEndian(chunkSize, chunkContentPrefix, 1);
+ for (int i = 0; i < mds.length; i++) {
+ mds[i].update(chunkContentPrefix);
+ }
+ try {
+ input.feedIntoDataDigester(digester, inputOffset, chunkSize);
+ } catch (IOException e) {
+ throw new DigestException(
+ "Failed to digest chunk #" + chunkIndex + " of section #"
+ + dataSourceIndex,
+ e);
+ }
+ for (int i = 0; i < digestAlgorithms.length; i++) {
+ int digestAlgorithm = digestAlgorithms[i];
+ byte[] concatenationOfChunkCountAndChunkDigests = digestsOfChunks[i];
+ int expectedDigestSizeBytes =
+ getContentDigestAlgorithmOutputSizeBytes(digestAlgorithm);
+ MessageDigest md = mds[i];
+ int actualDigestSizeBytes =
+ md.digest(
+ concatenationOfChunkCountAndChunkDigests,
+ 5 + chunkIndex * expectedDigestSizeBytes,
+ expectedDigestSizeBytes);
+ if (actualDigestSizeBytes != expectedDigestSizeBytes) {
+ throw new RuntimeException(
+ "Unexpected output size of " + md.getAlgorithm() + " digest: "
+ + actualDigestSizeBytes);
+ }
+ }
+ inputOffset += chunkSize;
+ inputRemaining -= chunkSize;
+ chunkIndex++;
+ }
+ dataSourceIndex++;
+ }
+
+ byte[][] result = new byte[digestAlgorithms.length][];
+ for (int i = 0; i < digestAlgorithms.length; i++) {
+ int digestAlgorithm = digestAlgorithms[i];
+ byte[] input = digestsOfChunks[i];
+ String jcaAlgorithmName = getContentDigestAlgorithmJcaDigestAlgorithm(digestAlgorithm);
+ MessageDigest md;
+ try {
+ md = MessageDigest.getInstance(jcaAlgorithmName);
+ } catch (NoSuchAlgorithmException e) {
+ throw new RuntimeException(jcaAlgorithmName + " digest not supported", e);
+ }
+ byte[] output = md.digest(input);
+ result[i] = output;
+ }
+ return result;
+ }
+
+ /**
+ * Returns the ZIP End of Central Directory (EoCD) and its offset in the file.
+ *
+ * @throws IOException if an I/O error occurs while reading the file.
+ * @throws SignatureNotFoundException if the EoCD could not be found.
+ */
+ static Pair<ByteBuffer, Long> getEocd(RandomAccessFile apk)
+ throws IOException, SignatureNotFoundException {
+ Pair<ByteBuffer, Long> eocdAndOffsetInFile =
+ ZipUtils.findZipEndOfCentralDirectoryRecord(apk);
+ if (eocdAndOffsetInFile == null) {
+ throw new SignatureNotFoundException(
+ "Not an APK file: ZIP End of Central Directory record not found");
+ }
+ return eocdAndOffsetInFile;
+ }
+
+ static long getCentralDirOffset(ByteBuffer eocd, long eocdOffset)
+ throws SignatureNotFoundException {
+ // Look up the offset of ZIP Central Directory.
+ long centralDirOffset = ZipUtils.getZipEocdCentralDirectoryOffset(eocd);
+ if (centralDirOffset > eocdOffset) {
+ throw new SignatureNotFoundException(
+ "ZIP Central Directory offset out of range: " + centralDirOffset
+ + ". ZIP End of Central Directory offset: " + eocdOffset);
+ }
+ long centralDirSize = ZipUtils.getZipEocdCentralDirectorySizeBytes(eocd);
+ if (centralDirOffset + centralDirSize != eocdOffset) {
+ throw new SignatureNotFoundException(
+ "ZIP Central Directory is not immediately followed by End of Central"
+ + " Directory");
+ }
+ return centralDirOffset;
+ }
+
+ private static long getChunkCount(long inputSizeBytes) {
+ return (inputSizeBytes + CHUNK_SIZE_BYTES - 1) / CHUNK_SIZE_BYTES;
+ }
+
+ private static final int CHUNK_SIZE_BYTES = 1024 * 1024;
+
+ static final int SIGNATURE_RSA_PSS_WITH_SHA256 = 0x0101;
+ static final int SIGNATURE_RSA_PSS_WITH_SHA512 = 0x0102;
+ static final int SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256 = 0x0103;
+ static final int SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512 = 0x0104;
+ static final int SIGNATURE_ECDSA_WITH_SHA256 = 0x0201;
+ static final int SIGNATURE_ECDSA_WITH_SHA512 = 0x0202;
+ static final int SIGNATURE_DSA_WITH_SHA256 = 0x0301;
+
+ static final int CONTENT_DIGEST_CHUNKED_SHA256 = 1;
+ static final int CONTENT_DIGEST_CHUNKED_SHA512 = 2;
+
+ static int compareSignatureAlgorithm(int sigAlgorithm1, int sigAlgorithm2) {
+ int digestAlgorithm1 = getSignatureAlgorithmContentDigestAlgorithm(sigAlgorithm1);
+ int digestAlgorithm2 = getSignatureAlgorithmContentDigestAlgorithm(sigAlgorithm2);
+ return compareContentDigestAlgorithm(digestAlgorithm1, digestAlgorithm2);
+ }
+
+ private static int compareContentDigestAlgorithm(int digestAlgorithm1, int digestAlgorithm2) {
+ switch (digestAlgorithm1) {
+ case CONTENT_DIGEST_CHUNKED_SHA256:
+ switch (digestAlgorithm2) {
+ case CONTENT_DIGEST_CHUNKED_SHA256:
+ return 0;
+ case CONTENT_DIGEST_CHUNKED_SHA512:
+ return -1;
+ default:
+ throw new IllegalArgumentException(
+ "Unknown digestAlgorithm2: " + digestAlgorithm2);
+ }
+ case CONTENT_DIGEST_CHUNKED_SHA512:
+ switch (digestAlgorithm2) {
+ case CONTENT_DIGEST_CHUNKED_SHA256:
+ return 1;
+ case CONTENT_DIGEST_CHUNKED_SHA512:
+ return 0;
+ default:
+ throw new IllegalArgumentException(
+ "Unknown digestAlgorithm2: " + digestAlgorithm2);
+ }
+ default:
+ throw new IllegalArgumentException("Unknown digestAlgorithm1: " + digestAlgorithm1);
+ }
+ }
+
+ static int getSignatureAlgorithmContentDigestAlgorithm(int sigAlgorithm) {
+ switch (sigAlgorithm) {
+ case SIGNATURE_RSA_PSS_WITH_SHA256:
+ case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256:
+ case SIGNATURE_ECDSA_WITH_SHA256:
+ case SIGNATURE_DSA_WITH_SHA256:
+ return CONTENT_DIGEST_CHUNKED_SHA256;
+ case SIGNATURE_RSA_PSS_WITH_SHA512:
+ case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512:
+ case SIGNATURE_ECDSA_WITH_SHA512:
+ return CONTENT_DIGEST_CHUNKED_SHA512;
+ default:
+ throw new IllegalArgumentException(
+ "Unknown signature algorithm: 0x"
+ + Long.toHexString(sigAlgorithm & 0xffffffff));
+ }
+ }
+
+ static String getContentDigestAlgorithmJcaDigestAlgorithm(int digestAlgorithm) {
+ switch (digestAlgorithm) {
+ case CONTENT_DIGEST_CHUNKED_SHA256:
+ return "SHA-256";
+ case CONTENT_DIGEST_CHUNKED_SHA512:
+ return "SHA-512";
+ default:
+ throw new IllegalArgumentException(
+ "Unknown content digest algorthm: " + digestAlgorithm);
+ }
+ }
+
+ private static int getContentDigestAlgorithmOutputSizeBytes(int digestAlgorithm) {
+ switch (digestAlgorithm) {
+ case CONTENT_DIGEST_CHUNKED_SHA256:
+ return 256 / 8;
+ case CONTENT_DIGEST_CHUNKED_SHA512:
+ return 512 / 8;
+ default:
+ throw new IllegalArgumentException(
+ "Unknown content digest algorthm: " + digestAlgorithm);
+ }
+ }
+
+ static String getSignatureAlgorithmJcaKeyAlgorithm(int sigAlgorithm) {
+ switch (sigAlgorithm) {
+ case SIGNATURE_RSA_PSS_WITH_SHA256:
+ case SIGNATURE_RSA_PSS_WITH_SHA512:
+ case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256:
+ case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512:
+ return "RSA";
+ case SIGNATURE_ECDSA_WITH_SHA256:
+ case SIGNATURE_ECDSA_WITH_SHA512:
+ return "EC";
+ case SIGNATURE_DSA_WITH_SHA256:
+ return "DSA";
+ default:
+ throw new IllegalArgumentException(
+ "Unknown signature algorithm: 0x"
+ + Long.toHexString(sigAlgorithm & 0xffffffff));
+ }
+ }
+
+ static Pair<String, ? extends AlgorithmParameterSpec>
+ getSignatureAlgorithmJcaSignatureAlgorithm(int sigAlgorithm) {
+ switch (sigAlgorithm) {
+ case SIGNATURE_RSA_PSS_WITH_SHA256:
+ return Pair.create(
+ "SHA256withRSA/PSS",
+ new PSSParameterSpec(
+ "SHA-256", "MGF1", MGF1ParameterSpec.SHA256, 256 / 8, 1));
+ case SIGNATURE_RSA_PSS_WITH_SHA512:
+ return Pair.create(
+ "SHA512withRSA/PSS",
+ new PSSParameterSpec(
+ "SHA-512", "MGF1", MGF1ParameterSpec.SHA512, 512 / 8, 1));
+ case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256:
+ return Pair.create("SHA256withRSA", null);
+ case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512:
+ return Pair.create("SHA512withRSA", null);
+ case SIGNATURE_ECDSA_WITH_SHA256:
+ return Pair.create("SHA256withECDSA", null);
+ case SIGNATURE_ECDSA_WITH_SHA512:
+ return Pair.create("SHA512withECDSA", null);
+ case SIGNATURE_DSA_WITH_SHA256:
+ return Pair.create("SHA256withDSA", null);
+ default:
+ throw new IllegalArgumentException(
+ "Unknown signature algorithm: 0x"
+ + Long.toHexString(sigAlgorithm & 0xffffffff));
+ }
+ }
+
+ /**
+ * Returns new byte buffer whose content is a shared subsequence of this buffer's content
+ * between the specified start (inclusive) and end (exclusive) positions. As opposed to
+ * {@link ByteBuffer#slice()}, the returned buffer's byte order is the same as the source
+ * buffer's byte order.
+ */
+ static ByteBuffer sliceFromTo(ByteBuffer source, int start, int end) {
+ if (start < 0) {
+ throw new IllegalArgumentException("start: " + start);
+ }
+ if (end < start) {
+ throw new IllegalArgumentException("end < start: " + end + " < " + start);
+ }
+ int capacity = source.capacity();
+ if (end > source.capacity()) {
+ throw new IllegalArgumentException("end > capacity: " + end + " > " + capacity);
+ }
+ int originalLimit = source.limit();
+ int originalPosition = source.position();
+ try {
+ source.position(0);
+ source.limit(end);
+ source.position(start);
+ ByteBuffer result = source.slice();
+ result.order(source.order());
+ return result;
+ } finally {
+ source.position(0);
+ source.limit(originalLimit);
+ source.position(originalPosition);
+ }
+ }
+
+ /**
+ * Relative <em>get</em> method for reading {@code size} number of bytes from the current
+ * position of this buffer.
+ *
+ * <p>This method reads the next {@code size} bytes at this buffer's current position,
+ * returning them as a {@code ByteBuffer} with start set to 0, limit and capacity set to
+ * {@code size}, byte order set to this buffer's byte order; and then increments the position by
+ * {@code size}.
+ */
+ static ByteBuffer getByteBuffer(ByteBuffer source, int size)
+ throws BufferUnderflowException {
+ if (size < 0) {
+ throw new IllegalArgumentException("size: " + size);
+ }
+ int originalLimit = source.limit();
+ int position = source.position();
+ int limit = position + size;
+ if ((limit < position) || (limit > originalLimit)) {
+ throw new BufferUnderflowException();
+ }
+ source.limit(limit);
+ try {
+ ByteBuffer result = source.slice();
+ result.order(source.order());
+ source.position(limit);
+ return result;
+ } finally {
+ source.limit(originalLimit);
+ }
+ }
+
+ static ByteBuffer getLengthPrefixedSlice(ByteBuffer source) throws IOException {
+ if (source.remaining() < 4) {
+ throw new IOException(
+ "Remaining buffer too short to contain length of length-prefixed field."
+ + " Remaining: " + source.remaining());
+ }
+ int len = source.getInt();
+ if (len < 0) {
+ throw new IllegalArgumentException("Negative length");
+ } else if (len > source.remaining()) {
+ throw new IOException("Length-prefixed field longer than remaining buffer."
+ + " Field length: " + len + ", remaining: " + source.remaining());
+ }
+ return getByteBuffer(source, len);
+ }
+
+ static byte[] readLengthPrefixedByteArray(ByteBuffer buf) throws IOException {
+ int len = buf.getInt();
+ if (len < 0) {
+ throw new IOException("Negative length");
+ } else if (len > buf.remaining()) {
+ throw new IOException("Underflow while reading length-prefixed value. Length: " + len
+ + ", available: " + buf.remaining());
+ }
+ byte[] result = new byte[len];
+ buf.get(result);
+ return result;
+ }
+
+ static void setUnsignedInt32LittleEndian(int value, byte[] result, int offset) {
+ result[offset] = (byte) (value & 0xff);
+ result[offset + 1] = (byte) ((value >>> 8) & 0xff);
+ result[offset + 2] = (byte) ((value >>> 16) & 0xff);
+ result[offset + 3] = (byte) ((value >>> 24) & 0xff);
+ }
+
+ private static final long APK_SIG_BLOCK_MAGIC_HI = 0x3234206b636f6c42L;
+ private static final long APK_SIG_BLOCK_MAGIC_LO = 0x20676953204b5041L;
+ private static final int APK_SIG_BLOCK_MIN_SIZE = 32;
+
+ static Pair<ByteBuffer, Long> findApkSigningBlock(
+ RandomAccessFile apk, long centralDirOffset)
+ throws IOException, SignatureNotFoundException {
+ // FORMAT:
+ // OFFSET DATA TYPE DESCRIPTION
+ // * @+0 bytes uint64: size in bytes (excluding this field)
+ // * @+8 bytes payload
+ // * @-24 bytes uint64: size in bytes (same as the one above)
+ // * @-16 bytes uint128: magic
+
+ if (centralDirOffset < APK_SIG_BLOCK_MIN_SIZE) {
+ throw new SignatureNotFoundException(
+ "APK too small for APK Signing Block. ZIP Central Directory offset: "
+ + centralDirOffset);
+ }
+ // Read the magic and offset in file from the footer section of the block:
+ // * uint64: size of block
+ // * 16 bytes: magic
+ ByteBuffer footer = ByteBuffer.allocate(24);
+ footer.order(ByteOrder.LITTLE_ENDIAN);
+ apk.seek(centralDirOffset - footer.capacity());
+ apk.readFully(footer.array(), footer.arrayOffset(), footer.capacity());
+ if ((footer.getLong(8) != APK_SIG_BLOCK_MAGIC_LO)
+ || (footer.getLong(16) != APK_SIG_BLOCK_MAGIC_HI)) {
+ throw new SignatureNotFoundException(
+ "No APK Signing Block before ZIP Central Directory");
+ }
+ // Read and compare size fields
+ long apkSigBlockSizeInFooter = footer.getLong(0);
+ if ((apkSigBlockSizeInFooter < footer.capacity())
+ || (apkSigBlockSizeInFooter > Integer.MAX_VALUE - 8)) {
+ throw new SignatureNotFoundException(
+ "APK Signing Block size out of range: " + apkSigBlockSizeInFooter);
+ }
+ int totalSize = (int) (apkSigBlockSizeInFooter + 8);
+ long apkSigBlockOffset = centralDirOffset - totalSize;
+ if (apkSigBlockOffset < 0) {
+ throw new SignatureNotFoundException(
+ "APK Signing Block offset out of range: " + apkSigBlockOffset);
+ }
+ ByteBuffer apkSigBlock = ByteBuffer.allocate(totalSize);
+ apkSigBlock.order(ByteOrder.LITTLE_ENDIAN);
+ apk.seek(apkSigBlockOffset);
+ apk.readFully(apkSigBlock.array(), apkSigBlock.arrayOffset(), apkSigBlock.capacity());
+ long apkSigBlockSizeInHeader = apkSigBlock.getLong(0);
+ if (apkSigBlockSizeInHeader != apkSigBlockSizeInFooter) {
+ throw new SignatureNotFoundException(
+ "APK Signing Block sizes in header and footer do not match: "
+ + apkSigBlockSizeInHeader + " vs " + apkSigBlockSizeInFooter);
+ }
+ return Pair.create(apkSigBlock, apkSigBlockOffset);
+ }
+
+ static ByteBuffer findApkSignatureSchemeBlock(ByteBuffer apkSigningBlock, int blockId)
+ throws SignatureNotFoundException {
+ checkByteOrderLittleEndian(apkSigningBlock);
+ // FORMAT:
+ // OFFSET DATA TYPE DESCRIPTION
+ // * @+0 bytes uint64: size in bytes (excluding this field)
+ // * @+8 bytes pairs
+ // * @-24 bytes uint64: size in bytes (same as the one above)
+ // * @-16 bytes uint128: magic
+ ByteBuffer pairs = sliceFromTo(apkSigningBlock, 8, apkSigningBlock.capacity() - 24);
+
+ int entryCount = 0;
+ while (pairs.hasRemaining()) {
+ entryCount++;
+ if (pairs.remaining() < 8) {
+ throw new SignatureNotFoundException(
+ "Insufficient data to read size of APK Signing Block entry #" + entryCount);
+ }
+ long lenLong = pairs.getLong();
+ if ((lenLong < 4) || (lenLong > Integer.MAX_VALUE)) {
+ throw new SignatureNotFoundException(
+ "APK Signing Block entry #" + entryCount
+ + " size out of range: " + lenLong);
+ }
+ int len = (int) lenLong;
+ int nextEntryPos = pairs.position() + len;
+ if (len > pairs.remaining()) {
+ throw new SignatureNotFoundException(
+ "APK Signing Block entry #" + entryCount + " size out of range: " + len
+ + ", available: " + pairs.remaining());
+ }
+ int id = pairs.getInt();
+ if (id == blockId) {
+ return getByteBuffer(pairs, len - 4);
+ }
+ pairs.position(nextEntryPos);
+ }
+
+ throw new SignatureNotFoundException(
+ "No block with ID " + blockId + " in APK Signing Block.");
+ }
+
+ private static void checkByteOrderLittleEndian(ByteBuffer buffer) {
+ if (buffer.order() != ByteOrder.LITTLE_ENDIAN) {
+ throw new IllegalArgumentException("ByteBuffer byte order must be little endian");
+ }
+ }
+
+ /**
+ * {@link DataDigester} that updates multiple {@link MessageDigest}s whenever data is fed.
+ */
+ private static class MultipleDigestDataDigester implements DataDigester {
+ private final MessageDigest[] mMds;
+
+ MultipleDigestDataDigester(MessageDigest[] mds) {
+ mMds = mds;
+ }
+
+ @Override
+ public void consume(ByteBuffer buffer) {
+ buffer = buffer.slice();
+ for (MessageDigest md : mMds) {
+ buffer.position(0);
+ md.update(buffer);
+ }
+ }
+
+ @Override
+ public void finish() {}
+ }
+
+}
diff --git a/core/java/android/util/apk/VerbatimX509Certificate.java b/core/java/android/util/apk/VerbatimX509Certificate.java
new file mode 100644
index 0000000..9984c6d
--- /dev/null
+++ b/core/java/android/util/apk/VerbatimX509Certificate.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.util.apk;
+
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.X509Certificate;
+
+/**
+ * For legacy reasons we need to return exactly the original encoded certificate bytes, instead
+ * of letting the underlying implementation have a shot at re-encoding the data.
+ */
+class VerbatimX509Certificate extends WrappedX509Certificate {
+ private final byte[] mEncodedVerbatim;
+
+ VerbatimX509Certificate(X509Certificate wrapped, byte[] encodedVerbatim) {
+ super(wrapped);
+ this.mEncodedVerbatim = encodedVerbatim;
+ }
+
+ @Override
+ public byte[] getEncoded() throws CertificateEncodingException {
+ return mEncodedVerbatim;
+ }
+}
diff --git a/core/java/android/util/apk/WrappedX509Certificate.java b/core/java/android/util/apk/WrappedX509Certificate.java
new file mode 100644
index 0000000..fdaa420
--- /dev/null
+++ b/core/java/android/util/apk/WrappedX509Certificate.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.util.apk;
+
+import java.math.BigInteger;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.Principal;
+import java.security.PublicKey;
+import java.security.SignatureException;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateExpiredException;
+import java.security.cert.CertificateNotYetValidException;
+import java.security.cert.X509Certificate;
+import java.util.Date;
+import java.util.Set;
+
+class WrappedX509Certificate extends X509Certificate {
+ private final X509Certificate mWrapped;
+
+ WrappedX509Certificate(X509Certificate wrapped) {
+ this.mWrapped = wrapped;
+ }
+
+ @Override
+ public Set<String> getCriticalExtensionOIDs() {
+ return mWrapped.getCriticalExtensionOIDs();
+ }
+
+ @Override
+ public byte[] getExtensionValue(String oid) {
+ return mWrapped.getExtensionValue(oid);
+ }
+
+ @Override
+ public Set<String> getNonCriticalExtensionOIDs() {
+ return mWrapped.getNonCriticalExtensionOIDs();
+ }
+
+ @Override
+ public boolean hasUnsupportedCriticalExtension() {
+ return mWrapped.hasUnsupportedCriticalExtension();
+ }
+
+ @Override
+ public void checkValidity()
+ throws CertificateExpiredException, CertificateNotYetValidException {
+ mWrapped.checkValidity();
+ }
+
+ @Override
+ public void checkValidity(Date date)
+ throws CertificateExpiredException, CertificateNotYetValidException {
+ mWrapped.checkValidity(date);
+ }
+
+ @Override
+ public int getVersion() {
+ return mWrapped.getVersion();
+ }
+
+ @Override
+ public BigInteger getSerialNumber() {
+ return mWrapped.getSerialNumber();
+ }
+
+ @Override
+ public Principal getIssuerDN() {
+ return mWrapped.getIssuerDN();
+ }
+
+ @Override
+ public Principal getSubjectDN() {
+ return mWrapped.getSubjectDN();
+ }
+
+ @Override
+ public Date getNotBefore() {
+ return mWrapped.getNotBefore();
+ }
+
+ @Override
+ public Date getNotAfter() {
+ return mWrapped.getNotAfter();
+ }
+
+ @Override
+ public byte[] getTBSCertificate() throws CertificateEncodingException {
+ return mWrapped.getTBSCertificate();
+ }
+
+ @Override
+ public byte[] getSignature() {
+ return mWrapped.getSignature();
+ }
+
+ @Override
+ public String getSigAlgName() {
+ return mWrapped.getSigAlgName();
+ }
+
+ @Override
+ public String getSigAlgOID() {
+ return mWrapped.getSigAlgOID();
+ }
+
+ @Override
+ public byte[] getSigAlgParams() {
+ return mWrapped.getSigAlgParams();
+ }
+
+ @Override
+ public boolean[] getIssuerUniqueID() {
+ return mWrapped.getIssuerUniqueID();
+ }
+
+ @Override
+ public boolean[] getSubjectUniqueID() {
+ return mWrapped.getSubjectUniqueID();
+ }
+
+ @Override
+ public boolean[] getKeyUsage() {
+ return mWrapped.getKeyUsage();
+ }
+
+ @Override
+ public int getBasicConstraints() {
+ return mWrapped.getBasicConstraints();
+ }
+
+ @Override
+ public byte[] getEncoded() throws CertificateEncodingException {
+ return mWrapped.getEncoded();
+ }
+
+ @Override
+ public void verify(PublicKey key) throws CertificateException, NoSuchAlgorithmException,
+ InvalidKeyException, NoSuchProviderException, SignatureException {
+ mWrapped.verify(key);
+ }
+
+ @Override
+ public void verify(PublicKey key, String sigProvider)
+ throws CertificateException, NoSuchAlgorithmException, InvalidKeyException,
+ NoSuchProviderException, SignatureException {
+ mWrapped.verify(key, sigProvider);
+ }
+
+ @Override
+ public String toString() {
+ return mWrapped.toString();
+ }
+
+ @Override
+ public PublicKey getPublicKey() {
+ return mWrapped.getPublicKey();
+ }
+}
diff --git a/core/java/android/util/jar/StrictJarVerifier.java b/core/java/android/util/jar/StrictJarVerifier.java
index debc170..4525490 100644
--- a/core/java/android/util/jar/StrictJarVerifier.java
+++ b/core/java/android/util/jar/StrictJarVerifier.java
@@ -18,6 +18,8 @@
package android.util.jar;
import android.util.apk.ApkSignatureSchemeV2Verifier;
+import android.util.apk.ApkSignatureSchemeV3Verifier;
+
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
@@ -36,6 +38,7 @@
import java.util.StringTokenizer;
import java.util.jar.Attributes;
import java.util.jar.JarFile;
+
import sun.security.jca.Providers;
import sun.security.pkcs.PKCS7;
import sun.security.pkcs.SignerInfo;
@@ -56,6 +59,15 @@
*/
class StrictJarVerifier {
/**
+ * {@code .SF} file header section attribute indicating that the APK is signed not just with
+ * JAR signature scheme but also with APK Signature Scheme v2 or newer. This attribute
+ * facilitates v2 signature stripping detection.
+ *
+ * <p>The attribute contains a comma-separated set of signature scheme IDs.
+ */
+ private static final String SF_ATTRIBUTE_ANDROID_APK_SIGNED_NAME = "X-Android-APK-Signed";
+
+ /**
* List of accepted digest algorithms. This list is in order from most
* preferred to least preferred.
*/
@@ -373,17 +385,17 @@
return;
}
- // If requested, check whether APK Signature Scheme v2 signature was stripped.
+ // If requested, check whether a newer APK Signature Scheme signature was stripped.
if (signatureSchemeRollbackProtectionsEnforced) {
String apkSignatureSchemeIdList =
- attributes.getValue(
- ApkSignatureSchemeV2Verifier.SF_ATTRIBUTE_ANDROID_APK_SIGNED_NAME);
+ attributes.getValue(SF_ATTRIBUTE_ANDROID_APK_SIGNED_NAME);
if (apkSignatureSchemeIdList != null) {
// This field contains a comma-separated list of APK signature scheme IDs which
// were used to sign this APK. If an ID is known to us, it means signatures of that
// scheme were stripped from the APK because otherwise we wouldn't have fallen back
// to verifying the APK using the JAR signature scheme.
boolean v2SignatureGenerated = false;
+ boolean v3SignatureGenerated = false;
StringTokenizer tokenizer = new StringTokenizer(apkSignatureSchemeIdList, ",");
while (tokenizer.hasMoreTokens()) {
String idText = tokenizer.nextToken().trim();
@@ -402,6 +414,12 @@
v2SignatureGenerated = true;
break;
}
+ if (id == ApkSignatureSchemeV3Verifier.SF_ATTRIBUTE_ANDROID_APK_SIGNED_ID) {
+ // This APK was supposed to be signed with APK Signature Scheme v3 but no
+ // such signature was found.
+ v3SignatureGenerated = true;
+ break;
+ }
}
if (v2SignatureGenerated) {
@@ -409,6 +427,11 @@
+ " is signed using APK Signature Scheme v2, but no such signature was"
+ " found. Signature stripped?");
}
+ if (v3SignatureGenerated) {
+ throw new SecurityException(signatureFile + " indicates " + jarName
+ + " is signed using APK Signature Scheme v3, but no such signature was"
+ + " found. Signature stripped?");
+ }
}
}
diff --git a/core/java/android/widget/EditText.java b/core/java/android/widget/EditText.java
index 56c3e4a..336c20c 100644
--- a/core/java/android/widget/EditText.java
+++ b/core/java/android/widget/EditText.java
@@ -105,6 +105,11 @@
@Override
public Editable getText() {
+ CharSequence text = super.getText();
+ if (text instanceof Editable) {
+ return (Editable) super.getText();
+ }
+ super.setText(text, BufferType.EDITABLE);
return (Editable) super.getText();
}
diff --git a/core/java/android/widget/Magnifier.java b/core/java/android/widget/Magnifier.java
index bd48f45..26dfcc2 100644
--- a/core/java/android/widget/Magnifier.java
+++ b/core/java/android/widget/Magnifier.java
@@ -125,7 +125,7 @@
mView.getWidth() - mBitmap.getWidth()));
final int startY = mCenterZoomCoords.y - mBitmap.getHeight() / 2;
- if (startX != mPrevStartCoordsInSurface.x || startY != mPrevStartCoordsInSurface.y) {
+ if (xPosInView != mPrevPosInView.x || yPosInView != mPrevPosInView.y) {
performPixelCopy(startX, startY);
mPrevPosInView.x = xPosInView;
diff --git a/core/java/com/android/internal/net/INetworkWatchlistManager.aidl b/core/java/com/android/internal/net/INetworkWatchlistManager.aidl
index 7e88369..ee01a23 100644
--- a/core/java/com/android/internal/net/INetworkWatchlistManager.aidl
+++ b/core/java/com/android/internal/net/INetworkWatchlistManager.aidl
@@ -22,5 +22,6 @@
interface INetworkWatchlistManager {
boolean startWatchlistLogging();
boolean stopWatchlistLogging();
+ void reloadWatchlist();
void reportWatchlistIfNecessary();
}
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 72f07b7..1739bed 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -506,8 +506,8 @@
final HistoryEventTracker mActiveEvents = new HistoryEventTracker();
long mHistoryBaseTime;
- boolean mHaveBatteryLevel = false;
- boolean mRecordingHistory = false;
+ protected boolean mHaveBatteryLevel = false;
+ protected boolean mRecordingHistory = false;
int mNumHistoryItems;
final Parcel mHistoryBuffer = Parcel.obtain();
@@ -652,6 +652,14 @@
new LongSamplingCounter[NUM_NETWORK_ACTIVITY_TYPES];
/**
+ * The WiFi Overall wakelock timer
+ * This timer tracks the actual aggregate time for which MC wakelocks are enabled
+ * since addition of per UID timers would not result in an accurate value due to overlapp of
+ * per uid wakelock timers
+ */
+ StopwatchTimer mWifiMulticastWakelockTimer;
+
+ /**
* The WiFi controller activity (time in tx, rx, idle, and power consumed) for the device.
*/
ControllerActivityCounterImpl mWifiActivity;
@@ -3975,30 +3983,85 @@
addHistoryEventLocked(elapsedRealtime, uptime, HistoryItem.EVENT_JOB_FINISH, name, uid);
}
- public void noteAlarmStartLocked(String name, int uid) {
- if (!mRecordAllHistory) {
- return;
- }
- uid = mapUid(uid);
- final long elapsedRealtime = mClocks.elapsedRealtime();
- final long uptime = mClocks.uptimeMillis();
- if (!mActiveEvents.updateState(HistoryItem.EVENT_ALARM_START, name, uid, 0)) {
- return;
- }
- addHistoryEventLocked(elapsedRealtime, uptime, HistoryItem.EVENT_ALARM_START, name, uid);
+ public void noteAlarmStartLocked(String name, WorkSource workSource, int uid) {
+ noteAlarmStartOrFinishLocked(HistoryItem.EVENT_ALARM_START, name, workSource, uid);
}
- public void noteAlarmFinishLocked(String name, int uid) {
+ public void noteAlarmFinishLocked(String name, WorkSource workSource, int uid) {
+ noteAlarmStartOrFinishLocked(HistoryItem.EVENT_ALARM_FINISH, name, workSource, uid);
+ }
+
+ private void noteAlarmStartOrFinishLocked(int historyItem, String name, WorkSource workSource,
+ int uid) {
if (!mRecordAllHistory) {
return;
}
- uid = mapUid(uid);
+
final long elapsedRealtime = mClocks.elapsedRealtime();
final long uptime = mClocks.uptimeMillis();
- if (!mActiveEvents.updateState(HistoryItem.EVENT_ALARM_FINISH, name, uid, 0)) {
+
+ if (workSource != null) {
+ for (int i = 0; i < workSource.size(); ++i) {
+ uid = mapUid(workSource.get(i));
+ if (mActiveEvents.updateState(historyItem, name, uid, 0)) {
+ addHistoryEventLocked(elapsedRealtime, uptime, historyItem, name, uid);
+ }
+ }
+
+ List<WorkChain> workChains = workSource.getWorkChains();
+ if (workChains != null) {
+ for (int i = 0; i < workChains.size(); ++i) {
+ uid = mapUid(workChains.get(i).getAttributionUid());
+ if (mActiveEvents.updateState(historyItem, name, uid, 0)) {
+ addHistoryEventLocked(elapsedRealtime, uptime, historyItem, name, uid);
+ }
+ }
+ }
+ } else {
+ uid = mapUid(uid);
+
+ if (mActiveEvents.updateState(historyItem, name, uid, 0)) {
+ addHistoryEventLocked(elapsedRealtime, uptime, historyItem, name, uid);
+ }
+ }
+ }
+
+ public void noteWakupAlarmLocked(String packageName, int uid, WorkSource workSource,
+ String tag) {
+ if (!isOnBattery()) {
return;
}
- addHistoryEventLocked(elapsedRealtime, uptime, HistoryItem.EVENT_ALARM_FINISH, name, uid);
+
+ if (workSource != null) {
+ for (int i = 0; i < workSource.size(); ++i) {
+ uid = workSource.get(i);
+ final String workSourceName = workSource.getName(i);
+
+ BatteryStatsImpl.Uid.Pkg pkg = getPackageStatsLocked(uid,
+ workSourceName != null ? workSourceName : packageName);
+ pkg.noteWakeupAlarmLocked(tag);
+
+ StatsLog.write(StatsLog.WAKEUP_ALARM_OCCURRED, uid, tag);
+ }
+
+ ArrayList<WorkChain> workChains = workSource.getWorkChains();
+ if (workChains != null) {
+ for (int i = 0; i < workChains.size(); ++i) {
+ final WorkChain wc = workChains.get(i);
+ uid = wc.getAttributionUid();
+
+ BatteryStatsImpl.Uid.Pkg pkg = getPackageStatsLocked(uid, packageName);
+ pkg.noteWakeupAlarmLocked(tag);
+
+ // TODO(statsd): Log the full attribution chain here once it's available
+ StatsLog.write(StatsLog.WAKEUP_ALARM_OCCURRED, uid, tag);
+ }
+ }
+ } else {
+ BatteryStatsImpl.Uid.Pkg pkg = getPackageStatsLocked(uid, packageName);
+ pkg.noteWakeupAlarmLocked(tag);
+ StatsLog.write(StatsLog.WAKEUP_ALARM_OCCURRED, uid, tag);
+ }
}
private void requestWakelockCpuUpdate() {
@@ -5534,6 +5597,12 @@
if (DEBUG_HISTORY) Slog.v(TAG, "WIFI multicast on to: "
+ Integer.toHexString(mHistoryCur.states));
addHistoryRecordLocked(elapsedRealtime, uptime);
+
+ // Start Wifi Multicast overall timer
+ if (!mWifiMulticastWakelockTimer.isRunningLocked()) {
+ if (DEBUG_HISTORY) Slog.v(TAG, "WiFi Multicast Overall Timer Started");
+ mWifiMulticastWakelockTimer.startRunningLocked(elapsedRealtime);
+ }
}
mWifiMulticastNesting++;
getUidStatsLocked(uid).noteWifiMulticastEnabledLocked(elapsedRealtime);
@@ -5549,6 +5618,12 @@
if (DEBUG_HISTORY) Slog.v(TAG, "WIFI multicast off to: "
+ Integer.toHexString(mHistoryCur.states));
addHistoryRecordLocked(elapsedRealtime, uptime);
+
+ // Stop Wifi Multicast overall timer
+ if (mWifiMulticastWakelockTimer.isRunningLocked()) {
+ if (DEBUG_HISTORY) Slog.v(TAG, "Multicast Overall Timer Stopped");
+ mWifiMulticastWakelockTimer.stopRunningLocked(elapsedRealtime);
+ }
}
getUidStatsLocked(uid).noteWifiMulticastDisabledLocked(elapsedRealtime);
}
@@ -5835,6 +5910,16 @@
return (int)mMobileRadioActiveUnknownCount.getCountLocked(which);
}
+ @Override public long getWifiMulticastWakelockTime(
+ long elapsedRealtimeUs, int which) {
+ return mWifiMulticastWakelockTimer.getTotalTimeLocked(
+ elapsedRealtimeUs, which);
+ }
+
+ @Override public int getWifiMulticastWakelockCount(int which) {
+ return mWifiMulticastWakelockTimer.getCountLocked(which);
+ }
+
@Override public long getWifiOnTime(long elapsedRealtimeUs, int which) {
return mWifiOnTimer.getTotalTimeLocked(elapsedRealtimeUs, which);
}
@@ -9435,6 +9520,8 @@
mMobileRadioActiveAdjustedTime = new LongSamplingCounter(mOnBatteryTimeBase);
mMobileRadioActiveUnknownTime = new LongSamplingCounter(mOnBatteryTimeBase);
mMobileRadioActiveUnknownCount = new LongSamplingCounter(mOnBatteryTimeBase);
+ mWifiMulticastWakelockTimer = new StopwatchTimer(mClocks, null,
+ WIFI_AGGREGATE_MULTICAST_ENABLED, null, mOnBatteryTimeBase);
mWifiOnTimer = new StopwatchTimer(mClocks, null, -4, null, mOnBatteryTimeBase);
mGlobalWifiRunningTimer = new StopwatchTimer(mClocks, null, -5, null, mOnBatteryTimeBase);
for (int i=0; i<NUM_WIFI_STATES; i++) {
@@ -10136,6 +10223,7 @@
for (int i=0; i<NUM_WIFI_SIGNAL_STRENGTH_BINS; i++) {
mWifiSignalStrengthsTimer[i].reset(false);
}
+ mWifiMulticastWakelockTimer.reset(false);
mWifiActivity.reset(false);
mBluetoothActivity.reset(false);
mModemActivity.reset(false);
@@ -12582,6 +12670,7 @@
mMobileRadioActiveAdjustedTime.readSummaryFromParcelLocked(in);
mMobileRadioActiveUnknownTime.readSummaryFromParcelLocked(in);
mMobileRadioActiveUnknownCount.readSummaryFromParcelLocked(in);
+ mWifiMulticastWakelockTimer.readSummaryFromParcelLocked(in);
mWifiRadioPowerState = DataConnectionRealTimeInfo.DC_POWER_STATE_LOW;
mWifiOn = false;
mWifiOnTimer.readSummaryFromParcelLocked(in);
@@ -13022,6 +13111,7 @@
mMobileRadioActiveAdjustedTime.writeSummaryFromParcelLocked(out);
mMobileRadioActiveUnknownTime.writeSummaryFromParcelLocked(out);
mMobileRadioActiveUnknownCount.writeSummaryFromParcelLocked(out);
+ mWifiMulticastWakelockTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
mWifiOnTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
mGlobalWifiRunningTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
for (int i=0; i<NUM_WIFI_STATES; i++) {
@@ -13485,6 +13575,8 @@
mMobileRadioActiveAdjustedTime = new LongSamplingCounter(mOnBatteryTimeBase, in);
mMobileRadioActiveUnknownTime = new LongSamplingCounter(mOnBatteryTimeBase, in);
mMobileRadioActiveUnknownCount = new LongSamplingCounter(mOnBatteryTimeBase, in);
+ mWifiMulticastWakelockTimer = new StopwatchTimer(mClocks, null, -4, null,
+ mOnBatteryTimeBase, in);
mWifiRadioPowerState = DataConnectionRealTimeInfo.DC_POWER_STATE_LOW;
mWifiOn = false;
mWifiOnTimer = new StopwatchTimer(mClocks, null, -4, null, mOnBatteryTimeBase, in);
@@ -13691,6 +13783,7 @@
mMobileRadioActiveAdjustedTime.writeToParcel(out);
mMobileRadioActiveUnknownTime.writeToParcel(out);
mMobileRadioActiveUnknownCount.writeToParcel(out);
+ mWifiMulticastWakelockTimer.writeToParcel(out, uSecRealtime);
mWifiOnTimer.writeToParcel(out, uSecRealtime);
mGlobalWifiRunningTimer.writeToParcel(out, uSecRealtime);
for (int i=0; i<NUM_WIFI_STATES; i++) {
@@ -13877,6 +13970,8 @@
mMobileRadioActiveTimer.logState(pr, " ");
pr.println("*** Mobile network active adjusted timer:");
mMobileRadioActiveAdjustedTime.logState(pr, " ");
+ pr.println("*** Wifi Multicast WakeLock Timer:");
+ mWifiMulticastWakelockTimer.logState(pr, " ");
pr.println("*** mWifiRadioPowerState=" + mWifiRadioPowerState);
pr.println("*** Wifi timer:");
mWifiOnTimer.logState(pr, " ");
diff --git a/core/java/com/android/internal/widget/ButtonBarLayout.java b/core/java/com/android/internal/widget/ButtonBarLayout.java
index 82affe2..ab8be33 100644
--- a/core/java/com/android/internal/widget/ButtonBarLayout.java
+++ b/core/java/com/android/internal/widget/ButtonBarLayout.java
@@ -30,9 +30,6 @@
* orientation when it can't fit its child views horizontally.
*/
public class ButtonBarLayout extends LinearLayout {
- /** Minimum screen height required for button stacking. */
- private static final int ALLOW_STACKING_MIN_HEIGHT_DP = 320;
-
/** Amount of the second button to "peek" above the fold when stacked. */
private static final int PEEK_BUTTON_DP = 16;
@@ -46,12 +43,8 @@
public ButtonBarLayout(Context context, AttributeSet attrs) {
super(context, attrs);
- final boolean allowStackingDefault =
- context.getResources().getConfiguration().screenHeightDp
- >= ALLOW_STACKING_MIN_HEIGHT_DP;
final TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.ButtonBarLayout);
- mAllowStacking = ta.getBoolean(R.styleable.ButtonBarLayout_allowStacking,
- allowStackingDefault);
+ mAllowStacking = ta.getBoolean(R.styleable.ButtonBarLayout_allowStacking, true);
ta.recycle();
}
diff --git a/core/proto/android/os/batterystats.proto b/core/proto/android/os/batterystats.proto
index 0fa428e..da21258 100644
--- a/core/proto/android/os/batterystats.proto
+++ b/core/proto/android/os/batterystats.proto
@@ -537,7 +537,24 @@
// Screen-off CPU time in milliseconds.
optional int64 screen_off_duration_ms = 3;
}
+ // CPU times accumulated across all process states.
repeated ByFrequency by_frequency = 3;
+
+ enum ProcessState {
+ TOP = 0;
+ FOREGROUND_SERVICE = 1;
+ FOREGROUND = 2;
+ BACKGROUND = 3;
+ TOP_SLEEPING = 4;
+ HEAVY_WEIGHT = 5;
+ CACHED = 6;
+ }
+ // CPU times at different process states.
+ message ByProcessState {
+ optional ProcessState process_state = 1;
+ repeated ByFrequency by_frequency = 2;
+ }
+ repeated ByProcessState by_process_state = 4;
}
optional Cpu cpu = 7;
diff --git a/core/proto/android/os/batterytype.proto b/core/proto/android/os/batterytype.proto
new file mode 100644
index 0000000..75d0dd3
--- /dev/null
+++ b/core/proto/android/os/batterytype.proto
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+syntax = "proto2";
+
+package android.os;
+
+option java_multiple_files = true;
+
+message BatteryTypeProto {
+ optional string type = 1;
+}
diff --git a/core/proto/android/os/cpuinfo.proto b/core/proto/android/os/cpuinfo.proto
index 522ff24..cd151e2 100644
--- a/core/proto/android/os/cpuinfo.proto
+++ b/core/proto/android/os/cpuinfo.proto
@@ -81,7 +81,9 @@
optional string virt = 8; // virtual memory size, i.e. 14.0G, 13.5M
optional string res = 9; // Resident size, i.e. 0, 3.1G
- // How Android memory manager will treat the task
+ // How Android memory manager will treat the task.
+ // TODO: use PsDumpProto.Process.Policy instead once we extern variables
+ // and are able to include the same .h file in two files.
enum Policy {
POLICY_UNKNOWN = 0;
POLICY_fg = 1; // foreground, the name is lower case for parsing the value
diff --git a/core/proto/android/os/incident.proto b/core/proto/android/os/incident.proto
index ac8f26d..a6db31f 100644
--- a/core/proto/android/os/incident.proto
+++ b/core/proto/android/os/incident.proto
@@ -18,12 +18,14 @@
option java_multiple_files = true;
option java_outer_classname = "IncidentProtoMetadata";
+import "frameworks/base/core/proto/android/os/batterytype.proto";
import "frameworks/base/core/proto/android/os/cpufreq.proto";
import "frameworks/base/core/proto/android/os/cpuinfo.proto";
import "frameworks/base/core/proto/android/os/incidentheader.proto";
import "frameworks/base/core/proto/android/os/kernelwake.proto";
import "frameworks/base/core/proto/android/os/pagetypeinfo.proto";
import "frameworks/base/core/proto/android/os/procrank.proto";
+import "frameworks/base/core/proto/android/os/ps.proto";
import "frameworks/base/core/proto/android/os/system_properties.proto";
import "frameworks/base/core/proto/android/providers/settings.proto";
import "frameworks/base/core/proto/android/server/activitymanagerservice.proto";
@@ -65,7 +67,7 @@
// Device information
optional SystemPropertiesProto system_properties = 1000 [
(section).type = SECTION_COMMAND,
- (section).args = "/system/bin/getprop"
+ (section).args = "getprop"
];
// Linux services
@@ -86,7 +88,7 @@
optional CpuInfo cpu_info = 2003 [
(section).type = SECTION_COMMAND,
- (section).args = "/system/bin/top -b -n 1 -H -s 6 -o pid,tid,user,pr,ni,%cpu,s,virt,res,pcy,cmd,name"
+ (section).args = "top -b -n 1 -H -s 6 -o pid,tid,user,pr,ni,%cpu,s,virt,res,pcy,cmd,name"
];
optional CpuFreq cpu_freq = 2004 [
@@ -94,6 +96,16 @@
(section).args = "/sys/devices/system/cpu/cpufreq/all_time_in_state"
];
+ optional PsDumpProto processes_and_threads = 2005 [
+ (section).type = SECTION_COMMAND,
+ (section).args = "ps -A -T -Z -O pri,nice,rtprio,sched,pcy,time"
+ ];
+
+ optional BatteryTypeProto battery_type = 2006 [
+ (section).type = SECTION_FILE,
+ (section).args = "/sys/class/power_supply/bms/battery_type"
+ ];
+
// System Services
optional com.android.server.fingerprint.FingerprintServiceDumpProto fingerprint = 3000 [
(section).type = SECTION_DUMPSYS,
diff --git a/core/proto/android/os/ps.proto b/core/proto/android/os/ps.proto
new file mode 100644
index 0000000..88c6609
--- /dev/null
+++ b/core/proto/android/os/ps.proto
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+syntax = "proto2";
+
+package android.os;
+
+option java_multiple_files = true;
+
+message PsDumpProto {
+ message Process {
+ // Security label, most commonly used for SELinux context data.
+ optional string label = 1;
+ optional string user = 2;
+ // Process ID number.
+ optional int32 pid = 3;
+ // The unique number representing a dispatchable entity (alias lwp,
+ // spid). This value may also appear as: a process ID (pid); a process
+ // group ID (pgrp); a session ID for the session leader (sid); a thread
+ // group ID for the thread group leader (tgid); and a tty process group
+ // ID for the process group leader (tpgid).
+ optional int32 tid = 4;
+ // Parent process ID.
+ optional int32 ppid = 5;
+ // Virtual set size (memory size) of the process, in KiB.
+ optional int32 vsz = 6;
+ // Resident set size. How many physical pages are associated with the
+ // process; real memory usage, in KiB.
+ optional int32 rss = 7;
+ // Name of the kernel function in which the process is sleeping, a "-"
+ // if the process is running, or a "*" if the process is multi-threaded
+ // and ps is not displaying threads.
+ optional string wchan = 8;
+ // Memory address of the process.
+ optional string addr = 9;
+
+ enum ProcessStateCode {
+ STATE_UNKNOWN = 0;
+ // Uninterruptible sleep (usually IO).
+ STATE_D = 1;
+ // Running or runnable (on run queue).
+ STATE_R = 2;
+ // Interruptible sleep (waiting for an event to complete).
+ STATE_S = 3;
+ // Stopped by job control signal.
+ STATE_T = 4;
+ // Stopped by debugger during the tracing.
+ STATE_TRACING = 5;
+ // Dead (should never be seen).
+ STATE_X = 6;
+ // Defunct ("zombie") process. Terminated but not reaped by its
+ // parent.
+ STATE_Z = 7;
+ }
+ // Minimal state display
+ optional ProcessStateCode s = 10;
+ // Priority of the process. Higher number means lower priority.
+ optional int32 pri = 11;
+ // Nice value. This ranges from 19 (nicest) to -20 (not nice to others).
+ optional sint32 ni = 12;
+ // Realtime priority.
+ optional string rtprio = 13; // Number or -
+
+ enum SchedulingPolicy {
+ option allow_alias = true;
+
+ // Regular names conflict with macros defined in
+ // bionic/libc/kernel/uapi/linux/sched.h.
+ SCH_OTHER = 0;
+ SCH_NORMAL = 0;
+
+ SCH_FIFO = 1;
+ SCH_RR = 2;
+ SCH_BATCH = 3;
+ SCH_ISO = 4;
+ SCH_IDLE = 5;
+ }
+ // Scheduling policy of the process.
+ optional SchedulingPolicy sch = 14;
+
+ // How Android memory manager will treat the task
+ enum Policy {
+ POLICY_UNKNOWN = 0;
+ // Foreground.
+ POLICY_FG = 1;
+ // Background.
+ POLICY_BG = 2;
+ POLICY_TA = 3; // TODO: figure out what this value is
+ }
+ optional Policy pcy = 15;
+ // Total CPU time, "[DD-]HH:MM:SS" format.
+ optional string time = 16;
+ // Command with all its arguments as a string.
+ optional string cmd = 17;
+ }
+ repeated Process processes = 1;
+}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 272e3c7..52bfcde 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -3870,6 +3870,14 @@
</intent-filter>
</receiver>
+ <receiver android:name="com.android.server.updates.NetworkWatchlistInstallReceiver"
+ android:permission="android.permission.UPDATE_CONFIG">
+ <intent-filter>
+ <action android:name="android.intent.action.UPDATE_NETWORK_WATCHLIST" />
+ <data android:scheme="content" android:host="*" android:mimeType="*/*" />
+ </intent-filter>
+ </receiver>
+
<receiver android:name="com.android.server.updates.ApnDbInstallReceiver"
android:permission="android.permission.UPDATE_CONFIG">
<intent-filter>
diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
index eef9866..fc86500 100644
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -525,7 +525,8 @@
Settings.Secure.VOICE_INTERACTION_SERVICE,
Settings.Secure.VOICE_RECOGNITION_SERVICE,
Settings.Secure.INSTANT_APPS_ENABLED,
- Settings.Secure.BACKUP_MANAGER_CONSTANTS);
+ Settings.Secure.BACKUP_MANAGER_CONSTANTS,
+ Settings.Secure.KEYGUARD_SLICE_URI);
@Test
public void systemSettingsBackedUpOrBlacklisted() {
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
index 0afec34..a55563a 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
@@ -15,16 +15,19 @@
*/
package com.android.internal.os;
+import static android.os.BatteryStats.STATS_CURRENT;
import static android.os.BatteryStats.STATS_SINCE_CHARGED;
import static android.os.BatteryStats.WAKE_TYPE_PARTIAL;
import android.app.ActivityManager;
import android.os.BatteryManager;
import android.os.BatteryStats;
+import android.os.BatteryStats.HistoryItem;
import android.os.WorkSource;
import android.support.test.filters.SmallTest;
import android.view.Display;
+import com.android.internal.os.BatteryStatsImpl.Uid;
import junit.framework.TestCase;
import java.util.ArrayList;
@@ -44,11 +47,14 @@
* Run: adb shell am instrument -e class com.android.internal.os.BatteryStatsNoteTest -w \
* com.android.frameworks.coretests/android.support.test.runner.AndroidJUnitRunner
*/
-public class BatteryStatsNoteTest extends TestCase{
+public class BatteryStatsNoteTest extends TestCase {
+
private static final int UID = 10500;
private static final WorkSource WS = new WorkSource(UID);
- /** Test BatteryStatsImpl.Uid.noteBluetoothScanResultLocked. */
+ /**
+ * Test BatteryStatsImpl.Uid.noteBluetoothScanResultLocked.
+ */
@SmallTest
public void testNoteBluetoothScanResultLocked() throws Exception {
MockBatteryStatsImpl bi = new MockBatteryStatsImpl(new MockClocks());
@@ -75,7 +81,9 @@
.getCountLocked(STATS_SINCE_CHARGED));
}
- /** Test BatteryStatsImpl.Uid.noteStartWakeLocked. */
+ /**
+ * Test BatteryStatsImpl.Uid.noteStartWakeLocked.
+ */
@SmallTest
public void testNoteStartWakeLocked() throws Exception {
final MockClocks clocks = new MockClocks(); // holds realtime and uptime in ms
@@ -86,7 +94,8 @@
bi.updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0);
bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_TOP);
- bi.getUidStatsLocked(UID).noteStartWakeLocked(pid, name, WAKE_TYPE_PARTIAL, clocks.realtime);
+ bi.getUidStatsLocked(UID)
+ .noteStartWakeLocked(pid, name, WAKE_TYPE_PARTIAL, clocks.realtime);
clocks.realtime = clocks.uptime = 100;
bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
@@ -94,7 +103,8 @@
clocks.realtime = clocks.uptime = 220;
bi.getUidStatsLocked(UID).noteStopWakeLocked(pid, name, WAKE_TYPE_PARTIAL, clocks.realtime);
- BatteryStats.Timer aggregTimer = bi.getUidStats().get(UID).getAggregatedPartialWakelockTimer();
+ BatteryStats.Timer aggregTimer = bi.getUidStats().get(UID)
+ .getAggregatedPartialWakelockTimer();
long actualTime = aggregTimer.getTotalTimeLocked(300_000, STATS_SINCE_CHARGED);
long bgTime = aggregTimer.getSubTimer().getTotalTimeLocked(300_000, STATS_SINCE_CHARGED);
assertEquals(220_000, actualTime);
@@ -102,7 +112,9 @@
}
- /** Test BatteryStatsImpl.noteUidProcessStateLocked. */
+ /**
+ * Test BatteryStatsImpl.noteUidProcessStateLocked.
+ */
@SmallTest
public void testNoteUidProcessStateLocked() throws Exception {
final MockClocks clocks = new MockClocks();
@@ -145,7 +157,6 @@
expectedRunTimeMs = stateRuntimeMap.get(ActivityManager.PROCESS_STATE_TOP);
assertEquals(expectedRunTimeMs * 1000, actualRunTimeUs);
-
actualRunTimeUs = uid.getProcessStateTime(BatteryStats.Uid.PROCESS_STATE_FOREGROUND_SERVICE,
elapsedTimeUs, STATS_SINCE_CHARGED);
expectedRunTimeMs = stateRuntimeMap.get(
@@ -153,19 +164,16 @@
+ stateRuntimeMap.get(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
assertEquals(expectedRunTimeMs * 1000, actualRunTimeUs);
-
actualRunTimeUs = uid.getProcessStateTime(BatteryStats.Uid.PROCESS_STATE_TOP_SLEEPING,
elapsedTimeUs, STATS_SINCE_CHARGED);
expectedRunTimeMs = stateRuntimeMap.get(ActivityManager.PROCESS_STATE_TOP_SLEEPING);
assertEquals(expectedRunTimeMs * 1000, actualRunTimeUs);
-
actualRunTimeUs = uid.getProcessStateTime(BatteryStats.Uid.PROCESS_STATE_FOREGROUND,
elapsedTimeUs, STATS_SINCE_CHARGED);
expectedRunTimeMs = stateRuntimeMap.get(ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND);
assertEquals(expectedRunTimeMs * 1000, actualRunTimeUs);
-
actualRunTimeUs = uid.getProcessStateTime(BatteryStats.Uid.PROCESS_STATE_BACKGROUND,
elapsedTimeUs, STATS_SINCE_CHARGED);
expectedRunTimeMs = stateRuntimeMap.get(ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND)
@@ -174,7 +182,6 @@
+ stateRuntimeMap.get(ActivityManager.PROCESS_STATE_RECEIVER);
assertEquals(expectedRunTimeMs * 1000, actualRunTimeUs);
-
actualRunTimeUs = uid.getProcessStateTime(BatteryStats.Uid.PROCESS_STATE_CACHED,
elapsedTimeUs, STATS_SINCE_CHARGED);
expectedRunTimeMs = stateRuntimeMap.get(ActivityManager.PROCESS_STATE_HOME)
@@ -191,7 +198,9 @@
assertEquals(expectedRunTimeMs * 1000, actualRunTimeUs);
}
- /** Test BatteryStatsImpl.updateTimeBasesLocked. */
+ /**
+ * Test BatteryStatsImpl.updateTimeBasesLocked.
+ */
@SmallTest
public void testUpdateTimeBasesLocked() throws Exception {
final MockClocks clocks = new MockClocks(); // holds realtime and uptime in ms
@@ -213,7 +222,9 @@
assertTrue(bi.getOnBatteryScreenOffTimeBase().isRunning());
}
- /** Test BatteryStatsImpl.noteScreenStateLocked sets timebases and screen states correctly. */
+ /**
+ * Test BatteryStatsImpl.noteScreenStateLocked sets timebases and screen states correctly.
+ */
@SmallTest
public void testNoteScreenStateLocked() throws Exception {
final MockClocks clocks = new MockClocks(); // holds realtime and uptime in ms
@@ -232,15 +243,13 @@
assertEquals(bi.getScreenState(), Display.STATE_OFF);
}
- /** Test BatteryStatsImpl.noteScreenStateLocked updates timers correctly.
+ /**
+ * Test BatteryStatsImpl.noteScreenStateLocked updates timers correctly.
*
- * Unknown and doze should both be subset of off state
+ * Unknown and doze should both be subset of off state
*
- * Timeline 0----100----200----310----400------------1000
- * Unknown -------
- * On -------
- * Off ------- ----------------------
- * Doze ----------------
+ * Timeline 0----100----200----310----400------------1000 Unknown ------- On ------- Off
+ * ------- ---------------------- Doze ----------------
*/
@SmallTest
public void testNoteScreenStateTimersLocked() throws Exception {
@@ -280,4 +289,161 @@
assertEquals(600_000, bi.getScreenDozeTime(1500_000, STATS_SINCE_CHARGED));
}
+ @SmallTest
+ public void testAlarmStartAndFinishLocked() throws Exception {
+ final MockClocks clocks = new MockClocks(); // holds realtime and uptime in ms
+ MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
+ bi.setRecordAllHistoryLocked(true);
+ bi.forceRecordAllHistory();
+
+ bi.updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0);
+ bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_TOP);
+
+ clocks.realtime = clocks.uptime = 100;
+ bi.noteAlarmStartLocked("foo", null, UID);
+ clocks.realtime = clocks.uptime = 5000;
+ bi.noteAlarmFinishLocked("foo", null, UID);
+
+ HistoryItem item = new HistoryItem();
+ assertTrue(bi.startIteratingHistoryLocked());
+
+ assertTrue(bi.getNextHistoryLocked(item));
+ assertEquals(HistoryItem.EVENT_ALARM_START, item.eventCode);
+ assertEquals("foo", item.eventTag.string);
+ assertEquals(UID, item.eventTag.uid);
+
+ // TODO(narayan): Figure out why this event is written to the history buffer. See
+ // test below where it is being interspersed between multiple START events too.
+ assertTrue(bi.getNextHistoryLocked(item));
+ assertEquals(HistoryItem.EVENT_NONE, item.eventCode);
+
+ assertTrue(bi.getNextHistoryLocked(item));
+ assertEquals(HistoryItem.EVENT_ALARM_FINISH, item.eventCode);
+ assertTrue(item.isDeltaData());
+ assertEquals("foo", item.eventTag.string);
+ assertEquals(UID, item.eventTag.uid);
+
+ assertFalse(bi.getNextHistoryLocked(item));
+ }
+
+ @SmallTest
+ public void testAlarmStartAndFinishLocked_workSource() throws Exception {
+ final MockClocks clocks = new MockClocks(); // holds realtime and uptime in ms
+ MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
+ bi.setRecordAllHistoryLocked(true);
+ bi.forceRecordAllHistory();
+
+ bi.updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0);
+ bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_TOP);
+
+ WorkSource ws = new WorkSource();
+ ws.add(100);
+ ws.createWorkChain().addNode(500, "tag");
+ bi.noteAlarmStartLocked("foo", ws, UID);
+ clocks.realtime = clocks.uptime = 5000;
+ bi.noteAlarmFinishLocked("foo", ws, UID);
+
+ HistoryItem item = new HistoryItem();
+ assertTrue(bi.startIteratingHistoryLocked());
+
+ assertTrue(bi.getNextHistoryLocked(item));
+ assertEquals(HistoryItem.EVENT_ALARM_START, item.eventCode);
+ assertEquals("foo", item.eventTag.string);
+ assertEquals(100, item.eventTag.uid);
+
+ assertTrue(bi.getNextHistoryLocked(item));
+ assertEquals(HistoryItem.EVENT_NONE, item.eventCode);
+
+ assertTrue(bi.getNextHistoryLocked(item));
+ assertEquals(HistoryItem.EVENT_ALARM_START, item.eventCode);
+ assertEquals("foo", item.eventTag.string);
+ assertEquals(500, item.eventTag.uid);
+
+ assertTrue(bi.getNextHistoryLocked(item));
+ assertEquals(HistoryItem.EVENT_ALARM_FINISH, item.eventCode);
+ assertEquals("foo", item.eventTag.string);
+ assertEquals(100, item.eventTag.uid);
+
+ assertTrue(bi.getNextHistoryLocked(item));
+ assertEquals(HistoryItem.EVENT_ALARM_FINISH, item.eventCode);
+ assertEquals("foo", item.eventTag.string);
+ assertEquals(500, item.eventTag.uid);
+ }
+
+ @SmallTest
+ public void testNoteWakupAlarmLocked() {
+ final MockClocks clocks = new MockClocks(); // holds realtime and uptime in ms
+ MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
+ bi.setRecordAllHistoryLocked(true);
+ bi.forceRecordAllHistory();
+ bi.mForceOnBattery = true;
+
+ bi.updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0);
+ bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_TOP);
+
+ bi.noteWakupAlarmLocked("com.foo.bar", UID, null, "tag");
+
+ Uid.Pkg pkg = bi.getPackageStatsLocked(UID, "com.foo.bar");
+ assertEquals(1, pkg.getWakeupAlarmStats().get("tag").getCountLocked(STATS_CURRENT));
+ assertEquals(1, pkg.getWakeupAlarmStats().size());
+ }
+
+ @SmallTest
+ public void testNoteWakupAlarmLocked_workSource_uid() {
+ final MockClocks clocks = new MockClocks(); // holds realtime and uptime in ms
+ MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
+ bi.setRecordAllHistoryLocked(true);
+ bi.forceRecordAllHistory();
+ bi.mForceOnBattery = true;
+
+ bi.updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0);
+ bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_TOP);
+
+ WorkSource ws = new WorkSource();
+ ws.add(100);
+
+ // When a WorkSource is present, "UID" should not be used - only the uids present in the
+ // WorkSource should be reported.
+ bi.noteWakupAlarmLocked("com.foo.bar", UID, ws, "tag");
+ Uid.Pkg pkg = bi.getPackageStatsLocked(UID, "com.foo.bar");
+ assertEquals(0, pkg.getWakeupAlarmStats().size());
+ pkg = bi.getPackageStatsLocked(100, "com.foo.bar");
+ assertEquals(1, pkg.getWakeupAlarmStats().size());
+
+ // If the WorkSource contains a "name", it should be interpreted as a package name and
+ // the packageName supplied as an argument must be ignored.
+ ws = new WorkSource();
+ ws.add(100, "com.foo.baz_alternate");
+ bi.noteWakupAlarmLocked("com.foo.baz", UID, ws, "tag");
+ pkg = bi.getPackageStatsLocked(100, "com.foo.baz");
+ assertEquals(0, pkg.getWakeupAlarmStats().size());
+ pkg = bi.getPackageStatsLocked(100, "com.foo.baz_alternate");
+ assertEquals(1, pkg.getWakeupAlarmStats().size());
+ }
+
+ @SmallTest
+ public void testNoteWakupAlarmLocked_workSource_workChain() {
+ final MockClocks clocks = new MockClocks(); // holds realtime and uptime in ms
+ MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
+ bi.setRecordAllHistoryLocked(true);
+ bi.forceRecordAllHistory();
+ bi.mForceOnBattery = true;
+
+ bi.updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0);
+ bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_TOP);
+
+ WorkSource ws = new WorkSource();
+ ws.createWorkChain().addNode(100, "com.foo.baz_alternate");
+ bi.noteWakupAlarmLocked("com.foo.bar", UID, ws, "tag");
+
+ // For WorkChains, again we must only attribute to the uids present in the WorkSource
+ // (and not to "UID"). However, unlike the older "tags" we do not change the packagename
+ // supplied as an argument, given that we're logging the entire attribution chain.
+ Uid.Pkg pkg = bi.getPackageStatsLocked(UID, "com.foo.bar");
+ assertEquals(0, pkg.getWakeupAlarmStats().size());
+ pkg = bi.getPackageStatsLocked(100, "com.foo.bar");
+ assertEquals(1, pkg.getWakeupAlarmStats().size());
+ pkg = bi.getPackageStatsLocked(100, "com.foo.baz_alternate");
+ assertEquals(0, pkg.getWakeupAlarmStats().size());
+ }
}
diff --git a/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java b/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
index de2fd12..f19ff67 100644
--- a/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
+++ b/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
@@ -16,6 +16,8 @@
package com.android.internal.os;
+import android.os.Handler;
+import android.os.Looper;
import android.util.SparseIntArray;
import java.util.ArrayList;
@@ -37,6 +39,9 @@
mOnBatteryTimeBase);
mBluetoothScanTimer = new StopwatchTimer(mClocks, null, -14, null, mOnBatteryTimeBase);
setExternalStatsSyncLocked(new DummyExternalStatsSync());
+
+ // A no-op handler.
+ mHandler = new Handler(Looper.getMainLooper()) {};
}
MockBatteryStatsImpl() {
@@ -59,6 +64,12 @@
return mForceOnBattery ? true : super.isOnBattery();
}
+ public void forceRecordAllHistory() {
+ mHaveBatteryLevel = true;
+ mRecordingHistory = true;
+ mRecordAllHistory = true;
+ }
+
public TimeBase getOnBatteryBackgroundTimeBase(int uid) {
return getUidStatsLocked(uid).mOnBatteryBackgroundTimeBase;
}
diff --git a/libs/hwui/tests/common/TestUtils.cpp b/libs/hwui/tests/common/TestUtils.cpp
index 1e42425..4a0d6ee 100644
--- a/libs/hwui/tests/common/TestUtils.cpp
+++ b/libs/hwui/tests/common/TestUtils.cpp
@@ -122,15 +122,19 @@
void TestUtils::drawUtf8ToCanvas(Canvas* canvas, const char* text, const SkPaint& paint, float x,
float y) {
auto utf16 = asciiToUtf16(text);
- canvas->drawText(utf16.get(), 0, strlen(text), strlen(text), x, y, minikin::Bidi::LTR, paint,
- nullptr);
+ SkPaint glyphPaint(paint);
+ glyphPaint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
+ canvas->drawText(utf16.get(), 0, strlen(text), strlen(text), x, y, minikin::Bidi::LTR,
+ glyphPaint, nullptr);
}
void TestUtils::drawUtf8ToCanvas(Canvas* canvas, const char* text, const SkPaint& paint,
const SkPath& path) {
auto utf16 = asciiToUtf16(text);
- canvas->drawTextOnPath(utf16.get(), strlen(text), minikin::Bidi::LTR, path, 0, 0, paint,
- nullptr);
+ SkPaint glyphPaint(paint);
+ glyphPaint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
+ canvas->drawTextOnPath(utf16.get(), strlen(text), minikin::Bidi::LTR, path, 0, 0, glyphPaint,
+ nullptr);
}
void TestUtils::TestTask::run() {
diff --git a/libs/hwui/tests/common/scenes/GlyphStressAnimation.cpp b/libs/hwui/tests/common/scenes/GlyphStressAnimation.cpp
index bf0aed9..38999cb 100644
--- a/libs/hwui/tests/common/scenes/GlyphStressAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/GlyphStressAnimation.cpp
@@ -40,22 +40,18 @@
}
void doFrame(int frameNr) override {
- std::unique_ptr<uint16_t[]> text =
- TestUtils::asciiToUtf16("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ");
- ssize_t textLength = 26 * 2;
+ const char* text = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
std::unique_ptr<Canvas> canvas(
Canvas::create_recording_canvas(container->stagingProperties().getWidth(),
container->stagingProperties().getHeight()));
Paint paint;
- paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
paint.setAntiAlias(true);
paint.setColor(Color::Black);
for (int i = 0; i < 5; i++) {
paint.setTextSize(10 + (frameNr % 20) + i * 20);
- canvas->drawText(text.get(), 0, textLength, textLength, 0, 100 * (i + 2),
- minikin::Bidi::FORCE_LTR, paint, nullptr);
+ TestUtils::drawUtf8ToCanvas(canvas.get(), text, paint, 0, 100 * (i + 2));
}
container->setStagingDisplayList(canvas->finishRecording());
diff --git a/libs/hwui/tests/common/scenes/ListOfFadedTextAnimation.cpp b/libs/hwui/tests/common/scenes/ListOfFadedTextAnimation.cpp
index 6bae80c..58c9980 100644
--- a/libs/hwui/tests/common/scenes/ListOfFadedTextAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/ListOfFadedTextAnimation.cpp
@@ -36,7 +36,6 @@
SkPaint textPaint;
textPaint.setTextSize(dp(20));
textPaint.setAntiAlias(true);
- textPaint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
TestUtils::drawUtf8ToCanvas(&canvas, "not that long long text", textPaint, dp(10), dp(30));
SkPoint pts[2];
diff --git a/libs/hwui/tests/common/scenes/ListViewAnimation.cpp b/libs/hwui/tests/common/scenes/ListViewAnimation.cpp
index d7ec288..fd8c252 100644
--- a/libs/hwui/tests/common/scenes/ListViewAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/ListViewAnimation.cpp
@@ -83,7 +83,6 @@
canvas.drawRoundRect(0, 0, itemWidth, itemHeight, dp(6), dp(6), roundRectPaint);
SkPaint textPaint;
- textPaint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
textPaint.setColor(rand() % 2 ? Color::Black : Color::Grey_500);
textPaint.setTextSize(dp(20));
textPaint.setAntiAlias(true);
diff --git a/libs/hwui/tests/common/scenes/MagnifierAnimation.cpp b/libs/hwui/tests/common/scenes/MagnifierAnimation.cpp
index 9eddc20..aa537b4 100644
--- a/libs/hwui/tests/common/scenes/MagnifierAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/MagnifierAnimation.cpp
@@ -38,7 +38,6 @@
card = TestUtils::createNode(
0, 0, width, height, [&](RenderProperties& props, Canvas& canvas) {
SkPaint paint;
- paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
paint.setAntiAlias(true);
paint.setTextSize(50);
diff --git a/libs/hwui/tests/common/scenes/SaveLayer2Animation.cpp b/libs/hwui/tests/common/scenes/SaveLayer2Animation.cpp
index fee0659..3befce4 100644
--- a/libs/hwui/tests/common/scenes/SaveLayer2Animation.cpp
+++ b/libs/hwui/tests/common/scenes/SaveLayer2Animation.cpp
@@ -42,10 +42,8 @@
mBluePaint.setColor(SkColorSetARGB(255, 0, 0, 255));
mBluePaint.setTextSize(padding);
- mBluePaint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
mGreenPaint.setColor(SkColorSetARGB(255, 0, 255, 0));
mGreenPaint.setTextSize(padding);
- mGreenPaint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
// interleave drawText and drawRect with saveLayer ops
for (int i = 0; i < regions; i++, top += smallRectHeight) {
@@ -54,18 +52,15 @@
canvas.drawColor(SkColorSetARGB(255, 255, 255, 0), SkBlendMode::kSrcOver);
std::string stri = std::to_string(i);
std::string offscreen = "offscreen line " + stri;
- std::unique_ptr<uint16_t[]> offtext = TestUtils::asciiToUtf16(offscreen.c_str());
- canvas.drawText(offtext.get(), 0, offscreen.length(), offscreen.length(), bounds.fLeft,
- top + padding, minikin::Bidi::FORCE_LTR, mBluePaint, nullptr);
+ TestUtils::drawUtf8ToCanvas(&canvas, offscreen.c_str(), mBluePaint, bounds.fLeft,
+ top + padding);
canvas.restore();
canvas.drawRect(bounds.fLeft, top + padding, bounds.fRight,
top + smallRectHeight - padding, mBluePaint);
std::string onscreen = "onscreen line " + stri;
- std::unique_ptr<uint16_t[]> ontext = TestUtils::asciiToUtf16(onscreen.c_str());
- canvas.drawText(ontext.get(), 0, onscreen.length(), onscreen.length(), bounds.fLeft,
- top + smallRectHeight - padding, minikin::Bidi::FORCE_LTR, mGreenPaint,
- nullptr);
+ TestUtils::drawUtf8ToCanvas(&canvas, onscreen.c_str(), mGreenPaint, bounds.fLeft,
+ top + smallRectHeight - padding);
}
}
void doFrame(int frameNr) override {}
diff --git a/libs/hwui/tests/common/scenes/TextAnimation.cpp b/libs/hwui/tests/common/scenes/TextAnimation.cpp
index a502116..a16b1784 100644
--- a/libs/hwui/tests/common/scenes/TextAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/TextAnimation.cpp
@@ -29,7 +29,6 @@
card = TestUtils::createNode(0, 0, width, height, [](RenderProperties& props,
Canvas& canvas) {
SkPaint paint;
- paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
paint.setAntiAlias(true);
paint.setTextSize(50);
diff --git a/libs/hwui/tests/common/scenes/TvApp.cpp b/libs/hwui/tests/common/scenes/TvApp.cpp
index c845e6c..003d8e9 100644
--- a/libs/hwui/tests/common/scenes/TvApp.cpp
+++ b/libs/hwui/tests/common/scenes/TvApp.cpp
@@ -117,7 +117,6 @@
canvas.drawColor(0xFFFFEEEE, SkBlendMode::kSrcOver);
SkPaint paint;
- paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
paint.setAntiAlias(true);
paint.setTextSize(24);
diff --git a/libs/hwui/tests/unit/FrameBuilderTests.cpp b/libs/hwui/tests/unit/FrameBuilderTests.cpp
index e56d2f8..4eb7751 100644
--- a/libs/hwui/tests/unit/FrameBuilderTests.cpp
+++ b/libs/hwui/tests/unit/FrameBuilderTests.cpp
@@ -537,7 +537,6 @@
canvas.save(SaveFlags::MatrixClip);
canvas.clipPath(&path, SkClipOp::kIntersect);
SkPaint paint;
- paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
paint.setAntiAlias(true);
paint.setTextSize(50);
TestUtils::drawUtf8ToCanvas(&canvas, "Test string1", paint, 100, 100);
@@ -569,7 +568,6 @@
auto node = TestUtils::createNode<RecordingCanvas>(0, 0, 400, 400, [](RenderProperties& props,
RecordingCanvas& canvas) {
SkPaint paint;
- paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
paint.setAntiAlias(true);
paint.setTextSize(50);
TestUtils::drawUtf8ToCanvas(&canvas, "Test string1", paint, 100, 0); // will be top clipped
@@ -603,7 +601,6 @@
textPaint.setAntiAlias(true);
textPaint.setTextSize(20);
textPaint.setFlags(textPaint.getFlags() | SkPaint::kStrikeThruText_ReserveFlag);
- textPaint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
for (int i = 0; i < LOOPS; i++) {
TestUtils::drawUtf8ToCanvas(&canvas, "test text", textPaint, 10, 100 * (i + 1));
}
@@ -654,7 +651,6 @@
auto node = TestUtils::createNode<RecordingCanvas>(
0, 0, 400, 400, [](RenderProperties& props, RecordingCanvas& canvas) {
SkPaint paint;
- paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
paint.setAntiAlias(true);
paint.setTextSize(50);
paint.setStrokeWidth(10);
diff --git a/libs/hwui/tests/unit/RecordingCanvasTests.cpp b/libs/hwui/tests/unit/RecordingCanvasTests.cpp
index 5aae15f..8a9e34f 100644
--- a/libs/hwui/tests/unit/RecordingCanvasTests.cpp
+++ b/libs/hwui/tests/unit/RecordingCanvasTests.cpp
@@ -175,7 +175,6 @@
SkPaint paint;
paint.setAntiAlias(true);
paint.setTextSize(20);
- paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
TestUtils::drawUtf8ToCanvas(&canvas, "test text", paint, 25, 25);
});
@@ -196,7 +195,6 @@
SkPaint paint;
paint.setAntiAlias(true);
paint.setTextSize(20);
- paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 2; j++) {
uint32_t flags = paint.getFlags();
@@ -238,7 +236,6 @@
SkPaint paint;
paint.setAntiAlias(true);
paint.setTextSize(20);
- paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
paint.setTextAlign(SkPaint::kLeft_Align);
TestUtils::drawUtf8ToCanvas(&canvas, "test text", paint, 25, 25);
paint.setTextAlign(SkPaint::kCenter_Align);
@@ -805,9 +802,7 @@
Paint paint;
paint.setAntiAlias(true);
paint.setTextSize(20);
- paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
- std::unique_ptr<uint16_t[]> dst = TestUtils::asciiToUtf16("HELLO");
- canvas.drawText(dst.get(), 0, 5, 5, 25, 25, minikin::Bidi::FORCE_LTR, paint, NULL);
+ TestUtils::drawUtf8ToCanvas(&canvas, "HELLO", paint, 25, 25);
});
int count = 0;
@@ -829,9 +824,7 @@
paint.setColor(SK_ColorWHITE);
paint.setAntiAlias(true);
paint.setTextSize(20);
- paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
- std::unique_ptr<uint16_t[]> dst = TestUtils::asciiToUtf16("HELLO");
- canvas.drawText(dst.get(), 0, 5, 5, 25, 25, minikin::Bidi::FORCE_LTR, paint, NULL);
+ TestUtils::drawUtf8ToCanvas(&canvas, "HELLO", paint, 25, 25);
});
Properties::enableHighContrastText = false;
diff --git a/libs/hwui/tests/unit/SkiaCanvasTests.cpp b/libs/hwui/tests/unit/SkiaCanvasTests.cpp
index 4138f59..1d7dc3d 100644
--- a/libs/hwui/tests/unit/SkiaCanvasTests.cpp
+++ b/libs/hwui/tests/unit/SkiaCanvasTests.cpp
@@ -36,7 +36,6 @@
SkPaint paint;
paint.setAntiAlias(true);
paint.setTextSize(20);
- paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
static const char* text = "testing text bounds";
// draw text directly into Recording canvas
diff --git a/libs/hwui/tests/unit/TextDropShadowCacheTests.cpp b/libs/hwui/tests/unit/TextDropShadowCacheTests.cpp
index 78d75d6..92d05e4 100644
--- a/libs/hwui/tests/unit/TextDropShadowCacheTests.cpp
+++ b/libs/hwui/tests/unit/TextDropShadowCacheTests.cpp
@@ -29,6 +29,7 @@
RENDERTHREAD_OPENGL_PIPELINE_TEST(TextDropShadowCache, addRemove) {
SkPaint paint;
paint.setTextSize(20);
+ paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
GammaFontRenderer gammaFontRenderer;
FontRenderer& fontRenderer = gammaFontRenderer.getFontRenderer();
diff --git a/media/java/android/media/AudioFocusInfo.java b/media/java/android/media/AudioFocusInfo.java
index 6d9c5e2..5d0c8e2 100644
--- a/media/java/android/media/AudioFocusInfo.java
+++ b/media/java/android/media/AudioFocusInfo.java
@@ -130,13 +130,11 @@
dest.writeInt(mSdkTarget);
}
- @SystemApi
@Override
public int hashCode() {
return Objects.hash(mAttributes, mClientUid, mClientId, mPackageName, mGainRequest, mFlags);
}
- @SystemApi
@Override
public boolean equals(Object obj) {
if (this == obj)
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index f4ec936..bef2bcb 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -1710,18 +1710,9 @@
}
private List<String> getSettingsNamesLocked(int settingsType, int userId) {
- boolean instantApp;
- if (UserHandle.getAppId(Binder.getCallingUid()) < Process.FIRST_APPLICATION_UID) {
- instantApp = false;
- } else {
- ApplicationInfo ai = getCallingApplicationInfoOrThrow();
- instantApp = ai.isInstantApp();
- }
- if (instantApp) {
- return new ArrayList<String>(getInstantAppAccessibleSettings(settingsType));
- } else {
- return mSettingsRegistry.getSettingsNamesLocked(settingsType, userId);
- }
+ // Don't enforce the instant app whitelist for now -- its too prone to unintended breakage
+ // in the current form.
+ return mSettingsRegistry.getSettingsNamesLocked(settingsType, userId);
}
private void enforceSettingReadable(String settingName, int settingsType, int userId) {
@@ -1734,8 +1725,10 @@
}
if (!getInstantAppAccessibleSettings(settingsType).contains(settingName)
&& !getOverlayInstantAppAccessibleSettings(settingsType).contains(settingName)) {
- throw new SecurityException("Setting " + settingName + " is not accessible from"
- + " ephemeral package " + getCallingPackage());
+ // Don't enforce the instant app whitelist for now -- its too prone to unintended
+ // breakage in the current form.
+ Slog.w(LOG_TAG, "Instant App " + ai.packageName
+ + " trying to access unexposed setting, this will be an error in the future.");
}
}
diff --git a/packages/SystemUI/Android.mk b/packages/SystemUI/Android.mk
index 2c5eb27..73fcdd7 100644
--- a/packages/SystemUI/Android.mk
+++ b/packages/SystemUI/Android.mk
@@ -39,7 +39,12 @@
android-support-v7-mediarouter \
android-support-v7-palette \
android-support-v14-preference \
- android-support-v17-leanback
+ android-support-v17-leanback \
+ android-slices-core \
+ android-slices-view \
+ android-slices-builders \
+ apptoolkit-arch-core-runtime \
+ apptoolkit-lifecycle-extensions \
LOCAL_STATIC_JAVA_LIBRARIES := \
SystemUI-tags \
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_status_area.xml b/packages/SystemUI/res-keyguard/layout/keyguard_status_area.xml
index 020cfee..b154d46 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_status_area.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_status_area.xml
@@ -24,31 +24,20 @@
android:layout_marginEnd="16dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginTop="@dimen/date_owner_info_margin"
android:layout_gravity="center_horizontal"
- android:paddingTop="4dp"
android:clipToPadding="false"
android:orientation="vertical"
android:layout_centerHorizontal="true">
<TextView android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:singleLine="true"
- android:ellipsize="end"
- android:fadingEdge="horizontal"
- android:gravity="center"
- android:textSize="22sp"
- android:textColor="?attr/wallpaperTextColor"
+ android:layout_marginBottom="@dimen/widget_vertical_padding"
+ android:theme="@style/TextAppearance.Keyguard"
/>
- <TextView android:id="@+id/text"
+ <LinearLayout android:id="@+id/row"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:singleLine="true"
+ android:orientation="horizontal"
android:gravity="center"
- android:visibility="gone"
- android:textSize="16sp"
- android:textColor="?attr/wallpaperTextColor"
- android:layout_marginTop="4dp"
- android:ellipsize="end"
/>
</com.android.keyguard.KeyguardSliceView>
\ No newline at end of file
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_status_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_status_view.xml
index 138733e..c97cfc4 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_status_view.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_status_view.xml
@@ -34,6 +34,7 @@
android:orientation="vertical">
<RelativeLayout
android:id="@+id/keyguard_clock_container"
+ android:animateLayoutChanges="true"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal|top">
@@ -59,14 +60,23 @@
android:layout_toEndOf="@id/clock_view"
android:visibility="invisible"
android:src="@drawable/ic_aod_charging_24dp"
- android:contentDescription="@string/accessibility_ambient_display_charging"
- />
+ android:contentDescription="@string/accessibility_ambient_display_charging" />
+ <View
+ android:id="@+id/clock_separator"
+ android:layout_width="16dp"
+ android:layout_height="1dp"
+ android:layout_marginTop="10dp"
+ android:layout_below="@id/clock_view"
+ android:background="#f00"
+ android:layout_centerHorizontal="true" />
<include layout="@layout/keyguard_status_area"
android:id="@+id/keyguard_status_area"
+ android:layout_marginTop="10dp"
+ android:layout_marginBottom="@dimen/widget_vertical_padding"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_below="@id/clock_view" />
+ android:layout_below="@id/clock_separator" />
</RelativeLayout>
<TextView
@@ -83,6 +93,5 @@
android:letterSpacing="0.05"
android:ellipsize="marquee"
android:singleLine="true" />
-
</LinearLayout>
</com.android.keyguard.KeyguardStatusView>
diff --git a/packages/SystemUI/res-keyguard/values-h560dp/dimens.xml b/packages/SystemUI/res-keyguard/values-h560dp/dimens.xml
index 1b6fa4c..3fb86d0 100644
--- a/packages/SystemUI/res-keyguard/values-h560dp/dimens.xml
+++ b/packages/SystemUI/res-keyguard/values-h560dp/dimens.xml
@@ -16,5 +16,5 @@
-->
<resources>
- <dimen name="widget_big_font_size">72dp</dimen>
+ <dimen name="widget_big_font_size">64dp</dimen>
</resources>
\ No newline at end of file
diff --git a/packages/SystemUI/res-keyguard/values-h650dp/dimens.xml b/packages/SystemUI/res-keyguard/values-h650dp/dimens.xml
index 1b6fa4c..3fb86d0 100644
--- a/packages/SystemUI/res-keyguard/values-h650dp/dimens.xml
+++ b/packages/SystemUI/res-keyguard/values-h650dp/dimens.xml
@@ -16,5 +16,5 @@
-->
<resources>
- <dimen name="widget_big_font_size">72dp</dimen>
+ <dimen name="widget_big_font_size">64dp</dimen>
</resources>
\ No newline at end of file
diff --git a/packages/SystemUI/res-keyguard/values/dimens.xml b/packages/SystemUI/res-keyguard/values/dimens.xml
index bcac072..463af61 100644
--- a/packages/SystemUI/res-keyguard/values/dimens.xml
+++ b/packages/SystemUI/res-keyguard/values/dimens.xml
@@ -42,9 +42,22 @@
<dimen name="eca_overlap">-10dip</dimen>
<!-- Default clock parameters -->
- <dimen name="bottom_text_spacing_digital">-1dp</dimen>
- <dimen name="widget_label_font_size">14sp</dimen>
- <dimen name="widget_big_font_size">72dp</dimen>
+ <dimen name="bottom_text_spacing_digital">-10dp</dimen>
+ <!-- Slice header -->
+ <dimen name="widget_title_font_size">28sp</dimen>
+ <!-- Slice subtitle -->
+ <dimen name="widget_label_font_size">16sp</dimen>
+ <!-- Clock without header -->
+ <dimen name="widget_big_font_size">64dp</dimen>
+ <!-- Clock with header -->
+ <dimen name="widget_small_font_size">22dp</dimen>
+ <!-- Dash between clock and header -->
+ <dimen name="widget_vertical_padding">16dp</dimen>
+ <!-- Subtitle paddings -->
+ <dimen name="widget_separator_thickness">2dp</dimen>
+ <dimen name="widget_horizontal_padding">8dp</dimen>
+ <dimen name="widget_icon_size">16dp</dimen>
+ <dimen name="widget_icon_padding">4dp</dimen>
<!-- The y translation to apply at the start in appear animations. -->
<dimen name="appear_y_translation_start">32dp</dimen>
diff --git a/packages/SystemUI/res-keyguard/values/styles.xml b/packages/SystemUI/res-keyguard/values/styles.xml
index 826e3ea..d50bab5 100644
--- a/packages/SystemUI/res-keyguard/values/styles.xml
+++ b/packages/SystemUI/res-keyguard/values/styles.xml
@@ -78,4 +78,18 @@
<item name="wallpaperTextColorSecondary">@*android:color/secondary_text_material_dark</item>
</style>
+ <style name="TextAppearance.Keyguard" parent="Theme.SystemUI">
+ <item name="android:textSize">@dimen/widget_title_font_size</item>
+ <item name="android:gravity">center</item>
+ <item name="android:ellipsize">end</item>
+ <item name="android:maxLines">2</item>
+ </style>
+
+ <style name="TextAppearance.Keyguard.Secondary">
+ <item name="android:layout_width">wrap_content</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:textSize">@dimen/widget_label_font_size</item>
+ <item name="android:singleLine">true</item>
+ </style>
+
</resources>
diff --git a/packages/SystemUI/res/layout/pip_menu_activity.xml b/packages/SystemUI/res/layout/pip_menu_activity.xml
index 8b7f692..03d587b 100644
--- a/packages/SystemUI/res/layout/pip_menu_activity.xml
+++ b/packages/SystemUI/res/layout/pip_menu_activity.xml
@@ -60,14 +60,24 @@
</FrameLayout>
</FrameLayout>
- <ImageView
- android:id="@+id/dismiss"
- android:layout_width="@dimen/pip_action_size"
- android:layout_height="@dimen/pip_action_size"
- android:layout_gravity="top|end"
- android:padding="@dimen/pip_action_padding"
- android:contentDescription="@string/pip_phone_close"
- android:src="@drawable/ic_close_white"
- android:background="?android:selectableItemBackgroundBorderless" />
+ <ImageView
+ android:id="@+id/settings"
+ android:layout_width="@dimen/pip_action_size"
+ android:layout_height="@dimen/pip_action_size"
+ android:layout_gravity="top|start"
+ android:padding="@dimen/pip_action_padding"
+ android:contentDescription="@string/pip_phone_settings"
+ android:src="@drawable/ic_settings"
+ android:background="?android:selectableItemBackgroundBorderless" />
+
+ <ImageView
+ android:id="@+id/dismiss"
+ android:layout_width="@dimen/pip_action_size"
+ android:layout_height="@dimen/pip_action_size"
+ android:layout_gravity="top|end"
+ android:padding="@dimen/pip_action_padding"
+ android:contentDescription="@string/pip_phone_close"
+ android:src="@drawable/ic_close_white"
+ android:background="?android:selectableItemBackgroundBorderless" />
</FrameLayout>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index fd205dd..98537a1 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1895,6 +1895,9 @@
<!-- Label for PIP close button [CHAR LIMIT=NONE]-->
<string name="pip_phone_close">Close</string>
+ <!-- Label for PIP settings button [CHAR LIMIT=NONE]-->
+ <string name="pip_phone_settings">Settings</string>
+
<!-- Label for PIP the drag to dismiss hint [CHAR LIMIT=NONE]-->
<string name="pip_phone_dismiss_hint">Drag down to dismiss</string>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
index cb3d59c..b9bf80d 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
@@ -17,38 +17,56 @@
package com.android.keyguard;
import android.app.PendingIntent;
-import android.app.slice.Slice;
-import android.app.slice.SliceItem;
-import android.app.slice.SliceQuery;
+import android.arch.lifecycle.LiveData;
+import android.arch.lifecycle.Observer;
import android.content.Context;
-import android.database.ContentObserver;
+import android.graphics.Canvas;
import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.drawable.Drawable;
import android.net.Uri;
-import android.os.Handler;
+import android.provider.Settings;
import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.android.internal.graphics.ColorUtils;
+import com.android.settingslib.Utils;
+import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.keyguard.KeyguardSliceProvider;
+import com.android.systemui.tuner.TunerService;
-import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.function.Consumer;
+
+import androidx.app.slice.Slice;
+import androidx.app.slice.SliceItem;
+import androidx.app.slice.core.SliceQuery;
+import androidx.app.slice.widget.SliceLiveData;
/**
* View visible under the clock on the lock screen and AoD.
*/
-public class KeyguardSliceView extends LinearLayout {
+public class KeyguardSliceView extends LinearLayout implements View.OnClickListener,
+ Observer<Slice>, TunerService.Tunable {
- private final Uri mKeyguardSliceUri;
+ private static final String TAG = "KeyguardSliceView";
+ private final HashMap<View, PendingIntent> mClickActions;
+ private Uri mKeyguardSliceUri;
private TextView mTitle;
- private TextView mText;
- private Slice mSlice;
- private PendingIntent mSliceAction;
+ private LinearLayout mRow;
private int mTextColor;
private float mDarkAmount = 0;
- private final ContentObserver mObserver;
+ private LiveData<Slice> mLiveData;
+ private int mIconSize;
+ private Consumer<Boolean> mListener;
+ private boolean mHasHeader;
public KeyguardSliceView(Context context) {
this(context, null, 0);
@@ -60,16 +78,20 @@
public KeyguardSliceView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
- mObserver = new KeyguardSliceObserver(new Handler());
- mKeyguardSliceUri = Uri.parse(KeyguardSliceProvider.KEYGUARD_SLICE_URI);;
+
+ TunerService tunerService = Dependency.get(TunerService.class);
+ tunerService.addTunable(this, Settings.Secure.KEYGUARD_SLICE_URI);
+
+ mClickActions = new HashMap<>();
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mTitle = findViewById(R.id.title);
- mText = findViewById(R.id.text);
- mTextColor = mTitle.getCurrentTextColor();
+ mRow = findViewById(R.id.row);
+ mTextColor = Utils.getColorAttr(mContext, R.attr.wallpaperTextColor);
+ mIconSize = (int) mContext.getResources().getDimension(R.dimen.widget_icon_size);
}
@Override
@@ -77,57 +99,103 @@
super.onAttachedToWindow();
// Set initial content
- showSlice(Slice.bindSlice(getContext().getContentResolver(), mKeyguardSliceUri,
- Collections.emptyList()));
+ showSlice(Slice.bindSlice(getContext(), mKeyguardSliceUri));
// Make sure we always have the most current slice
- getContext().getContentResolver().registerContentObserver(mKeyguardSliceUri,
- false /* notifyDescendants */, mObserver);
+ mLiveData.observeForever(this);
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
- getContext().getContentResolver().unregisterContentObserver(mObserver);
+ mLiveData.removeObserver(this);
}
private void showSlice(Slice slice) {
- // Items will be wrapped into an action when they have tap targets.
- SliceItem actionSlice = SliceQuery.find(slice, SliceItem.FORMAT_ACTION);
- if (actionSlice != null) {
- mSlice = actionSlice.getSlice();
- mSliceAction = actionSlice.getAction();
- } else {
- mSlice = slice;
- mSliceAction = null;
- }
- if (mSlice == null) {
- setVisibility(GONE);
- return;
- }
+ // Main area
+ SliceItem mainItem = SliceQuery.find(slice, android.app.slice.SliceItem.FORMAT_SLICE,
+ null /* hints */, new String[]{android.app.slice.Slice.HINT_LIST_ITEM});
+ mHasHeader = mainItem != null;
- SliceItem title = SliceQuery.find(mSlice, SliceItem.FORMAT_TEXT, Slice.HINT_TITLE, null);
- if (title == null) {
+ List<SliceItem> subItems = SliceQuery.findAll(slice,
+ android.app.slice.SliceItem.FORMAT_SLICE,
+ new String[]{android.app.slice.Slice.HINT_LIST_ITEM},
+ null /* nonHints */);
+
+ if (!mHasHeader) {
mTitle.setVisibility(GONE);
} else {
mTitle.setVisibility(VISIBLE);
- mTitle.setText(title.getText());
+ SliceItem mainTitle = SliceQuery.find(mainItem.getSlice(),
+ android.app.slice.SliceItem.FORMAT_TEXT,
+ new String[]{android.app.slice.Slice.HINT_TITLE},
+ null /* nonHints */);
+ mTitle.setText(mainTitle.getText());
}
- SliceItem text = SliceQuery.find(mSlice, SliceItem.FORMAT_TEXT, null, Slice.HINT_TITLE);
- if (text == null) {
- mText.setVisibility(GONE);
- } else {
- mText.setVisibility(VISIBLE);
- mText.setText(text.getText());
+ mClickActions.clear();
+ final int subItemsCount = subItems.size();
+
+ for (int i = 0; i < subItemsCount; i++) {
+ SliceItem item = subItems.get(i);
+ final Uri itemTag = item.getSlice().getUri();
+ // Try to reuse the view if already exists in the layout
+ KeyguardSliceButton button = mRow.findViewWithTag(itemTag);
+ if (button == null) {
+ button = new KeyguardSliceButton(mContext);
+ button.setTextColor(mTextColor);
+ button.setTag(itemTag);
+ } else {
+ mRow.removeView(button);
+ }
+ button.setHasDivider(i < subItemsCount - 1);
+ mRow.addView(button, i);
+
+ PendingIntent pendingIntent;
+ try {
+ pendingIntent = item.getAction();
+ } catch (RuntimeException e) {
+ Log.w(TAG, "Cannot retrieve action from keyguard slice", e);
+ pendingIntent = null;
+ }
+ mClickActions.put(button, pendingIntent);
+
+ SliceItem title = SliceQuery.find(item.getSlice(),
+ android.app.slice.SliceItem.FORMAT_TEXT,
+ new String[]{android.app.slice.Slice.HINT_TITLE},
+ null /* nonHints */);
+ button.setText(title.getText());
+
+ Drawable iconDrawable = null;
+ SliceItem icon = SliceQuery.find(item.getSlice(),
+ android.app.slice.SliceItem.FORMAT_IMAGE);
+ if (icon != null) {
+ iconDrawable = icon.getIcon().loadDrawable(mContext);
+ final int width = (int) (iconDrawable.getIntrinsicWidth()
+ / (float) iconDrawable.getIntrinsicHeight() * mIconSize);
+ iconDrawable.setBounds(0, 0, Math.max(width, 1), mIconSize);
+ }
+ button.setCompoundDrawablesRelative(iconDrawable, null, null, null);
+ button.setOnClickListener(this);
}
- final int visibility = title == null && text == null ? GONE : VISIBLE;
+ // Removing old views
+ for (int i = 0; i < mRow.getChildCount(); i++) {
+ View child = mRow.getChildAt(i);
+ if (!mClickActions.containsKey(child)) {
+ mRow.removeView(child);
+ i--;
+ }
+ }
+
+ final int visibility = mHasHeader || subItemsCount > 0 ? VISIBLE : GONE;
if (visibility != getVisibility()) {
setVisibility(visibility);
}
+
+ mListener.accept(mHasHeader);
}
public void setDark(float darkAmount) {
@@ -135,30 +203,113 @@
updateTextColors();
}
- public void setTextColor(int textColor) {
- mTextColor = textColor;
- }
-
private void updateTextColors() {
final int blendedColor = ColorUtils.blendARGB(mTextColor, Color.WHITE, mDarkAmount);
mTitle.setTextColor(blendedColor);
- mText.setTextColor(blendedColor);
+ int childCount = mRow.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ View v = mRow.getChildAt(i);
+ if (v instanceof Button) {
+ ((Button) v).setTextColor(blendedColor);
+ }
+ }
}
- private class KeyguardSliceObserver extends ContentObserver {
- KeyguardSliceObserver(Handler handler) {
- super(handler);
+ @Override
+ public void onClick(View v) {
+ final PendingIntent action = mClickActions.get(v);
+ if (action != null) {
+ try {
+ action.send();
+ } catch (PendingIntent.CanceledException e) {
+ Log.i(TAG, "Pending intent cancelled, nothing to launch", e);
+ }
+ }
+ }
+
+ public void setListener(Consumer<Boolean> listener) {
+ mListener = listener;
+ }
+
+ public boolean hasHeader() {
+ return mHasHeader;
+ }
+
+ /**
+ * LiveData observer lifecycle.
+ * @param slice the new slice content.
+ */
+ @Override
+ public void onChanged(Slice slice) {
+ showSlice(slice);
+ }
+
+ @Override
+ public void onTuningChanged(String key, String newValue) {
+ setupUri(newValue);
+ }
+
+ public void setupUri(String uriString) {
+ if (uriString == null) {
+ uriString = KeyguardSliceProvider.KEYGUARD_SLICE_URI;
+ }
+
+ boolean wasObserving = false;
+ if (mLiveData != null && mLiveData.hasActiveObservers()) {
+ wasObserving = true;
+ mLiveData.removeObserver(this);
+ }
+
+ mKeyguardSliceUri = Uri.parse(uriString);
+ mLiveData = SliceLiveData.fromUri(mContext, mKeyguardSliceUri);
+
+ if (wasObserving) {
+ mLiveData.observeForever(this);
+ showSlice(Slice.bindSlice(getContext(), mKeyguardSliceUri));
+ }
+ }
+
+ /**
+ * Representation of an item that appears under the clock on main keyguard message.
+ * Shows optional separator.
+ */
+ private class KeyguardSliceButton extends Button {
+
+ private final Paint mPaint;
+ private boolean mHasDivider;
+
+ public KeyguardSliceButton(Context context) {
+ super(context, null /* attrs */,
+ com.android.keyguard.R.style.TextAppearance_Keyguard_Secondary);
+ mPaint = new Paint();
+ mPaint.setStyle(Paint.Style.STROKE);
+ float dividerWidth = context.getResources()
+ .getDimension(R.dimen.widget_separator_thickness);
+ mPaint.setStrokeWidth(dividerWidth);
+ int horizontalPadding = (int) context.getResources()
+ .getDimension(R.dimen.widget_horizontal_padding);
+ setPadding(horizontalPadding, 0, horizontalPadding, 0);
+ setCompoundDrawablePadding((int) context.getResources()
+ .getDimension(R.dimen.widget_icon_padding));
+ }
+
+ public void setHasDivider(boolean hasDivider) {
+ mHasDivider = hasDivider;
}
@Override
- public void onChange(boolean selfChange) {
- this.onChange(selfChange, null);
+ public void setTextColor(int color) {
+ super.setTextColor(color);
+ mPaint.setColor(color);
}
@Override
- public void onChange(boolean selfChange, Uri uri) {
- showSlice(Slice.bindSlice(getContext().getContentResolver(), mKeyguardSliceUri,
- Collections.emptyList()));
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+ if (mHasDivider) {
+ final int lineX = getLayoutDirection() == LAYOUT_DIRECTION_RTL ? 0 : getWidth();
+ canvas.drawLine(lineX, 0, lineX, getHeight(), mPaint);
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
index 78cf2b9..4b9a874 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
@@ -28,6 +28,7 @@
import android.support.v4.graphics.ColorUtils;
import android.text.TextUtils;
import android.text.format.DateFormat;
+import android.util.ArraySet;
import android.util.AttributeSet;
import android.util.Log;
import android.util.Slog;
@@ -38,11 +39,11 @@
import android.widget.TextClock;
import android.widget.TextView;
-import com.android.internal.util.ArrayUtils;
import com.android.internal.widget.LockPatternUtils;
-import com.android.settingslib.Utils;
import com.android.systemui.ChargingView;
+import com.google.android.collect.Sets;
+
import java.util.Locale;
public class KeyguardStatusView extends GridLayout {
@@ -52,8 +53,11 @@
private final LockPatternUtils mLockPatternUtils;
private final AlarmManager mAlarmManager;
+ private final float mSmallClockScale;
+ private final float mWidgetPadding;
private TextClock mClockView;
+ private View mClockSeparator;
private TextView mOwnerInfo;
private ViewGroup mClockContainer;
private ChargingView mBatteryDoze;
@@ -61,7 +65,7 @@
private Runnable mPendingMarqueeStart;
private Handler mHandler;
- private View[] mVisibleInDoze;
+ private ArraySet<View> mVisibleInDoze;
private boolean mPulsing;
private float mDarkAmount = 0;
private int mTextColor;
@@ -112,6 +116,9 @@
mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
mLockPatternUtils = new LockPatternUtils(getContext());
mHandler = new Handler(Looper.myLooper());
+ mSmallClockScale = getResources().getDimension(R.dimen.widget_small_font_size)
+ / getResources().getDimension(R.dimen.widget_big_font_size);
+ mWidgetPadding = getResources().getDimension(R.dimen.widget_vertical_padding);
}
private void setEnableMarquee(boolean enabled) {
@@ -150,9 +157,14 @@
mOwnerInfo = findViewById(R.id.owner_info);
mBatteryDoze = findViewById(R.id.battery_doze);
mKeyguardSlice = findViewById(R.id.keyguard_status_area);
- mVisibleInDoze = new View[]{mBatteryDoze, mClockView, mKeyguardSlice};
+ mClockSeparator = findViewById(R.id.clock_separator);
+ mVisibleInDoze = Sets.newArraySet(mBatteryDoze, mClockView, mKeyguardSlice,
+ mClockSeparator);
mTextColor = mClockView.getCurrentTextColor();
+ mKeyguardSlice.setListener(this::onSliceContentChanged);
+ onSliceContentChanged(mKeyguardSlice.hasHeader());
+
boolean shouldMarquee = KeyguardUpdateMonitor.getInstance(mContext).isDeviceInteractive();
setEnableMarquee(shouldMarquee);
refresh();
@@ -163,6 +175,22 @@
mClockView.setElegantTextHeight(false);
}
+ private void onSliceContentChanged(boolean hasHeader) {
+ final float clockScale = hasHeader ? mSmallClockScale : 1;
+ float translation = (mClockView.getHeight() - (mClockView.getHeight() * clockScale)) / 2f;
+ if (hasHeader) {
+ translation -= mWidgetPadding;
+ }
+ mClockView.setTranslationY(translation);
+ mClockView.setScaleX(clockScale);
+ mClockView.setScaleY(clockScale);
+ final float batteryTranslation =
+ -(mClockView.getWidth() - (mClockView.getWidth() * clockScale)) / 2;
+ mBatteryDoze.setTranslationX(batteryTranslation);
+ mBatteryDoze.setTranslationY(translation);
+ mClockSeparator.setVisibility(hasHeader ? VISIBLE : GONE);
+ }
+
@Override
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
@@ -201,17 +229,6 @@
return mClockView.getTextSize();
}
- public static String formatNextAlarm(Context context, AlarmManager.AlarmClockInfo info) {
- if (info == null) {
- return "";
- }
- String skeleton = DateFormat.is24HourFormat(context, ActivityManager.getCurrentUser())
- ? "EHm"
- : "Ehma";
- String pattern = DateFormat.getBestDateTimePattern(Locale.getDefault(), skeleton);
- return DateFormat.format(pattern, info.getTriggerTime()).toString();
- }
-
private void updateOwnerInfo() {
if (mOwnerInfo == null) return;
String ownerInfo = getOwnerInfo();
@@ -303,7 +320,7 @@
final int N = mClockContainer.getChildCount();
for (int i = 0; i < N; i++) {
View child = mClockContainer.getChildAt(i);
- if (ArrayUtils.contains(mVisibleInDoze, child)) {
+ if (mVisibleInDoze.contains(child)) {
continue;
}
child.setAlpha(dark ? 0 : 1);
@@ -312,10 +329,12 @@
mOwnerInfo.setAlpha(dark ? 0 : 1);
}
+ final int blendedTextColor = ColorUtils.blendARGB(mTextColor, Color.WHITE, darkAmount);
updateDozeVisibleViews();
mBatteryDoze.setDark(dark);
mKeyguardSlice.setDark(darkAmount);
- mClockView.setTextColor(ColorUtils.blendARGB(mTextColor, Color.WHITE, darkAmount));
+ mClockView.setTextColor(blendedTextColor);
+ mClockSeparator.setBackgroundColor(blendedTextColor);
}
public void setPulsing(boolean pulsing) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
index 6ddc76b..bd46c5f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
@@ -16,38 +16,55 @@
package com.android.systemui.keyguard;
+import android.app.ActivityManager;
+import android.app.AlarmManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.graphics.drawable.Icon;
import android.icu.text.DateFormat;
import android.icu.text.DisplayContext;
import android.net.Uri;
import android.os.Handler;
-import android.app.slice.Slice;
-import android.app.slice.SliceProvider;
+import android.text.TextUtils;
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.R;
+import com.android.systemui.statusbar.policy.NextAlarmController;
+import com.android.systemui.statusbar.policy.NextAlarmControllerImpl;
import java.util.Date;
import java.util.Locale;
+import androidx.app.slice.Slice;
+import androidx.app.slice.SliceProvider;
+import androidx.app.slice.builders.ListBuilder;
+import androidx.app.slice.builders.ListBuilder.RowBuilder;
+
/**
* Simple Slice provider that shows the current date.
*/
-public class KeyguardSliceProvider extends SliceProvider {
+public class KeyguardSliceProvider extends SliceProvider implements
+ NextAlarmController.NextAlarmChangeCallback {
public static final String KEYGUARD_SLICE_URI = "content://com.android.systemui.keyguard/main";
+ public static final String KEYGUARD_DATE_URI = "content://com.android.systemui.keyguard/date";
+ public static final String KEYGUARD_NEXT_ALARM_URI =
+ "content://com.android.systemui.keyguard/alarm";
private final Date mCurrentTime = new Date();
protected final Uri mSliceUri;
+ protected final Uri mDateUri;
+ protected final Uri mAlarmUri;
private final Handler mHandler;
private String mDatePattern;
private DateFormat mDateFormat;
private String mLastText;
private boolean mRegistered;
private boolean mRegisteredEveryMinute;
+ private String mNextAlarm;
+ private NextAlarmController mNextAlarmController;
/**
* Receiver responsible for time ticking and updating the date format.
@@ -80,23 +97,49 @@
KeyguardSliceProvider(Handler handler) {
mHandler = handler;
mSliceUri = Uri.parse(KEYGUARD_SLICE_URI);
+ mDateUri = Uri.parse(KEYGUARD_DATE_URI);
+ mAlarmUri = Uri.parse(KEYGUARD_NEXT_ALARM_URI);
}
@Override
public Slice onBindSlice(Uri sliceUri) {
- return new Slice.Builder(sliceUri).addText(mLastText, null, Slice.HINT_TITLE).build();
+ ListBuilder builder = new ListBuilder(mSliceUri)
+ .addRow(new RowBuilder(mDateUri).setTitle(mLastText));
+ if (!TextUtils.isEmpty(mNextAlarm)) {
+ Icon icon = Icon.createWithResource(getContext(), R.drawable.ic_access_alarms_big);
+ builder.addRow(new RowBuilder(mAlarmUri).setTitle(mNextAlarm).addEndItem(icon));
+ }
+
+ return builder.build();
}
@Override
- public boolean onCreate() {
-
+ public boolean onCreateSliceProvider() {
+ mNextAlarmController = new NextAlarmControllerImpl(getContext());
+ mNextAlarmController.addCallback(this);
mDatePattern = getContext().getString(R.string.system_ui_date_pattern);
-
registerClockUpdate(false /* everyMinute */);
updateClock();
return true;
}
+ public static String formatNextAlarm(Context context, AlarmManager.AlarmClockInfo info) {
+ if (info == null) {
+ return "";
+ }
+ String skeleton = android.text.format.DateFormat
+ .is24HourFormat(context, ActivityManager.getCurrentUser()) ? "EHm" : "Ehma";
+ String pattern = android.text.format.DateFormat
+ .getBestDateTimePattern(Locale.getDefault(), skeleton);
+ return android.text.format.DateFormat.format(pattern, info.getTriggerTime()).toString();
+ }
+
+ /**
+ * Registers a broadcast receiver for clock updates, include date, time zone and manually
+ * changing the date/time via the settings app.
+ *
+ * @param everyMinute {@code true} if you also want updates every minute.
+ */
protected void registerClockUpdate(boolean everyMinute) {
if (mRegistered) {
if (mRegisteredEveryMinute == everyMinute) {
@@ -156,4 +199,10 @@
void cleanDateFormat() {
mDateFormat = null;
}
+
+ @Override
+ public void onNextAlarmChanged(AlarmManager.AlarmClockInfo nextAlarm) {
+ mNextAlarm = formatNextAlarm(getContext(), nextAlarm);
+ getContext().getContentResolver().notifyChange(mSliceUri, null /* observer */);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
index db999c4..dce3e24 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
@@ -64,7 +64,6 @@
private InputConsumerController mInputConsumerController;
private PipMenuActivityController mMenuController;
private PipMediaController mMediaController;
- private PipNotificationController mNotificationController;
private PipTouchHandler mTouchHandler;
/**
@@ -76,8 +75,6 @@
mTouchHandler.onActivityPinned();
mMediaController.onActivityPinned();
mMenuController.onActivityPinned();
- mNotificationController.onActivityPinned(packageName, userId,
- true /* deferUntilAnimationEnds */);
SystemServicesProxy.getInstance(mContext).setPipVisibility(true);
}
@@ -90,7 +87,6 @@
final int userId = topActivity != null ? topPipActivityInfo.second : 0;
mMenuController.onActivityUnpinned();
mTouchHandler.onActivityUnpinned(topActivity);
- mNotificationController.onActivityUnpinned(topActivity, userId);
SystemServicesProxy.getInstance(mContext).setPipVisibility(topActivity != null);
}
@@ -107,7 +103,6 @@
mTouchHandler.setTouchEnabled(true);
mTouchHandler.onPinnedStackAnimationEnded();
mMenuController.onPinnedStackAnimationEnded();
- mNotificationController.onPinnedStackAnimationEnded();
}
@Override
@@ -182,8 +177,6 @@
mInputConsumerController);
mTouchHandler = new PipTouchHandler(context, mActivityManager, mMenuController,
mInputConsumerController);
- mNotificationController = new PipNotificationController(context, mActivityManager,
- mTouchHandler.getMotionHelper());
EventBus.getDefault().register(this);
}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
index 90f7b8d..bfe07a9 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
@@ -16,6 +16,10 @@
package com.android.systemui.pip.phone;
+import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+import static android.provider.Settings.ACTION_PICTURE_IN_PICTURE_SETTINGS;
+
import static com.android.systemui.pip.phone.PipMenuActivityController.EXTRA_ACTIONS;
import static com.android.systemui.pip.phone.PipMenuActivityController.EXTRA_ALLOW_TIMEOUT;
import static com.android.systemui.pip.phone.PipMenuActivityController.EXTRA_CONTROLLER_MESSENGER;
@@ -39,6 +43,7 @@
import android.app.ActivityManager;
import android.app.PendingIntent.CanceledException;
import android.app.RemoteAction;
+import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.ParceledListSlice;
import android.graphics.Color;
@@ -46,12 +51,15 @@
import android.graphics.Rect;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
+import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
+import android.os.UserHandle;
import android.util.Log;
+import android.util.Pair;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
@@ -105,6 +113,7 @@
private Drawable mBackgroundDrawable;
private View mMenuContainer;
private LinearLayout mActionsGroup;
+ private View mSettingsButton;
private View mDismissButton;
private ImageView mExpandButton;
private int mBetweenActionPaddingLand;
@@ -218,6 +227,11 @@
}
return true;
});
+ mSettingsButton = findViewById(R.id.settings);
+ mSettingsButton.setAlpha(0);
+ mSettingsButton.setOnClickListener((v) -> {
+ showSettings();
+ });
mDismissButton = findViewById(R.id.dismiss);
mDismissButton.setAlpha(0);
mDismissButton.setOnClickListener((v) -> {
@@ -352,12 +366,14 @@
ObjectAnimator menuAnim = ObjectAnimator.ofFloat(mMenuContainer, View.ALPHA,
mMenuContainer.getAlpha(), 1f);
menuAnim.addUpdateListener(mMenuBgUpdateListener);
+ ObjectAnimator settingsAnim = ObjectAnimator.ofFloat(mSettingsButton, View.ALPHA,
+ mSettingsButton.getAlpha(), 1f);
ObjectAnimator dismissAnim = ObjectAnimator.ofFloat(mDismissButton, View.ALPHA,
mDismissButton.getAlpha(), 1f);
if (menuState == MENU_STATE_FULL) {
- mMenuContainerAnimator.playTogether(menuAnim, dismissAnim);
+ mMenuContainerAnimator.playTogether(menuAnim, settingsAnim, dismissAnim);
} else {
- mMenuContainerAnimator.play(dismissAnim);
+ mMenuContainerAnimator.playTogether(settingsAnim, dismissAnim);
}
mMenuContainerAnimator.setInterpolator(Interpolators.ALPHA_IN);
mMenuContainerAnimator.setDuration(MENU_FADE_DURATION);
@@ -394,9 +410,11 @@
ObjectAnimator menuAnim = ObjectAnimator.ofFloat(mMenuContainer, View.ALPHA,
mMenuContainer.getAlpha(), 0f);
menuAnim.addUpdateListener(mMenuBgUpdateListener);
+ ObjectAnimator settingsAnim = ObjectAnimator.ofFloat(mSettingsButton, View.ALPHA,
+ mSettingsButton.getAlpha(), 0f);
ObjectAnimator dismissAnim = ObjectAnimator.ofFloat(mDismissButton, View.ALPHA,
mDismissButton.getAlpha(), 0f);
- mMenuContainerAnimator.playTogether(menuAnim, dismissAnim);
+ mMenuContainerAnimator.playTogether(menuAnim, settingsAnim, dismissAnim);
mMenuContainerAnimator.setInterpolator(Interpolators.ALPHA_OUT);
mMenuContainerAnimator.setDuration(MENU_FADE_DURATION);
mMenuContainerAnimator.addListener(new AnimatorListenerAdapter() {
@@ -526,12 +544,14 @@
final float menuAlpha = 1 - fraction;
if (mMenuState == MENU_STATE_FULL) {
mMenuContainer.setAlpha(menuAlpha);
+ mSettingsButton.setAlpha(menuAlpha);
mDismissButton.setAlpha(menuAlpha);
final float interpolatedAlpha =
MENU_BACKGROUND_ALPHA * menuAlpha + DISMISS_BACKGROUND_ALPHA * fraction;
alpha = (int) (interpolatedAlpha * 255);
} else {
if (mMenuState == MENU_STATE_CLOSE) {
+ mSettingsButton.setAlpha(menuAlpha);
mDismissButton.setAlpha(menuAlpha);
}
alpha = (int) (fraction * DISMISS_BACKGROUND_ALPHA * 255);
@@ -588,6 +608,19 @@
sendMessage(m, "Could not notify controller to show PIP menu");
}
+ private void showSettings() {
+ final Pair<ComponentName, Integer> topPipActivityInfo =
+ PipUtils.getTopPinnedActivity(this, ActivityManager.getService());
+ if (topPipActivityInfo.first != null) {
+ final UserHandle user = UserHandle.of(topPipActivityInfo.second);
+ final Intent settingsIntent = new Intent(ACTION_PICTURE_IN_PICTURE_SETTINGS,
+ Uri.fromParts("package", topPipActivityInfo.first.getPackageName(), null));
+ settingsIntent.putExtra(Intent.EXTRA_USER_HANDLE, user);
+ settingsIntent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK);
+ startActivity(settingsIntent);
+ }
+ }
+
private void notifyActivityCallback(Messenger callback) {
Message m = Message.obtain();
m.what = PipMenuActivityController.MESSAGE_UPDATE_ACTIVITY_CALLBACK;
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipNotificationController.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipNotificationController.java
deleted file mode 100644
index 6d083e9..0000000
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipNotificationController.java
+++ /dev/null
@@ -1,231 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.pip.phone;
-
-import static android.app.AppOpsManager.MODE_ALLOWED;
-import static android.app.AppOpsManager.OP_PICTURE_IN_PICTURE;
-import static android.app.PendingIntent.FLAG_CANCEL_CURRENT;
-import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
-import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
-import static android.provider.Settings.ACTION_PICTURE_IN_PICTURE_SETTINGS;
-
-import android.app.AppOpsManager;
-import android.app.AppOpsManager.OnOpChangedListener;
-import android.app.IActivityManager;
-import android.app.Notification;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.graphics.Bitmap;
-import android.graphics.Bitmap.Config;
-import android.graphics.Canvas;
-import android.graphics.drawable.Drawable;
-import android.net.Uri;
-import android.os.UserHandle;
-import android.util.IconDrawableFactory;
-import android.util.Log;
-import android.util.Pair;
-
-import com.android.systemui.R;
-import com.android.systemui.SystemUI;
-import com.android.systemui.util.NotificationChannels;
-
-/**
- * Manages the BTW notification that shows whenever an activity enters or leaves picture-in-picture.
- */
-public class PipNotificationController {
- private static final String TAG = PipNotificationController.class.getSimpleName();
-
- private static final String NOTIFICATION_TAG = PipNotificationController.class.getName();
- private static final int NOTIFICATION_ID = 0;
-
- private Context mContext;
- private IActivityManager mActivityManager;
- private AppOpsManager mAppOpsManager;
- private NotificationManager mNotificationManager;
- private IconDrawableFactory mIconDrawableFactory;
-
- private PipMotionHelper mMotionHelper;
-
- // Used when building a deferred notification
- private String mDeferredNotificationPackageName;
- private int mDeferredNotificationUserId;
-
- private AppOpsManager.OnOpChangedListener mAppOpsChangedListener = new OnOpChangedListener() {
- @Override
- public void onOpChanged(String op, String packageName) {
- try {
- // Dismiss the PiP once the user disables the app ops setting for that package
- final Pair<ComponentName, Integer> topPipActivityInfo =
- PipUtils.getTopPinnedActivity(mContext, mActivityManager);
- if (topPipActivityInfo.first != null) {
- final ApplicationInfo appInfo = mContext.getPackageManager()
- .getApplicationInfoAsUser(packageName, 0, topPipActivityInfo.second);
- if (appInfo.packageName.equals(topPipActivityInfo.first.getPackageName()) &&
- mAppOpsManager.checkOpNoThrow(OP_PICTURE_IN_PICTURE, appInfo.uid,
- packageName) != MODE_ALLOWED) {
- mMotionHelper.dismissPip();
- }
- }
- } catch (NameNotFoundException e) {
- // Unregister the listener if the package can't be found
- unregisterAppOpsListener();
- }
- }
- };
-
- public PipNotificationController(Context context, IActivityManager activityManager,
- PipMotionHelper motionHelper) {
- mContext = context;
- mActivityManager = activityManager;
- mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
- mNotificationManager = NotificationManager.from(context);
- mMotionHelper = motionHelper;
- mIconDrawableFactory = IconDrawableFactory.newInstance(context);
- }
-
- public void onActivityPinned(String packageName, int userId, boolean deferUntilAnimationEnds) {
- // Clear any existing notification
- mNotificationManager.cancel(NOTIFICATION_TAG, NOTIFICATION_ID);
-
- if (deferUntilAnimationEnds) {
- mDeferredNotificationPackageName = packageName;
- mDeferredNotificationUserId = userId;
- } else {
- showNotificationForApp(packageName, userId);
- }
-
- // Register for changes to the app ops setting for this package while it is in PiP
- registerAppOpsListener(packageName);
- }
-
- public void onPinnedStackAnimationEnded() {
- if (mDeferredNotificationPackageName != null) {
- showNotificationForApp(mDeferredNotificationPackageName, mDeferredNotificationUserId);
- mDeferredNotificationPackageName = null;
- mDeferredNotificationUserId = 0;
- }
- }
-
- public void onActivityUnpinned(ComponentName topPipActivity, int userId) {
- // Unregister for changes to the previously PiP'ed package
- unregisterAppOpsListener();
-
- // Reset the deferred notification package
- mDeferredNotificationPackageName = null;
- mDeferredNotificationUserId = 0;
-
- if (topPipActivity != null) {
- // onActivityUnpinned() is only called after the transition is complete, so we don't
- // need to defer until the animation ends to update the notification
- onActivityPinned(topPipActivity.getPackageName(), userId,
- false /* deferUntilAnimationEnds */);
- } else {
- mNotificationManager.cancel(NOTIFICATION_TAG, NOTIFICATION_ID);
- }
- }
-
- /**
- * Builds and shows the notification for the given app.
- */
- private void showNotificationForApp(String packageName, int userId) {
- // Build a new notification
- try {
- final UserHandle user = UserHandle.of(userId);
- final Context userContext = mContext.createPackageContextAsUser(
- mContext.getPackageName(), 0, user);
- final Notification.Builder builder =
- new Notification.Builder(userContext, NotificationChannels.GENERAL)
- .setLocalOnly(true)
- .setOngoing(true)
- .setSmallIcon(R.drawable.pip_notification_icon)
- .setColor(mContext.getColor(
- com.android.internal.R.color.system_notification_accent_color));
- if (updateNotificationForApp(builder, packageName, user)) {
- SystemUI.overrideNotificationAppName(mContext, builder);
-
- // Show the new notification
- mNotificationManager.notify(NOTIFICATION_TAG, NOTIFICATION_ID, builder.build());
- }
- } catch (NameNotFoundException e) {
- Log.e(TAG, "Could not show notification for application", e);
- }
- }
-
- /**
- * Updates the notification builder with app-specific information, returning whether it was
- * successful.
- */
- private boolean updateNotificationForApp(Notification.Builder builder, String packageName,
- UserHandle user) throws NameNotFoundException {
- final PackageManager pm = mContext.getPackageManager();
- final ApplicationInfo appInfo;
- try {
- appInfo = pm.getApplicationInfoAsUser(packageName, 0, user.getIdentifier());
- } catch (NameNotFoundException e) {
- Log.e(TAG, "Could not update notification for application", e);
- return false;
- }
-
- if (appInfo != null) {
- final String appName = pm.getUserBadgedLabel(pm.getApplicationLabel(appInfo), user)
- .toString();
- final String message = mContext.getString(R.string.pip_notification_message, appName);
- final Intent settingsIntent = new Intent(ACTION_PICTURE_IN_PICTURE_SETTINGS,
- Uri.fromParts("package", packageName, null));
- settingsIntent.putExtra(Intent.EXTRA_USER_HANDLE, user);
- settingsIntent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK);
-
- final Drawable iconDrawable = mIconDrawableFactory.getBadgedIcon(appInfo);
- builder.setContentTitle(mContext.getString(R.string.pip_notification_title, appName))
- .setContentText(message)
- .setContentIntent(PendingIntent.getActivityAsUser(mContext, packageName.hashCode(),
- settingsIntent, FLAG_CANCEL_CURRENT, null, user))
- .setStyle(new Notification.BigTextStyle().bigText(message))
- .setLargeIcon(createBitmap(iconDrawable).createAshmemBitmap());
- return true;
- }
- return false;
- }
-
- private void registerAppOpsListener(String packageName) {
- mAppOpsManager.startWatchingMode(OP_PICTURE_IN_PICTURE, packageName,
- mAppOpsChangedListener);
- }
-
- private void unregisterAppOpsListener() {
- mAppOpsManager.stopWatchingMode(mAppOpsChangedListener);
- }
-
- /**
- * Bakes a drawable into a bitmap.
- */
- private Bitmap createBitmap(Drawable d) {
- Bitmap bitmap = Bitmap.createBitmap(d.getIntrinsicWidth(), d.getIntrinsicHeight(),
- Config.ARGB_8888);
- Canvas c = new Canvas(bitmap);
- d.setBounds(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());
- d.draw(c);
- c.setBitmap(null);
- return bitmap;
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java
index 0b7b6d5..927a49c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java
@@ -18,11 +18,6 @@
import static android.app.StatusBarManager.DISABLE2_QUICK_SETTINGS;
-import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_QS_DATE;
-
-import android.app.ActivityManager;
-import android.app.AlarmManager;
-import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
@@ -31,7 +26,6 @@
import android.graphics.drawable.Drawable;
import android.graphics.drawable.RippleDrawable;
import android.os.UserManager;
-import android.provider.AlarmClock;
import android.support.annotation.Nullable;
import android.support.annotation.VisibleForTesting;
import android.util.AttributeSet;
@@ -39,24 +33,19 @@
import android.view.View.OnClickListener;
import android.widget.FrameLayout;
import android.widget.ImageView;
-import android.widget.TextView;
import android.widget.Toast;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto;
-import com.android.keyguard.KeyguardStatusView;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.settingslib.Utils;
import com.android.settingslib.drawable.UserIconDrawable;
import com.android.systemui.Dependency;
-import com.android.systemui.FontSizeUtils;
import com.android.systemui.R;
import com.android.systemui.R.dimen;
-import com.android.systemui.R.id;
import com.android.systemui.SysUiServiceProvider;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.qs.TouchAnimator.Builder;
-import com.android.systemui.qs.TouchAnimator.ListenerAdapter;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.phone.ExpandableIndicator;
import com.android.systemui.statusbar.phone.MultiUserSwitch;
@@ -65,8 +54,6 @@
import com.android.systemui.statusbar.policy.NetworkController;
import com.android.systemui.statusbar.policy.NetworkController.EmergencyListener;
import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
-import com.android.systemui.statusbar.policy.NextAlarmController;
-import com.android.systemui.statusbar.policy.NextAlarmController.NextAlarmChangeCallback;
import com.android.systemui.statusbar.policy.UserInfoController;
import com.android.systemui.statusbar.policy.UserInfoController.OnUserInfoChangedListener;
import com.android.systemui.tuner.TunerService;
diff --git a/packages/SystemUI/tests/Android.mk b/packages/SystemUI/tests/Android.mk
index 9d44895..066cfe5 100644
--- a/packages/SystemUI/tests/Android.mk
+++ b/packages/SystemUI/tests/Android.mk
@@ -46,7 +46,12 @@
android-support-v7-mediarouter \
android-support-v7-palette \
android-support-v14-preference \
- android-support-v17-leanback
+ android-support-v17-leanback \
+ android-slices-core \
+ android-slices-view \
+ android-slices-builders \
+ apptoolkit-arch-core-runtime \
+ apptoolkit-lifecycle-extensions \
LOCAL_STATIC_JAVA_LIBRARIES := \
metrics-helper-lib \
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java
index 4eae342..be28569 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java
@@ -16,11 +16,10 @@
package com.android.systemui.keyguard;
-import android.app.slice.Slice;
-import android.app.slice.SliceItem;
-import android.app.slice.SliceQuery;
+import androidx.app.slice.Slice;
import android.content.Intent;
import android.net.Uri;
+import android.os.Debug;
import android.os.Handler;
import android.support.test.filters.SmallTest;
import android.testing.AndroidTestingRunner;
@@ -34,6 +33,9 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import androidx.app.slice.SliceItem;
+import androidx.app.slice.core.SliceQuery;
+
@SmallTest
@RunWith(AndroidTestingRunner.class)
@RunWithLooper(setAsMainLooper = true)
@@ -63,7 +65,8 @@
@Test
public void returnsValidSlice() {
Slice slice = mProvider.onBindSlice(Uri.parse(KeyguardSliceProvider.KEYGUARD_SLICE_URI));
- SliceItem text = SliceQuery.find(slice, SliceItem.FORMAT_TEXT, Slice.HINT_TITLE,
+ SliceItem text = SliceQuery.find(slice, android.app.slice.SliceItem.FORMAT_TEXT,
+ android.app.slice.Slice.HINT_TITLE,
null /* nonHints */);
Assert.assertNotNull("Slice must provide a title.", text);
}
@@ -78,9 +81,10 @@
@Test
public void updatesClock() {
+ mProvider.mUpdateClockInvokations = 0;
mProvider.mIntentReceiver.onReceive(getContext(), new Intent(Intent.ACTION_TIME_TICK));
TestableLooper.get(this).processAllMessages();
- Assert.assertEquals("Clock should have been updated.", 2 /* expected */,
+ Assert.assertEquals("Clock should have been updated.", 1 /* expected */,
mProvider.mUpdateClockInvokations);
}
diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto
index 935b787..1aaa538 100644
--- a/proto/src/metrics_constants.proto
+++ b/proto/src/metrics_constants.proto
@@ -5094,6 +5094,36 @@
// Tag used to report autofill field classification scores
FIELD_AUTOFILL_MATCH_SCORE = 1274;
+ // ACTION: Usb config has been changed to charging
+ // CATEGORY: SETTINGS
+ // OS: P
+ ACTION_USB_CONFIG_CHARGING = 1275;
+
+ // ACTION: Usb config has been changed to mtp (file transfer)
+ // CATEGORY: SETTINGS
+ // OS: P
+ ACTION_USB_CONFIG_MTP = 1276;
+
+ // ACTION: Usb config has been changed to ptp (photo transfer)
+ // CATEGORY: SETTINGS
+ // OS: P
+ ACTION_USB_CONFIG_PTP = 1277;
+
+ // ACTION: Usb config has been changed to rndis (usb tethering)
+ // CATEGORY: SETTINGS
+ // OS: P
+ ACTION_USB_CONFIG_RNDIS = 1278;
+
+ // ACTION: Usb config has been changed to midi
+ // CATEGORY: SETTINGS
+ // OS: P
+ ACTION_USB_CONFIG_MIDI = 1279;
+
+ // ACTION: Usb config has been changed to accessory
+ // CATEGORY: SETTINGS
+ // OS: P
+ ACTION_USB_CONFIG_ACCESSORY = 1280;
+
// ---- End P Constants, all P constants go above this line ----
// Add new aosp constants above this line.
// END OF AOSP CONSTANTS
diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java
index 06cf982..472723d 100644
--- a/services/core/java/com/android/server/AlarmManagerService.java
+++ b/services/core/java/com/android/server/AlarmManagerService.java
@@ -3035,15 +3035,8 @@
Slog.v(TAG, "sending alarm " + alarm);
}
if (RECORD_ALARMS_IN_HISTORY) {
- if (alarm.workSource != null && alarm.workSource.size() > 0) {
- for (int wi=0; wi<alarm.workSource.size(); wi++) {
- ActivityManager.noteAlarmStart(
- alarm.operation, alarm.workSource.get(wi), alarm.statsTag);
- }
- } else {
- ActivityManager.noteAlarmStart(
- alarm.operation, alarm.uid, alarm.statsTag);
- }
+ ActivityManager.noteAlarmStart(alarm.operation, alarm.workSource, alarm.uid,
+ alarm.statsTag);
}
mDeliveryTracker.deliverLocked(alarm, nowELAPSED, allowWhileIdle);
} catch (RuntimeException e) {
@@ -3553,15 +3546,8 @@
fs.aggregateTime += nowELAPSED - fs.startTime;
}
if (RECORD_ALARMS_IN_HISTORY) {
- if (inflight.mWorkSource != null && inflight.mWorkSource.size() > 0) {
- for (int wi=0; wi<inflight.mWorkSource.size(); wi++) {
- ActivityManager.noteAlarmFinish(
- inflight.mPendingIntent, inflight.mWorkSource.get(wi), inflight.mTag);
- }
- } else {
- ActivityManager.noteAlarmFinish(
- inflight.mPendingIntent, inflight.mUid, inflight.mTag);
- }
+ ActivityManager.noteAlarmFinish(inflight.mPendingIntent, inflight.mWorkSource,
+ inflight.mUid, inflight.mTag);
}
}
@@ -3771,18 +3757,9 @@
|| alarm.type == RTC_WAKEUP) {
bs.numWakeup++;
fs.numWakeup++;
- if (alarm.workSource != null && alarm.workSource.size() > 0) {
- for (int wi=0; wi<alarm.workSource.size(); wi++) {
- final String wsName = alarm.workSource.getName(wi);
- ActivityManager.noteWakeupAlarm(
- alarm.operation, alarm.workSource.get(wi),
- (wsName != null) ? wsName : alarm.packageName,
- alarm.statsTag);
- }
- } else {
- ActivityManager.noteWakeupAlarm(
- alarm.operation, alarm.uid, alarm.packageName, alarm.statsTag);
- }
+ ActivityManager.noteWakeupAlarm(
+ alarm.operation, alarm.workSource, alarm.uid, alarm.packageName,
+ alarm.statsTag);
}
}
}
diff --git a/services/core/java/com/android/server/EntropyMixer.java b/services/core/java/com/android/server/EntropyMixer.java
index 9877717..5e6e9d34 100644
--- a/services/core/java/com/android/server/EntropyMixer.java
+++ b/services/core/java/com/android/server/EntropyMixer.java
@@ -196,11 +196,14 @@
* Mixes in the output from HW RNG (if present) into the Linux RNG.
*/
private void addHwRandomEntropy() {
+ if (!new File(hwRandomDevice).exists()) {
+ // HW RNG not present/exposed -- ignore
+ return;
+ }
+
try {
RandomBlock.fromFile(hwRandomDevice).toFile(randomDevice, false);
Slog.i(TAG, "Added HW RNG output to entropy pool");
- } catch (FileNotFoundException ignored) {
- // HW RNG not present/exposed -- ignore
} catch (IOException e) {
Slog.w(TAG, "Failed to add HW RNG output to entropy pool", e);
}
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index 31aea63..46eea78 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -5595,25 +5595,26 @@
long ident = Binder.clearCallingIdentity();
try {
packages = mPackageManager.getPackagesForUid(callingUid);
+ if (packages != null) {
+ for (String name : packages) {
+ try {
+ PackageInfo packageInfo =
+ mPackageManager.getPackageInfo(name, 0 /* flags */);
+ if (packageInfo != null
+ && (packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM)
+ != 0) {
+ return true;
+ }
+ } catch (NameNotFoundException e) {
+ Log.w(TAG, String.format("Could not find package [%s]", name), e);
+ }
+ }
+ } else {
+ Log.w(TAG, "No known packages with uid " + callingUid);
+ }
} finally {
Binder.restoreCallingIdentity(ident);
}
- if (packages != null) {
- for (String name : packages) {
- try {
- PackageInfo packageInfo = mPackageManager.getPackageInfo(name, 0 /* flags */);
- if (packageInfo != null
- && (packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM)
- != 0) {
- return true;
- }
- } catch (NameNotFoundException e) {
- Log.w(TAG, String.format("Could not find package [%s]", name), e);
- }
- }
- } else {
- Log.w(TAG, "No known packages with uid " + callingUid);
- }
return false;
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index d92b3b8..5936ce1 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -13858,68 +13858,100 @@
Context.WINDOW_SERVICE)).addView(v, lp);
}
- public void noteWakeupAlarm(IIntentSender sender, int sourceUid, String sourcePkg, String tag) {
- if (sender != null && !(sender instanceof PendingIntentRecord)) {
- return;
+ @Override
+ public void noteWakeupAlarm(IIntentSender sender, WorkSource workSource, int sourceUid,
+ String sourcePkg, String tag) {
+ if (workSource != null && workSource.isEmpty()) {
+ workSource = null;
}
- final PendingIntentRecord rec = (PendingIntentRecord)sender;
- final BatteryStatsImpl stats = mBatteryStatsService.getActiveStatistics();
- synchronized (stats) {
- if (mBatteryStatsService.isOnBattery()) {
- mBatteryStatsService.enforceCallingPermission();
- int MY_UID = Binder.getCallingUid();
- final int uid;
- if (sender == null) {
- uid = sourceUid;
- } else {
- uid = rec.uid == MY_UID ? SYSTEM_UID : rec.uid;
+
+ if (sourceUid <= 0 && workSource == null) {
+ // Try and derive a UID to attribute things to based on the caller.
+ if (sender != null) {
+ if (!(sender instanceof PendingIntentRecord)) {
+ return;
}
- BatteryStatsImpl.Uid.Pkg pkg =
- stats.getPackageStatsLocked(sourceUid >= 0 ? sourceUid : uid,
- sourcePkg != null ? sourcePkg : rec.key.packageName);
- pkg.noteWakeupAlarmLocked(tag);
- StatsLog.write(StatsLog.WAKEUP_ALARM_OCCURRED, sourceUid >= 0 ? sourceUid : uid,
- tag);
+
+ final PendingIntentRecord rec = (PendingIntentRecord) sender;
+ final int callerUid = Binder.getCallingUid();
+ sourceUid = rec.uid == callerUid ? SYSTEM_UID : rec.uid;
+ } else {
+ // TODO(narayan): Should we throw an exception in this case ? It means that we
+ // haven't been able to derive a UID to attribute things to.
+ return;
}
}
+
+ if (DEBUG_POWER) {
+ Slog.w(TAG, "noteWakupAlarm[ sourcePkg=" + sourcePkg + ", sourceUid=" + sourceUid
+ + ", workSource=" + workSource + ", tag=" + tag + "]");
+ }
+
+ mBatteryStatsService.noteWakupAlarm(sourcePkg, sourceUid, workSource, tag);
}
- public void noteAlarmStart(IIntentSender sender, int sourceUid, String tag) {
- if (sender != null && !(sender instanceof PendingIntentRecord)) {
- return;
+ @Override
+ public void noteAlarmStart(IIntentSender sender, WorkSource workSource, int sourceUid,
+ String tag) {
+ if (workSource != null && workSource.isEmpty()) {
+ workSource = null;
}
- final PendingIntentRecord rec = (PendingIntentRecord)sender;
- final BatteryStatsImpl stats = mBatteryStatsService.getActiveStatistics();
- synchronized (stats) {
- mBatteryStatsService.enforceCallingPermission();
- int MY_UID = Binder.getCallingUid();
- final int uid;
- if (sender == null) {
- uid = sourceUid;
+
+ if (sourceUid <= 0 && workSource == null) {
+ // Try and derive a UID to attribute things to based on the caller.
+ if (sender != null) {
+ if (!(sender instanceof PendingIntentRecord)) {
+ return;
+ }
+
+ final PendingIntentRecord rec = (PendingIntentRecord) sender;
+ final int callerUid = Binder.getCallingUid();
+ sourceUid = rec.uid == callerUid ? SYSTEM_UID : rec.uid;
} else {
- uid = rec.uid == MY_UID ? SYSTEM_UID : rec.uid;
+ // TODO(narayan): Should we throw an exception in this case ? It means that we
+ // haven't been able to derive a UID to attribute things to.
+ return;
}
- mBatteryStatsService.noteAlarmStart(tag, sourceUid >= 0 ? sourceUid : uid);
}
+
+ if (DEBUG_POWER) {
+ Slog.w(TAG, "noteAlarmStart[sourceUid=" + sourceUid + ", workSource=" + workSource +
+ ", tag=" + tag + "]");
+ }
+
+ mBatteryStatsService.noteAlarmStart(tag, workSource, sourceUid);
}
- public void noteAlarmFinish(IIntentSender sender, int sourceUid, String tag) {
- if (sender != null && !(sender instanceof PendingIntentRecord)) {
- return;
+ @Override
+ public void noteAlarmFinish(IIntentSender sender, WorkSource workSource, int sourceUid,
+ String tag) {
+ if (workSource != null && workSource.isEmpty()) {
+ workSource = null;
}
- final PendingIntentRecord rec = (PendingIntentRecord)sender;
- final BatteryStatsImpl stats = mBatteryStatsService.getActiveStatistics();
- synchronized (stats) {
- mBatteryStatsService.enforceCallingPermission();
- int MY_UID = Binder.getCallingUid();
- final int uid;
- if (sender == null) {
- uid = sourceUid;
+
+ if (sourceUid <= 0 && workSource == null) {
+ // Try and derive a UID to attribute things to based on the caller.
+ if (sender != null) {
+ if (!(sender instanceof PendingIntentRecord)) {
+ return;
+ }
+
+ final PendingIntentRecord rec = (PendingIntentRecord) sender;
+ final int callerUid = Binder.getCallingUid();
+ sourceUid = rec.uid == callerUid ? SYSTEM_UID : rec.uid;
} else {
- uid = rec.uid == MY_UID ? SYSTEM_UID : rec.uid;
+ // TODO(narayan): Should we throw an exception in this case ? It means that we
+ // haven't been able to derive a UID to attribute things to.
+ return;
}
- mBatteryStatsService.noteAlarmFinish(tag, sourceUid >= 0 ? sourceUid : uid);
}
+
+ if (DEBUG_POWER) {
+ Slog.w(TAG, "noteAlarmFinish[sourceUid=" + sourceUid + ", workSource=" + workSource +
+ ", tag=" + tag + "]");
+ }
+
+ mBatteryStatsService.noteAlarmFinish(tag, workSource, sourceUid);
}
public boolean killPids(int[] pids, String pReason, boolean secure) {
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index 35318f6..430320a 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -38,6 +38,7 @@
import android.os.UserHandle;
import android.os.UserManagerInternal;
import android.os.WorkSource;
+import android.os.WorkSource.WorkChain;
import android.os.connectivity.CellularBatteryStats;
import android.os.health.HealthStatsParceler;
import android.os.health.HealthStatsWriter;
@@ -66,6 +67,7 @@
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CodingErrorAction;
import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ExecutionException;
@@ -446,17 +448,24 @@
}
}
- public void noteAlarmStart(String name, int uid) {
+ public void noteWakupAlarm(String name, int uid, WorkSource workSource, String tag) {
enforceCallingPermission();
synchronized (mStats) {
- mStats.noteAlarmStartLocked(name, uid);
+ mStats.noteWakupAlarmLocked(name, uid, workSource, tag);
}
}
- public void noteAlarmFinish(String name, int uid) {
+ public void noteAlarmStart(String name, WorkSource workSource, int uid) {
enforceCallingPermission();
synchronized (mStats) {
- mStats.noteAlarmFinishLocked(name, uid);
+ mStats.noteAlarmStartLocked(name, workSource, uid);
+ }
+ }
+
+ public void noteAlarmFinish(String name, WorkSource workSource, int uid) {
+ enforceCallingPermission();
+ synchronized (mStats) {
+ mStats.noteAlarmFinishLocked(name, workSource, uid);
}
}
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java
index 95f5cb7..1719710 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java
@@ -115,16 +115,12 @@
/**
* Returns the current generation ID of the platform key. This increments whenever a platform
* key has to be replaced. (e.g., because the user has removed and then re-added their lock
- * screen).
+ * screen). Returns -1 if no key has been generated yet.
*
* @hide
*/
public int getGenerationId() {
- int generationId = mDatabase.getPlatformKeyGenerationId(mUserId);
- if (generationId == -1) {
- return 1;
- }
- return generationId;
+ return mDatabase.getPlatformKeyGenerationId(mUserId);
}
/**
@@ -207,14 +203,22 @@
Locale.US, "Platform key generation %d exists already.", generationId));
return;
}
- if (generationId == 1) {
+ if (generationId == -1) {
Log.i(TAG, "Generating initial platform ID.");
} else {
Log.w(TAG, String.format(Locale.US, "Platform generation ID was %d but no "
+ "entry was present in AndroidKeyStore. Generating fresh key.", generationId));
}
+ if (generationId == -1) {
+ generationId = 1;
+ } else {
+ // Had to generate a fresh key, bump the generation id
+ generationId++;
+ }
+
generateAndLoadKey(generationId);
+ mDatabase.setPlatformKeyGenerationId(mUserId, generationId);
}
/**
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
index fe1cad4..eccf241 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
@@ -216,7 +216,6 @@
// Any application should be able to check status for its own keys.
// If caller is a recovery agent it can check statuses for other packages, but
// only for recoverable keys it manages.
- checkRecoverKeyStorePermission();
return mDatabase.getStatusForAllKeys(Binder.getCallingUid());
}
diff --git a/services/core/java/com/android/server/net/watchlist/NetworkWatchlistService.java b/services/core/java/com/android/server/net/watchlist/NetworkWatchlistService.java
index 171703a..f35e6ec 100644
--- a/services/core/java/com/android/server/net/watchlist/NetworkWatchlistService.java
+++ b/services/core/java/com/android/server/net/watchlist/NetworkWatchlistService.java
@@ -33,6 +33,7 @@
import android.util.Slog;
import com.android.internal.R;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.DumpUtils;
import com.android.internal.net.INetworkWatchlistManager;
@@ -92,6 +93,7 @@
}
}
+ @GuardedBy("mLoggingSwitchLock")
private volatile boolean mIsLoggingEnabled = false;
private final Object mLoggingSwitchLock = new Object();
@@ -220,36 +222,11 @@
}
}
- /**
- * Set a new network watchlist.
- * This method should be called by ConfigUpdater only.
- *
- * @return True if network watchlist is updated.
- */
- public boolean setNetworkSecurityWatchlist(List<byte[]> domainsCrc32Digests,
- List<byte[]> domainsSha256Digests,
- List<byte[]> ipAddressesCrc32Digests,
- List<byte[]> ipAddressesSha256Digests) {
- Slog.i(TAG, "Setting network watchlist");
- if (domainsCrc32Digests == null || domainsSha256Digests == null
- || ipAddressesCrc32Digests == null || ipAddressesSha256Digests == null) {
- Slog.e(TAG, "Parameters cannot be null");
- return false;
- }
- if (domainsCrc32Digests.size() != domainsSha256Digests.size()
- || ipAddressesCrc32Digests.size() != ipAddressesSha256Digests.size()) {
- Slog.e(TAG, "Must need to have the same number of CRC32 and SHA256 digests");
- return false;
- }
- if (domainsSha256Digests.size() + ipAddressesSha256Digests.size()
- > MAX_NUM_OF_WATCHLIST_DIGESTS) {
- Slog.e(TAG, "Total watchlist size cannot exceed " + MAX_NUM_OF_WATCHLIST_DIGESTS);
- return false;
- }
- mSettings.writeSettingsToDisk(domainsCrc32Digests, domainsSha256Digests,
- ipAddressesCrc32Digests, ipAddressesSha256Digests);
- Slog.i(TAG, "Set network watchlist: Success");
- return true;
+ @Override
+ public void reloadWatchlist() throws RemoteException {
+ enforceWatchlistLoggingPermission();
+ Slog.i(TAG, "Reloading watchlist");
+ mSettings.reloadSettings();
}
@Override
diff --git a/services/core/java/com/android/server/net/watchlist/WatchlistReportDbHelper.java b/services/core/java/com/android/server/net/watchlist/WatchlistReportDbHelper.java
index f48463f..838aa53 100644
--- a/services/core/java/com/android/server/net/watchlist/WatchlistReportDbHelper.java
+++ b/services/core/java/com/android/server/net/watchlist/WatchlistReportDbHelper.java
@@ -21,10 +21,12 @@
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
+import android.os.Environment;
import android.util.Pair;
import com.android.internal.util.HexDump;
+import java.io.File;
import java.util.ArrayList;
import java.util.GregorianCalendar;
import java.util.HashMap;
@@ -83,9 +85,12 @@
HashMap<String, String> appDigestCNCList;
}
+ static File getSystemWatchlistDbFile() {
+ return new File(Environment.getDataSystemDirectory(), NAME);
+ }
+
private WatchlistReportDbHelper(Context context) {
- super(context, WatchlistSettings.getSystemWatchlistFile(NAME).getAbsolutePath(),
- null, VERSION);
+ super(context, getSystemWatchlistDbFile().getAbsolutePath(), null, VERSION);
// Memory optimization - close idle connections after 30s of inactivity
setIdleConnectionTimeout(IDLE_CONNECTION_TIMEOUT_MS);
}
diff --git a/services/core/java/com/android/server/net/watchlist/WatchlistSettings.java b/services/core/java/com/android/server/net/watchlist/WatchlistSettings.java
index c50f0d5..70002ea 100644
--- a/services/core/java/com/android/server/net/watchlist/WatchlistSettings.java
+++ b/services/core/java/com/android/server/net/watchlist/WatchlistSettings.java
@@ -19,8 +19,10 @@
import android.os.Environment;
import android.util.AtomicFile;
import android.util.Log;
+import android.util.Slog;
import android.util.Xml;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.FastXmlSerializer;
import com.android.internal.util.HexDump;
@@ -51,10 +53,9 @@
class WatchlistSettings {
private static final String TAG = "WatchlistSettings";
- // Settings xml will be stored in /data/system/network_watchlist/watchlist_settings.xml
- static final String SYSTEM_WATCHLIST_DIR = "network_watchlist";
-
- private static final String WATCHLIST_XML_FILE = "watchlist_settings.xml";
+ // Watchlist config that pushed by ConfigUpdater.
+ private static final String NETWORK_WATCHLIST_DB_PATH =
+ "/data/misc/network_watchlist/network_watchlist.xml";
private static class XmlTags {
private static final String WATCHLIST_SETTINGS = "watchlist-settings";
@@ -65,86 +66,74 @@
private static final String HASH = "hash";
}
- private static WatchlistSettings sInstance = new WatchlistSettings();
- private final AtomicFile mXmlFile;
- private final Object mLock = new Object();
- private HarmfulDigests mCrc32DomainDigests = new HarmfulDigests(new ArrayList<>());
- private HarmfulDigests mSha256DomainDigests = new HarmfulDigests(new ArrayList<>());
- private HarmfulDigests mCrc32IpDigests = new HarmfulDigests(new ArrayList<>());
- private HarmfulDigests mSha256IpDigests = new HarmfulDigests(new ArrayList<>());
+ private static class CrcShaDigests {
+ final HarmfulDigests crc32Digests;
+ final HarmfulDigests sha256Digests;
- public static synchronized WatchlistSettings getInstance() {
+ public CrcShaDigests(HarmfulDigests crc32Digests, HarmfulDigests sha256Digests) {
+ this.crc32Digests = crc32Digests;
+ this.sha256Digests = sha256Digests;
+ }
+ }
+
+ private final static WatchlistSettings sInstance = new WatchlistSettings();
+ private final AtomicFile mXmlFile;
+
+ private volatile CrcShaDigests mDomainDigests;
+ private volatile CrcShaDigests mIpDigests;
+
+ public static WatchlistSettings getInstance() {
return sInstance;
}
private WatchlistSettings() {
- this(getSystemWatchlistFile(WATCHLIST_XML_FILE));
+ this(new File(NETWORK_WATCHLIST_DB_PATH));
}
@VisibleForTesting
protected WatchlistSettings(File xmlFile) {
mXmlFile = new AtomicFile(xmlFile);
- readSettingsLocked();
+ reloadSettings();
}
- static File getSystemWatchlistFile(String filename) {
- final File dataSystemDir = Environment.getDataSystemDirectory();
- final File systemWatchlistDir = new File(dataSystemDir, SYSTEM_WATCHLIST_DIR);
- systemWatchlistDir.mkdirs();
- return new File(systemWatchlistDir, filename);
- }
-
- private void readSettingsLocked() {
- synchronized (mLock) {
- FileInputStream stream;
- try {
- stream = mXmlFile.openRead();
- } catch (FileNotFoundException e) {
- Log.i(TAG, "No watchlist settings: " + mXmlFile.getBaseFile().getAbsolutePath());
- return;
- }
+ public void reloadSettings() {
+ try (FileInputStream stream = mXmlFile.openRead()){
final List<byte[]> crc32DomainList = new ArrayList<>();
final List<byte[]> sha256DomainList = new ArrayList<>();
final List<byte[]> crc32IpList = new ArrayList<>();
final List<byte[]> sha256IpList = new ArrayList<>();
- try {
- XmlPullParser parser = Xml.newPullParser();
- parser.setInput(stream, StandardCharsets.UTF_8.name());
- parser.nextTag();
- parser.require(XmlPullParser.START_TAG, null, XmlTags.WATCHLIST_SETTINGS);
- while (parser.nextTag() == XmlPullParser.START_TAG) {
- String tagName = parser.getName();
- switch (tagName) {
- case XmlTags.CRC32_DOMAIN:
- parseHash(parser, tagName, crc32DomainList);
- break;
- case XmlTags.CRC32_IP:
- parseHash(parser, tagName, crc32IpList);
- break;
- case XmlTags.SHA256_DOMAIN:
- parseHash(parser, tagName, sha256DomainList);
- break;
- case XmlTags.SHA256_IP:
- parseHash(parser, tagName, sha256IpList);
- break;
- default:
- Log.w(TAG, "Unknown element: " + parser.getName());
- XmlUtils.skipCurrentTag(parser);
- }
- }
- parser.require(XmlPullParser.END_TAG, null, XmlTags.WATCHLIST_SETTINGS);
- writeSettingsToMemory(crc32DomainList, sha256DomainList, crc32IpList, sha256IpList);
- } catch (IllegalStateException | NullPointerException | NumberFormatException |
- XmlPullParserException | IOException | IndexOutOfBoundsException e) {
- Log.w(TAG, "Failed parsing " + e);
- } finally {
- try {
- stream.close();
- } catch (IOException e) {
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(stream, StandardCharsets.UTF_8.name());
+ parser.nextTag();
+ parser.require(XmlPullParser.START_TAG, null, XmlTags.WATCHLIST_SETTINGS);
+ while (parser.nextTag() == XmlPullParser.START_TAG) {
+ String tagName = parser.getName();
+ switch (tagName) {
+ case XmlTags.CRC32_DOMAIN:
+ parseHash(parser, tagName, crc32DomainList);
+ break;
+ case XmlTags.CRC32_IP:
+ parseHash(parser, tagName, crc32IpList);
+ break;
+ case XmlTags.SHA256_DOMAIN:
+ parseHash(parser, tagName, sha256DomainList);
+ break;
+ case XmlTags.SHA256_IP:
+ parseHash(parser, tagName, sha256IpList);
+ break;
+ default:
+ Log.w(TAG, "Unknown element: " + parser.getName());
+ XmlUtils.skipCurrentTag(parser);
}
}
+ parser.require(XmlPullParser.END_TAG, null, XmlTags.WATCHLIST_SETTINGS);
+ writeSettingsToMemory(crc32DomainList, sha256DomainList, crc32IpList, sha256IpList);
+ Log.i(TAG, "Reload watchlist done");
+ } catch (IllegalStateException | NullPointerException | NumberFormatException |
+ XmlPullParserException | IOException | IndexOutOfBoundsException e) {
+ Slog.e(TAG, "Failed parsing xml", e);
}
}
@@ -161,101 +150,61 @@
}
/**
- * Write network watchlist settings to disk.
- * Adb should not use it, should use writeSettingsToMemory directly instead.
- */
- public void writeSettingsToDisk(List<byte[]> newCrc32DomainList,
- List<byte[]> newSha256DomainList,
- List<byte[]> newCrc32IpList,
- List<byte[]> newSha256IpList) {
- synchronized (mLock) {
- FileOutputStream stream;
- try {
- stream = mXmlFile.startWrite();
- } catch (IOException e) {
- Log.w(TAG, "Failed to write display settings: " + e);
- return;
- }
-
- try {
- XmlSerializer out = new FastXmlSerializer();
- out.setOutput(stream, StandardCharsets.UTF_8.name());
- out.startDocument(null, true);
- out.startTag(null, XmlTags.WATCHLIST_SETTINGS);
-
- writeHashSetToXml(out, XmlTags.SHA256_DOMAIN, newSha256DomainList);
- writeHashSetToXml(out, XmlTags.SHA256_IP, newSha256IpList);
- writeHashSetToXml(out, XmlTags.CRC32_DOMAIN, newCrc32DomainList);
- writeHashSetToXml(out, XmlTags.CRC32_IP, newCrc32IpList);
-
- out.endTag(null, XmlTags.WATCHLIST_SETTINGS);
- out.endDocument();
- mXmlFile.finishWrite(stream);
- writeSettingsToMemory(newCrc32DomainList, newSha256DomainList, newCrc32IpList,
- newSha256IpList);
- } catch (IOException e) {
- Log.w(TAG, "Failed to write display settings, restoring backup.", e);
- mXmlFile.failWrite(stream);
- }
- }
- }
-
- /**
* Write network watchlist settings to memory.
*/
public void writeSettingsToMemory(List<byte[]> newCrc32DomainList,
List<byte[]> newSha256DomainList,
List<byte[]> newCrc32IpList,
List<byte[]> newSha256IpList) {
- synchronized (mLock) {
- mCrc32DomainDigests = new HarmfulDigests(newCrc32DomainList);
- mCrc32IpDigests = new HarmfulDigests(newCrc32IpList);
- mSha256DomainDigests = new HarmfulDigests(newSha256DomainList);
- mSha256IpDigests = new HarmfulDigests(newSha256IpList);
- }
- }
-
- private static void writeHashSetToXml(XmlSerializer out, String tagName, List<byte[]> hashSet)
- throws IOException {
- out.startTag(null, tagName);
- for (byte[] hash : hashSet) {
- out.startTag(null, XmlTags.HASH);
- out.text(HexDump.toHexString(hash));
- out.endTag(null, XmlTags.HASH);
- }
- out.endTag(null, tagName);
+ mDomainDigests = new CrcShaDigests(new HarmfulDigests(newCrc32DomainList),
+ new HarmfulDigests(newSha256DomainList));
+ mIpDigests = new CrcShaDigests(new HarmfulDigests(newCrc32IpList),
+ new HarmfulDigests(newSha256IpList));
}
public boolean containsDomain(String domain) {
+ final CrcShaDigests domainDigests = mDomainDigests;
+ if (domainDigests == null) {
+ Slog.wtf(TAG, "domainDigests should not be null");
+ return false;
+ }
// First it does a quick CRC32 check.
final byte[] crc32 = getCrc32(domain);
- if (!mCrc32DomainDigests.contains(crc32)) {
+ if (!domainDigests.crc32Digests.contains(crc32)) {
return false;
}
// Now we do a slow SHA256 check.
final byte[] sha256 = getSha256(domain);
- return mSha256DomainDigests.contains(sha256);
+ return domainDigests.sha256Digests.contains(sha256);
}
public boolean containsIp(String ip) {
+ final CrcShaDigests ipDigests = mIpDigests;
+ if (ipDigests == null) {
+ Slog.wtf(TAG, "ipDigests should not be null");
+ return false;
+ }
// First it does a quick CRC32 check.
final byte[] crc32 = getCrc32(ip);
- if (!mCrc32IpDigests.contains(crc32)) {
+ if (!ipDigests.crc32Digests.contains(crc32)) {
return false;
}
// Now we do a slow SHA256 check.
final byte[] sha256 = getSha256(ip);
- return mSha256IpDigests.contains(sha256);
+ return ipDigests.sha256Digests.contains(sha256);
}
- /** Get CRC32 of a string */
+ /** Get CRC32 of a string
+ *
+ * TODO: Review if we should use CRC32 or other algorithms
+ */
private byte[] getCrc32(String str) {
final CRC32 crc = new CRC32();
crc.update(str.getBytes());
final long tmp = crc.getValue();
- return new byte[]{(byte)(tmp >> 24 & 255), (byte)(tmp >> 16 & 255),
- (byte)(tmp >> 8 & 255), (byte)(tmp & 255)};
+ return new byte[]{(byte) (tmp >> 24 & 255), (byte) (tmp >> 16 & 255),
+ (byte) (tmp >> 8 & 255), (byte) (tmp & 255)};
}
/** Get SHA256 of a string */
@@ -273,12 +222,12 @@
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.println("Domain CRC32 digest list:");
- mCrc32DomainDigests.dump(fd, pw, args);
+ mDomainDigests.crc32Digests.dump(fd, pw, args);
pw.println("Domain SHA256 digest list:");
- mSha256DomainDigests.dump(fd, pw, args);
+ mDomainDigests.sha256Digests.dump(fd, pw, args);
pw.println("Ip CRC32 digest list:");
- mCrc32IpDigests.dump(fd, pw, args);
+ mIpDigests.crc32Digests.dump(fd, pw, args);
pw.println("Ip SHA256 digest list:");
- mSha256IpDigests.dump(fd, pw, args);
+ mIpDigests.sha256Digests.dump(fd, pw, args);
}
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 696d895..1157af4 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -163,10 +163,12 @@
import android.content.pm.PackageInfo;
import android.content.pm.PackageInfoLite;
import android.content.pm.PackageInstaller;
+import android.content.pm.PackageList;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.PackageManager.LegacyPackageDeleteObserver;
import android.content.pm.PackageManager.PackageInfoFlags;
+import android.content.pm.PackageManagerInternal.PackageListObserver;
import android.content.pm.PackageParser;
import android.content.pm.PackageParser.ActivityIntentInfo;
import android.content.pm.PackageParser.Package;
@@ -757,6 +759,9 @@
@GuardedBy("mPackages")
final SparseArray<Map<String, Integer>> mChangedPackagesSequenceNumbers = new SparseArray<>();
+ @GuardedBy("mPackages")
+ final private ArraySet<PackageListObserver> mPackageListObservers = new ArraySet<>();
+
class PackageParserCallback implements PackageParser.Callback {
@Override public final boolean hasFeature(String feature) {
return PackageManagerService.this.hasSystemFeature(feature, 0);
@@ -2095,6 +2100,10 @@
}
}
+ if (allNewUsers && !update) {
+ notifyPackageAdded(packageName);
+ }
+
// Log current value of "unknown sources" setting
EventLog.writeEvent(EventLogTags.UNKNOWN_SOURCES_ENABLED,
getUnknownSourcesSettings());
@@ -12983,6 +12992,34 @@
});
}
+ @Override
+ public void notifyPackageAdded(String packageName) {
+ final PackageListObserver[] observers;
+ synchronized (mPackages) {
+ if (mPackageListObservers.size() == 0) {
+ return;
+ }
+ observers = (PackageListObserver[]) mPackageListObservers.toArray();
+ }
+ for (int i = observers.length - 1; i >= 0; --i) {
+ observers[i].onPackageAdded(packageName);
+ }
+ }
+
+ @Override
+ public void notifyPackageRemoved(String packageName) {
+ final PackageListObserver[] observers;
+ synchronized (mPackages) {
+ if (mPackageListObservers.size() == 0) {
+ return;
+ }
+ observers = (PackageListObserver[]) mPackageListObservers.toArray();
+ }
+ for (int i = observers.length - 1; i >= 0; --i) {
+ observers[i].onPackageRemoved(packageName);
+ }
+ }
+
/**
* Sends a broadcast for the given action.
* <p>If {@code isInstantApp} is {@code true}, then the broadcast is protected with
@@ -17640,6 +17677,7 @@
removedPackage, extras,
Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND,
null, null, broadcastUsers, instantUserIds);
+ packageSender.notifyPackageRemoved(removedPackage);
}
}
if (removedAppId >= 0) {
@@ -20395,10 +20433,9 @@
}
}
sUserManager.systemReady();
-
// If we upgraded grant all default permissions before kicking off.
for (int userId : grantPermissionsUserIds) {
- mDefaultPermissionPolicy.grantDefaultPermissions(mPackages.values(), userId);
+ mDefaultPermissionPolicy.grantDefaultPermissions(userId);
}
if (grantPermissionsUserIds == EMPTY_INT_ARRAY) {
@@ -22445,8 +22482,8 @@
}
void onNewUserCreated(final int userId) {
+ mDefaultPermissionPolicy.grantDefaultPermissions(userId);
synchronized(mPackages) {
- mDefaultPermissionPolicy.grantDefaultPermissions(mPackages.values(), userId);
// If permission review for legacy apps is required, we represent
// dagerous permissions for such apps as always granted runtime
// permissions to keep per user flag state whether review is needed.
@@ -22933,6 +22970,29 @@
}
@Override
+ public PackageList getPackageList(PackageListObserver observer) {
+ synchronized (mPackages) {
+ final int N = mPackages.size();
+ final ArrayList<String> list = new ArrayList<>(N);
+ for (int i = 0; i < N; i++) {
+ list.add(mPackages.keyAt(i));
+ }
+ final PackageList packageList = new PackageList(list, observer);
+ if (observer != null) {
+ mPackageListObservers.add(packageList);
+ }
+ return packageList;
+ }
+ }
+
+ @Override
+ public void removePackageListObserver(PackageListObserver observer) {
+ synchronized (mPackages) {
+ mPackageListObservers.remove(observer);
+ }
+ }
+
+ @Override
public PackageParser.Package getDisabledPackage(String packageName) {
synchronized (mPackages) {
final PackageSetting ps = mSettings.getDisabledSystemPkgLPr(packageName);
@@ -23594,4 +23654,6 @@
final IIntentReceiver finishedReceiver, final int[] userIds, int[] instantUserIds);
void sendPackageAddedForNewUsers(String packageName, boolean sendBootCompleted,
boolean includeStopped, int appId, int[] userIds, int[] instantUserIds);
+ void notifyPackageAdded(String packageName);
+ void notifyPackageRemoved(String packageName);
}
diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
index 01f3c57..d38dc9a 100644
--- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
@@ -30,6 +30,7 @@
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
+import android.content.pm.PackageList;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.PackageParser;
@@ -252,11 +253,11 @@
}
}
- public void grantDefaultPermissions(Collection<PackageParser.Package> packages, int userId) {
+ public void grantDefaultPermissions(int userId) {
if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_EMBEDDED, 0)) {
- grantAllRuntimePermissions(packages, userId);
+ grantAllRuntimePermissions(userId);
} else {
- grantPermissionsToSysComponentsAndPrivApps(packages, userId);
+ grantPermissionsToSysComponentsAndPrivApps(userId);
grantDefaultSystemHandlerPermissions(userId);
grantDefaultPermissionExceptions(userId);
}
@@ -278,10 +279,14 @@
}
}
- private void grantAllRuntimePermissions(
- Collection<PackageParser.Package> packages, int userId) {
+ private void grantAllRuntimePermissions(int userId) {
Log.i(TAG, "Granting all runtime permissions for user " + userId);
- for (PackageParser.Package pkg : packages) {
+ final PackageList packageList = mServiceInternal.getPackageList();
+ for (String packageName : packageList.getPackageNames()) {
+ final PackageParser.Package pkg = mServiceInternal.getPackage(packageName);
+ if (pkg == null) {
+ continue;
+ }
grantRuntimePermissionsForPackage(userId, pkg);
}
}
@@ -290,10 +295,14 @@
mHandler.sendEmptyMessage(MSG_READ_DEFAULT_PERMISSION_EXCEPTIONS);
}
- private void grantPermissionsToSysComponentsAndPrivApps(
- Collection<PackageParser.Package> packages, int userId) {
+ private void grantPermissionsToSysComponentsAndPrivApps(int userId) {
Log.i(TAG, "Granting permissions to platform components for user " + userId);
- for (PackageParser.Package pkg : packages) {
+ final PackageList packageList = mServiceInternal.getPackageList();
+ for (String packageName : packageList.getPackageNames()) {
+ final PackageParser.Package pkg = mServiceInternal.getPackage(packageName);
+ if (pkg == null) {
+ continue;
+ }
if (!isSysComponentOrPersistentPlatformSignedPrivApp(pkg)
|| !doesPackageSupportRuntimePermissions(pkg)
|| pkg.requestedPermissions.isEmpty()) {
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 02c8f68..bc7f2e6 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -57,6 +57,7 @@
import android.os.UserHandle;
import android.os.UserManager;
import android.os.WorkSource;
+import android.os.WorkSource.WorkChain;
import android.provider.Settings;
import android.provider.Settings.SettingNotFoundException;
import android.service.dreams.DreamManagerInternal;
@@ -1976,6 +1977,16 @@
return true;
}
}
+
+ final ArrayList<WorkChain> workChains = wakeLock.mWorkSource.getWorkChains();
+ if (workChains != null) {
+ for (int k = 0; k < workChains.size(); k++) {
+ final int uid = workChains.get(k).getAttributionUid();
+ if (userId == UserHandle.getUserId(uid)) {
+ return true;
+ }
+ }
+ }
}
return userId == UserHandle.getUserId(wakeLock.mOwnerUid);
}
diff --git a/services/core/java/com/android/server/updates/NetworkWatchlistInstallReceiver.java b/services/core/java/com/android/server/updates/NetworkWatchlistInstallReceiver.java
new file mode 100644
index 0000000..3b7ddc2
--- /dev/null
+++ b/services/core/java/com/android/server/updates/NetworkWatchlistInstallReceiver.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.updates;
+
+import android.content.Context;
+import android.content.Intent;
+import android.net.NetworkWatchlistManager;
+import android.os.RemoteException;
+import android.util.Slog;
+
+public class NetworkWatchlistInstallReceiver extends ConfigUpdateInstallReceiver {
+
+ public NetworkWatchlistInstallReceiver() {
+ super("/data/misc/network_watchlist/", "network_watchlist.xml", "metadata/", "version");
+ }
+
+ @Override
+ protected void postInstall(Context context, Intent intent) {
+ try {
+ context.getSystemService(NetworkWatchlistManager.class).reloadWatchlist();
+ } catch (Exception e) {
+ // Network Watchlist is not available
+ Slog.wtf("NetworkWatchlistInstallReceiver", "Unable to reload watchlist");
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index dfb385b..863922f 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -2222,12 +2222,13 @@
mWinAnimator + ": " + mPolicyVisibilityAfterAnim);
}
mPolicyVisibility = mPolicyVisibilityAfterAnim;
- setDisplayLayoutNeeded();
if (!mPolicyVisibility) {
+ mWinAnimator.hide("checkPolicyVisibilityChange");
if (mService.mCurrentFocus == this) {
if (DEBUG_FOCUS_LIGHT) Slog.i(TAG,
"setAnimationLocked: setting mFocusMayChange true");
mService.mFocusMayChange = true;
+ setDisplayLayoutNeeded();
}
// Window is no longer visible -- make sure if we were waiting
// for it to be displayed before enabling the display, that
@@ -4291,8 +4292,22 @@
float9[Matrix.MSKEW_Y] = mWinAnimator.mDtDx;
float9[Matrix.MSKEW_X] = mWinAnimator.mDtDy;
float9[Matrix.MSCALE_Y] = mWinAnimator.mDsDy;
- float9[Matrix.MTRANS_X] = mSurfacePosition.x + mShownPosition.x;
- float9[Matrix.MTRANS_Y] = mSurfacePosition.y + mShownPosition.y;
+ int x = mSurfacePosition.x + mShownPosition.x;
+ int y = mSurfacePosition.y + mShownPosition.y;
+
+ // If changed, also adjust transformFrameToSurfacePosition
+ final WindowContainer parent = getParent();
+ if (isChildWindow()) {
+ final WindowState parentWindow = getParentWindow();
+ x += parentWindow.mFrame.left - parentWindow.mAttrs.surfaceInsets.left;
+ y += parentWindow.mFrame.top - parentWindow.mAttrs.surfaceInsets.top;
+ } else if (parent != null) {
+ final Rect parentBounds = parent.getBounds();
+ x += parentBounds.left;
+ y += parentBounds.top;
+ }
+ float9[Matrix.MTRANS_X] = x;
+ float9[Matrix.MTRANS_Y] = y;
float9[Matrix.MPERSP_0] = 0;
float9[Matrix.MPERSP_1] = 0;
float9[Matrix.MPERSP_2] = 1;
@@ -4439,6 +4454,8 @@
private void transformFrameToSurfacePosition(int left, int top, Point outPoint) {
outPoint.set(left, top);
+
+ // If changed, also adjust getTransformationMatrix
final WindowContainer parentWindowContainer = getParent();
if (isChildWindow()) {
// TODO: This probably falls apart at some point and we should
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java
index 6f13a98..97fbca2 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java
@@ -205,6 +205,14 @@
}
@Test
+ public void init_savesGenerationIdToDatabase() throws Exception {
+ mPlatformKeyManager.init();
+
+ assertEquals(1,
+ mRecoverableKeyStoreDb.getPlatformKeyGenerationId(USER_ID_FIXTURE));
+ }
+
+ @Test
public void init_setsGenerationIdTo1() throws Exception {
mPlatformKeyManager.init();
@@ -212,7 +220,38 @@
}
@Test
+ public void init_incrementsGenerationIdIfKeyIsUnavailable() throws Exception {
+ mPlatformKeyManager.init();
+
+ mPlatformKeyManager.init();
+
+ assertEquals(2, mPlatformKeyManager.getGenerationId());
+ }
+
+ @Test
+ public void init_doesNotIncrementGenerationIdIfKeyAvailable() throws Exception {
+ mPlatformKeyManager.init();
+ when(mKeyStoreProxy
+ .containsAlias("com.android.server.locksettings.recoverablekeystore/"
+ + "platform/42/1/decrypt")).thenReturn(true);
+ when(mKeyStoreProxy
+ .containsAlias("com.android.server.locksettings.recoverablekeystore/"
+ + "platform/42/1/encrypt")).thenReturn(true);
+
+ mPlatformKeyManager.init();
+
+ assertEquals(1, mPlatformKeyManager.getGenerationId());
+ }
+
+ @Test
+ public void getGenerationId_returnsMinusOneIfNotInitialized() throws Exception {
+ assertEquals(-1, mPlatformKeyManager.getGenerationId());
+ }
+
+ @Test
public void getDecryptKey_getsDecryptKeyWithCorrectAlias() throws Exception {
+ mPlatformKeyManager.init();
+
mPlatformKeyManager.getDecryptKey();
verify(mKeyStoreProxy).getKey(
@@ -222,6 +261,8 @@
@Test
public void getEncryptKey_getsDecryptKeyWithCorrectAlias() throws Exception {
+ mPlatformKeyManager.init();
+
mPlatformKeyManager.getEncryptKey();
verify(mKeyStoreProxy).getKey(
diff --git a/services/tests/servicestests/src/com/android/server/net/watchlist/WatchlistSettingsTests.java b/services/tests/servicestests/src/com/android/server/net/watchlist/WatchlistSettingsTests.java
index f3cb980..212d25d 100644
--- a/services/tests/servicestests/src/com/android/server/net/watchlist/WatchlistSettingsTests.java
+++ b/services/tests/servicestests/src/com/android/server/net/watchlist/WatchlistSettingsTests.java
@@ -95,41 +95,6 @@
}
@Test
- public void testWatchlistSettings_writeSettingsToDisk() throws Exception {
- copyWatchlistSettingsXml(mContext, TEST_XML_1, mTestXmlFile);
- WatchlistSettings settings = new WatchlistSettings(mTestXmlFile);
- settings.writeSettingsToDisk(Arrays.asList(TEST_NEW_CC_DOMAIN_CRC32),
- Arrays.asList(TEST_NEW_CC_DOMAIN_SHA256), Arrays.asList(TEST_NEW_CC_IP_CRC32),
- Arrays.asList(TEST_NEW_CC_IP_SHA256));
- // Ensure old watchlist is not in memory
- assertFalse(settings.containsDomain(TEST_CC_DOMAIN));
- assertFalse(settings.containsIp(TEST_CC_IP));
- assertFalse(settings.containsDomain(TEST_NOT_EXIST_CC_DOMAIN));
- assertFalse(settings.containsIp(TEST_NOT_EXIST_CC_IP));
- assertFalse(settings.containsDomain(TEST_SHA256_ONLY_DOMAIN));
- assertFalse(settings.containsIp(TEST_SHA256_ONLY_IP));
- assertFalse(settings.containsDomain(TEST_CRC32_ONLY_DOMAIN));
- assertFalse(settings.containsIp(TEST_CRC32_ONLY_IP));
- // Ensure new watchlist is in memory
- assertTrue(settings.containsDomain(TEST_NEW_CC_DOMAIN));
- assertTrue(settings.containsIp(TEST_NEW_CC_IP));
- // Reload settings from disk and test again
- settings = new WatchlistSettings(mTestXmlFile);
- // Ensure old watchlist is not in memory
- assertFalse(settings.containsDomain(TEST_CC_DOMAIN));
- assertFalse(settings.containsIp(TEST_CC_IP));
- assertFalse(settings.containsDomain(TEST_NOT_EXIST_CC_DOMAIN));
- assertFalse(settings.containsIp(TEST_NOT_EXIST_CC_IP));
- assertFalse(settings.containsDomain(TEST_SHA256_ONLY_DOMAIN));
- assertFalse(settings.containsIp(TEST_SHA256_ONLY_IP));
- assertFalse(settings.containsDomain(TEST_CRC32_ONLY_DOMAIN));
- assertFalse(settings.containsIp(TEST_CRC32_ONLY_IP));
- // Ensure new watchlist is in memory
- assertTrue(settings.containsDomain(TEST_NEW_CC_DOMAIN));
- assertTrue(settings.containsIp(TEST_NEW_CC_IP));
- }
-
- @Test
public void testWatchlistSettings_writeSettingsToMemory() throws Exception {
copyWatchlistSettingsXml(mContext, TEST_XML_1, mTestXmlFile);
WatchlistSettings settings = new WatchlistSettings(mTestXmlFile);
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageManagerServiceTest.java
index e12a8da..cdac516 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageManagerServiceTest.java
@@ -17,75 +17,93 @@
package com.android.server.pm;
import android.content.IIntentReceiver;
-
import android.os.Bundle;
+import android.support.test.runner.AndroidJUnit4;
-import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.SmallTest;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
import java.io.File;
// runtest -c com.android.server.pm.PackageManagerServiceTest frameworks-services
-
-@SmallTest
-public class PackageManagerServiceTest extends AndroidTestCase {
- @Override
- protected void setUp() throws Exception {
- super.setUp();
+// bit FrameworksServicesTests:com.android.server.pm.PackageManagerServiceTest
+@RunWith(AndroidJUnit4.class)
+public class PackageManagerServiceTest {
+ @Before
+ public void setUp() throws Exception {
}
- @Override
- protected void tearDown() throws Exception {
- super.tearDown();
+ @After
+ public void tearDown() throws Exception {
}
+ @Test
public void testPackageRemoval() throws Exception {
- class PackageSenderImpl implements PackageSender {
- public void sendPackageBroadcast(final String action, final String pkg,
- final Bundle extras, final int flags, final String targetPkg,
- final IIntentReceiver finishedReceiver, final int[] userIds,
- int[] instantUserIds) {
+ class PackageSenderImpl implements PackageSender {
+ public void sendPackageBroadcast(final String action, final String pkg,
+ final Bundle extras, final int flags, final String targetPkg,
+ final IIntentReceiver finishedReceiver, final int[] userIds,
+ int[] instantUserIds) {
+ }
+
+ public void sendPackageAddedForNewUsers(String packageName,
+ boolean sendBootComplete, boolean includeStopped, int appId,
+ int[] userIds, int[] instantUserIds) {
+ }
+
+ @Override
+ public void notifyPackageAdded(String packageName) {
+ }
+
+ @Override
+ public void notifyPackageRemoved(String packageName) {
+ }
}
- public void sendPackageAddedForNewUsers(String packageName,
- boolean sendBootComplete, boolean includeStopped, int appId,
- int[] userIds, int[] instantUserIds) {
- }
- }
+ PackageSenderImpl sender = new PackageSenderImpl();
+ PackageSetting setting = null;
+ PackageManagerService.PackageRemovedInfo pri =
+ new PackageManagerService.PackageRemovedInfo(sender);
- PackageSenderImpl sender = new PackageSenderImpl();
- PackageSetting setting = null;
- PackageManagerService.PackageRemovedInfo pri =
- new PackageManagerService.PackageRemovedInfo(sender);
+ // Initial conditions: nothing there
+ Assert.assertNull(pri.removedUsers);
+ Assert.assertNull(pri.broadcastUsers);
- // Initial conditions: nothing there
- assertNull(pri.removedUsers);
- assertNull(pri.broadcastUsers);
+ // populateUsers with nothing leaves nothing
+ pri.populateUsers(null, setting);
+ Assert.assertNull(pri.broadcastUsers);
- // populateUsers with nothing leaves nothing
- pri.populateUsers(null, setting);
- assertNull(pri.broadcastUsers);
+ // Create a real (non-null) PackageSetting and confirm that the removed
+ // users are copied properly
+ setting = new PackageSetting("name", "realName", new File("codePath"),
+ new File("resourcePath"), "legacyNativeLibraryPathString",
+ "primaryCpuAbiString", "secondaryCpuAbiString",
+ "cpuAbiOverrideString", 0, 0, 0, "parentPackageName", null, 0,
+ null, null);
+ pri.populateUsers(new int[] {
+ 1, 2, 3, 4, 5
+ }, setting);
+ Assert.assertNotNull(pri.broadcastUsers);
+ Assert.assertEquals(5, pri.broadcastUsers.length);
+ Assert.assertNotNull(pri.instantUserIds);
+ Assert.assertEquals(0, pri.instantUserIds.length);
- // Create a real (non-null) PackageSetting and confirm that the removed
- // users are copied properly
- setting = new PackageSetting("name", "realName", new File("codePath"),
- new File("resourcePath"), "legacyNativeLibraryPathString",
- "primaryCpuAbiString", "secondaryCpuAbiString",
- "cpuAbiOverrideString", 0, 0, 0, "parentPackageName", null, 0,
- null, null);
- pri.populateUsers(new int[] {1, 2, 3, 4, 5}, setting);
- assertNotNull(pri.broadcastUsers);
- assertEquals(5, pri.broadcastUsers.length);
+ // Exclude a user
+ pri.broadcastUsers = null;
+ final int EXCLUDED_USER_ID = 4;
+ setting.setInstantApp(true, EXCLUDED_USER_ID);
+ pri.populateUsers(new int[] {
+ 1, 2, 3, EXCLUDED_USER_ID, 5
+ }, setting);
+ Assert.assertNotNull(pri.broadcastUsers);
+ Assert.assertEquals(4, pri.broadcastUsers.length);
+ Assert.assertNotNull(pri.instantUserIds);
+ Assert.assertEquals(1, pri.instantUserIds.length);
- // Exclude a user
- pri.broadcastUsers = null;
- final int EXCLUDED_USER_ID = 4;
- setting.setInstantApp(true, EXCLUDED_USER_ID);
- pri.populateUsers(new int[] {1, 2, 3, EXCLUDED_USER_ID, 5}, setting);
- assertNotNull(pri.broadcastUsers);
- assertEquals(5 - 1, pri.broadcastUsers.length);
-
- // TODO: test that sendApplicationHiddenForUser() actually fills in
- // broadcastUsers
+ // TODO: test that sendApplicationHiddenForUser() actually fills in
+ // broadcastUsers
}
}
diff --git a/telephony/java/android/telephony/euicc/EuiccManager.java b/telephony/java/android/telephony/euicc/EuiccManager.java
index 973df31..176057d 100644
--- a/telephony/java/android/telephony/euicc/EuiccManager.java
+++ b/telephony/java/android/telephony/euicc/EuiccManager.java
@@ -15,8 +15,10 @@
*/
package android.telephony.euicc;
+import android.annotation.IntDef;
import android.annotation.Nullable;
import android.annotation.SdkConstant;
+import android.annotation.SystemApi;
import android.app.Activity;
import android.app.PendingIntent;
import android.content.Context;
@@ -29,6 +31,9 @@
import com.android.internal.telephony.euicc.IEuiccController;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
/**
* EuiccManager is the application interface to eUICCs, or eSIMs/embedded SIMs.
*
@@ -167,6 +172,35 @@
*/
public static final String META_DATA_CARRIER_ICON = "android.telephony.euicc.carriericon";
+ /**
+ * Euicc OTA update status which can be got by {@link #getOtaStatus}
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = {"EUICC_OTA_"}, value = {
+ EUICC_OTA_IN_PROGRESS,
+ EUICC_OTA_FAILED,
+ EUICC_OTA_SUCCEEDED,
+ EUICC_OTA_NOT_NEEDED,
+ EUICC_OTA_STATUS_UNAVAILABLE
+
+ })
+ public @interface OtaStatus{}
+
+ /**
+ * An OTA is in progress. During this time, the eUICC is not available and the user may lose
+ * network access.
+ */
+ public static final int EUICC_OTA_IN_PROGRESS = 1;
+ /** The OTA update failed. */
+ public static final int EUICC_OTA_FAILED = 2;
+ /** The OTA update finished successfully. */
+ public static final int EUICC_OTA_SUCCEEDED = 3;
+ /** The OTA update not needed since current eUICC OS is latest. */
+ public static final int EUICC_OTA_NOT_NEEDED = 4;
+ /** The OTA status is unavailable since eUICC service is unavailable. */
+ public static final int EUICC_OTA_STATUS_UNAVAILABLE = 5;
+
private final Context mContext;
/** @hide */
@@ -211,6 +245,26 @@
}
/**
+ * Returns the current status of eUICC OTA.
+ *
+ * <p>Requires the {@link android.Manifest.permission#WRITE_EMBEDDED_SUBSCRIPTIONS} permission.
+ *
+ * @return the status of eUICC OTA. If {@link #isEnabled()} is false or the eUICC is not ready,
+ * {@link OtaStatus#EUICC_OTA_STATUS_UNAVAILABLE} will be returned.
+ */
+ @SystemApi
+ public int getOtaStatus() {
+ if (!isEnabled()) {
+ return EUICC_OTA_STATUS_UNAVAILABLE;
+ }
+ try {
+ return getIEuiccController().getOtaStatus();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Attempt to download the given {@link DownloadableSubscription}.
*
* <p>Requires the {@link android.Manifest.permission#WRITE_EMBEDDED_SUBSCRIPTIONS} permission,
diff --git a/telephony/java/com/android/internal/telephony/RILConstants.java b/telephony/java/com/android/internal/telephony/RILConstants.java
index e2d25b8..f804cb0 100644
--- a/telephony/java/com/android/internal/telephony/RILConstants.java
+++ b/telephony/java/com/android/internal/telephony/RILConstants.java
@@ -417,6 +417,8 @@
int RIL_REQUEST_SET_CARRIER_INFO_IMSI_ENCRYPTION = 141;
int RIL_REQUEST_START_NETWORK_SCAN = 142;
int RIL_REQUEST_STOP_NETWORK_SCAN = 143;
+ int RIL_REQUEST_GET_SLOT_STATUS = 144;
+ int RIL_REQUEST_SET_LOGICAL_TO_PHYSICAL_SLOT_MAPPING = 145;
int RIL_RESPONSE_ACKNOWLEDGEMENT = 800;
@@ -471,4 +473,5 @@
int RIL_UNSOL_MODEM_RESTART = 1047;
int RIL_UNSOL_CARRIER_INFO_IMSI_ENCRYPTION = 1048;
int RIL_UNSOL_NETWORK_SCAN_RESULT = 1049;
+ int RIL_UNSOL_ICC_SLOT_STATUS = 1050;
}
diff --git a/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl b/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl
index b3fc90d..0a0ad90 100644
--- a/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl
+++ b/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl
@@ -30,6 +30,7 @@
oneway void getDefaultDownloadableSubscriptionList(
String callingPackage, in PendingIntent callbackIntent);
String getEid();
+ int getOtaStatus();
oneway void downloadSubscription(in DownloadableSubscription subscription,
boolean switchAfterDownload, String callingPackage, in PendingIntent callbackIntent);
EuiccInfo getEuiccInfo();
diff --git a/tests/DexLoggerIntegrationTests/Android.mk b/tests/DexLoggerIntegrationTests/Android.mk
new file mode 100644
index 0000000..7187a37
--- /dev/null
+++ b/tests/DexLoggerIntegrationTests/Android.mk
@@ -0,0 +1,49 @@
+#
+# Copyright 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+LOCAL_PATH:= $(call my-dir)
+
+# Build a tiny library that the test app can dynamically load
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+LOCAL_MODULE := DexLoggerTestLibrary
+LOCAL_SRC_FILES := $(call all-java-files-under, src/com/android/dcl)
+
+include $(BUILD_JAVA_LIBRARY)
+
+dexloggertest_jar := $(LOCAL_BUILT_MODULE)
+
+
+# Build the test app itself
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+LOCAL_PACKAGE_NAME := DexLoggerIntegrationTests
+LOCAL_COMPATIBILITY_SUITE := device-tests
+LOCAL_CERTIFICATE := platform
+LOCAL_SRC_FILES := $(call all-java-files-under, src/com/android/server/pm)
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ android-support-test \
+ truth-prebuilt \
+
+# This gets us the javalib.jar built by DexLoggerTestLibrary above.
+LOCAL_JAVA_RESOURCE_FILES := $(dexloggertest_jar)
+
+include $(BUILD_PACKAGE)
diff --git a/tests/DexLoggerIntegrationTests/AndroidManifest.xml b/tests/DexLoggerIntegrationTests/AndroidManifest.xml
new file mode 100644
index 0000000..a847e8f
--- /dev/null
+++ b/tests/DexLoggerIntegrationTests/AndroidManifest.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.frameworks.dexloggertest">
+
+ <!-- Tests feature introduced in P (27) -->
+ <uses-sdk
+ android:minSdkVersion="27"
+ android:targetSdkVersion="27" />
+
+ <uses-permission android:name="android.permission.READ_LOGS" />
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation
+ android:name="android.support.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.frameworks.dexloggertest"
+ android:label="Integration test for DexLogger" />
+</manifest>
diff --git a/tests/DexLoggerIntegrationTests/AndroidTest.xml b/tests/DexLoggerIntegrationTests/AndroidTest.xml
new file mode 100644
index 0000000..8ed19f8
--- /dev/null
+++ b/tests/DexLoggerIntegrationTests/AndroidTest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<configuration description="Runs DexLogger Integration Tests">
+ <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+ <option name="test-file-name" value="DexLoggerIntegrationTests.apk"/>
+ <option name="cleanup-apks" value="true"/>
+ </target_preparer>
+
+ <option name="test-suite-tag" value="apct"/>
+ <option name="test-tag" value="DexLoggerIntegrationTests"/>
+
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+ <option name="package" value="com.android.frameworks.dexloggertest"/>
+ <option name="runner" value="android.support.test.runner.AndroidJUnitRunner"/>
+ </test>
+</configuration>
diff --git a/tests/DexLoggerIntegrationTests/src/com/android/dcl/Simple.java b/tests/DexLoggerIntegrationTests/src/com/android/dcl/Simple.java
new file mode 100644
index 0000000..e995a26
--- /dev/null
+++ b/tests/DexLoggerIntegrationTests/src/com/android/dcl/Simple.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dcl;
+
+/** Dummy class which is built into a jar purely so we can pass it to DexClassLoader. */
+public final class Simple {
+ public Simple() {}
+}
diff --git a/tests/DexLoggerIntegrationTests/src/com/android/server/pm/DexLoggerIntegrationTests.java b/tests/DexLoggerIntegrationTests/src/com/android/server/pm/DexLoggerIntegrationTests.java
new file mode 100644
index 0000000..d9f34d5
--- /dev/null
+++ b/tests/DexLoggerIntegrationTests/src/com/android/server/pm/DexLoggerIntegrationTests.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.support.test.InstrumentationRegistry;
+import android.util.EventLog;
+
+import dalvik.system.DexClassLoader;
+
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.MessageDigest;
+import java.util.ArrayList;
+import java.util.Formatter;
+import java.util.List;
+
+/**
+ * Integration tests for {@link com.android.server.pm.dex.DexLogger}.
+ *
+ * The setup for the test dynamically loads code in a jar extracted
+ * from our assets (a secondary dex file).
+ *
+ * We then use adb to trigger secondary dex file reconcilation (and
+ * wait for it to complete). As a side-effect of this DexLogger should
+ * be notified of the file and should log the hash of the file's name
+ * and content. We verify that this message appears in the event log.
+ *
+ * Run with "atest DexLoggerIntegrationTests".
+ */
+@RunWith(JUnit4.class)
+public final class DexLoggerIntegrationTests {
+
+ private static final String TAG = DexLoggerIntegrationTests.class.getSimpleName();
+
+ private static final String PACKAGE_NAME = "com.android.frameworks.dexloggertest";
+
+ private static final int SNET_TAG = 0x534e4554;
+ private static final String DCL_SUBTAG = "dcl";
+
+ // Obtained via "echo -n copied.jar | sha256sum"
+ private static final String EXPECTED_NAME_HASH =
+ "1B6C71DB26F36582867432CCA12FB6A517470C9F9AABE9198DD4C5C030D6DC0C";
+
+ private static String expectedContentHash;
+
+ @BeforeClass
+ public static void setUpAll() throws Exception {
+ Context context = InstrumentationRegistry.getTargetContext();
+ MessageDigest hasher = MessageDigest.getInstance("SHA-256");
+
+ // Copy the jar from our Java resources to a private data directory
+ File privateCopy = new File(context.getDir("jars", Context.MODE_PRIVATE), "copied.jar");
+ try (InputStream input = DexLoggerIntegrationTests.class.getResourceAsStream("/javalib.jar");
+ OutputStream output = new FileOutputStream(privateCopy)) {
+ byte[] buffer = new byte[1024];
+ while (true) {
+ int numRead = input.read(buffer);
+ if (numRead < 0) {
+ break;
+ }
+ output.write(buffer, 0, numRead);
+ hasher.update(buffer, 0, numRead);
+ }
+ }
+
+ // Remember the SHA-256 of the file content to check that it is the same as
+ // the value we see logged.
+ Formatter formatter = new Formatter();
+ for (byte b : hasher.digest()) {
+ formatter.format("%02X", b);
+ }
+ expectedContentHash = formatter.toString();
+
+ // Feed the jar to a class loader and make sure it contains what we expect.
+ ClassLoader loader =
+ new DexClassLoader(
+ privateCopy.toString(), null, null, context.getClass().getClassLoader());
+ loader.loadClass("com.android.dcl.Simple");
+ }
+
+ @Test
+ public void testDexLoggerReconcileGeneratesEvents() throws Exception {
+ int[] tagList = new int[] { SNET_TAG };
+ List<EventLog.Event> events = new ArrayList<>();
+
+ // There may already be events in the event log - figure out the most recent one
+ EventLog.readEvents(tagList, events);
+ long previousEventNanos = events.isEmpty() ? 0 : events.get(events.size() - 1).getTimeNanos();
+ events.clear();
+
+ Process process = Runtime.getRuntime().exec(
+ "cmd package reconcile-secondary-dex-files " + PACKAGE_NAME);
+ int exitCode = process.waitFor();
+ assertThat(exitCode).isEqualTo(0);
+
+ int myUid = android.os.Process.myUid();
+ String expectedMessage = EXPECTED_NAME_HASH + " " + expectedContentHash;
+
+ EventLog.readEvents(tagList, events);
+ boolean found = false;
+ for (EventLog.Event event : events) {
+ if (event.getTimeNanos() <= previousEventNanos) {
+ continue;
+ }
+ Object[] data = (Object[]) event.getData();
+
+ // We only care about DCL events that we generated.
+ String subTag = (String) data[0];
+ if (!DCL_SUBTAG.equals(subTag)) {
+ continue;
+ }
+ int uid = (int) data[1];
+ if (uid != myUid) {
+ continue;
+ }
+
+ String message = (String) data[2];
+ assertThat(message).isEqualTo(expectedMessage);
+ found = true;
+ }
+
+ assertThat(found).isTrue();
+ }
+}
diff --git a/wifi/java/android/net/wifi/WifiScanner.java b/wifi/java/android/net/wifi/WifiScanner.java
index e3752ac..928a1da 100644
--- a/wifi/java/android/net/wifi/WifiScanner.java
+++ b/wifi/java/android/net/wifi/WifiScanner.java
@@ -160,6 +160,24 @@
*/
public static final int REPORT_EVENT_NO_BATCH = (1 << 2);
+ /**
+ * This is used to indicate the purpose of the scan to the wifi chip in
+ * {@link ScanSettings#type}.
+ * On devices with multiple hardware radio chains (and hence different modes of scan),
+ * this type serves as an indication to the hardware on what mode of scan to perform.
+ * Only apps holding android.Manifest.permission.NETWORK_STACK permission can set this value.
+ *
+ * Note: This serves as an intent and not as a stipulation, the wifi chip
+ * might honor or ignore the indication based on the current radio conditions. Always
+ * use the {@link ScanResult#radioChainInfos} to figure out the radio chain configuration used
+ * to receive the corresponding scan result.
+ */
+ /** {@hide} */
+ public static final int TYPE_LOW_LATENCY = 0;
+ /** {@hide} */
+ public static final int TYPE_LOW_POWER = 1;
+ /** {@hide} */
+ public static final int TYPE_HIGH_ACCURACY = 2;
/** {@hide} */
public static final String SCAN_PARAMS_SCAN_SETTINGS_KEY = "ScanSettings";
@@ -193,7 +211,8 @@
* list of hidden networks to scan for. Explicit probe requests are sent out for such
* networks during scan. Only valid for single scan requests.
* {@hide}
- * */
+ */
+ @RequiresPermission(android.Manifest.permission.NETWORK_STACK)
public HiddenNetwork[] hiddenNetworks;
/** period of background scan; in millisecond, 0 => single shot scan */
public int periodInMs;
@@ -223,6 +242,13 @@
* {@hide}
*/
public boolean isPnoScan;
+ /**
+ * Indicate the type of scan to be performed by the wifi chip.
+ * Default value: {@link #TYPE_LOW_LATENCY}.
+ * {@hide}
+ */
+ @RequiresPermission(android.Manifest.permission.NETWORK_STACK)
+ public int type = TYPE_LOW_LATENCY;
/** Implement the Parcelable interface {@hide} */
public int describeContents() {
@@ -239,6 +265,7 @@
dest.writeInt(maxPeriodInMs);
dest.writeInt(stepCount);
dest.writeInt(isPnoScan ? 1 : 0);
+ dest.writeInt(type);
if (channels != null) {
dest.writeInt(channels.length);
for (int i = 0; i < channels.length; i++) {
@@ -272,6 +299,7 @@
settings.maxPeriodInMs = in.readInt();
settings.stepCount = in.readInt();
settings.isPnoScan = in.readInt() == 1;
+ settings.type = in.readInt();
int num_channels = in.readInt();
settings.channels = new ChannelSpec[num_channels];
for (int i = 0; i < num_channels; i++) {
diff --git a/wifi/java/android/net/wifi/hotspot2/ProvisioningCallback.java b/wifi/java/android/net/wifi/hotspot2/ProvisioningCallback.java
index 8b86cdd..2ea6e79 100644
--- a/wifi/java/android/net/wifi/hotspot2/ProvisioningCallback.java
+++ b/wifi/java/android/net/wifi/hotspot2/ProvisioningCallback.java
@@ -26,13 +26,49 @@
*/
public abstract class ProvisioningCallback {
- /**
+ /**
* The reason code for Provisioning Failure due to connection failure to OSU AP.
* @hide
*/
public static final int OSU_FAILURE_AP_CONNECTION = 1;
/**
+ * The reason code for Provisioning Failure due to connection failure to OSU AP.
+ * @hide
+ */
+ public static final int OSU_FAILURE_SERVER_URL_INVALID = 2;
+
+ /**
+ * The reason code for Provisioning Failure due to connection failure to OSU AP.
+ * @hide
+ */
+ public static final int OSU_FAILURE_SERVER_CONNECTION = 3;
+
+ /**
+ * The reason code for Provisioning Failure due to connection failure to OSU AP.
+ * @hide
+ */
+ public static final int OSU_FAILURE_SERVER_VALIDATION = 4;
+
+ /**
+ * The reason code for Provisioning Failure due to connection failure to OSU AP.
+ * @hide
+ */
+ public static final int OSU_FAILURE_PROVIDER_VERIFICATION = 5;
+
+ /**
+ * The reason code for Provisioning Failure when a provisioning flow is aborted.
+ * @hide
+ */
+ public static final int OSU_FAILURE_PROVISIONING_ABORTED = 6;
+
+ /**
+ * The reason code for Provisioning Failure when a provisioning flow is aborted.
+ * @hide
+ */
+ public static final int OSU_FAILURE_PROVISIONING_NOT_AVAILABLE = 7;
+
+ /**
* The status code for Provisioning flow to indicate connecting to OSU AP
* @hide
*/
@@ -45,6 +81,24 @@
public static final int OSU_STATUS_AP_CONNECTED = 2;
/**
+ * The status code for Provisioning flow to indicate connecting to OSU AP
+ * @hide
+ */
+ public static final int OSU_STATUS_SERVER_CONNECTED = 3;
+
+ /**
+ * The status code for Provisioning flow to indicate connecting to OSU AP
+ * @hide
+ */
+ public static final int OSU_STATUS_SERVER_VALIDATED = 4;
+
+ /**
+ * The status code for Provisioning flow to indicate connecting to OSU AP
+ * @hide
+ */
+ public static final int OSU_STATUS_PROVIDER_VERIFIED = 5;
+
+ /**
* Provisioning status for OSU failure
* @param status indicates error condition
*/
diff --git a/wifi/java/android/net/wifi/rtt/ResponderConfig.java b/wifi/java/android/net/wifi/rtt/ResponderConfig.java
index 8be7782..c3e1007 100644
--- a/wifi/java/android/net/wifi/rtt/ResponderConfig.java
+++ b/wifi/java/android/net/wifi/rtt/ResponderConfig.java
@@ -37,7 +37,7 @@
*
* @hide (@SystemApi)
*/
-public class ResponderConfig implements Parcelable {
+public final class ResponderConfig implements Parcelable {
private static final int AWARE_BAND_2_DISCOVERY_CHANNEL = 2437;
/** @hide */
@@ -122,15 +122,14 @@
/**
* The MAC address of the Responder. Will be null if a Wi-Fi Aware peer identifier (the
* peerHandle field) ise used to identify the Responder.
- * TODO: convert to MacAddress
*/
- public MacAddress macAddress;
+ public final MacAddress macAddress;
/**
* The peer identifier of a Wi-Fi Aware Responder. Will be null if a MAC Address (the macAddress
* field) is used to identify the Responder.
*/
- public PeerHandle peerHandle;
+ public final PeerHandle peerHandle;
/**
* The device type of the Responder.
@@ -171,7 +170,7 @@
public final int preamble;
/**
- * Constructs Responder configuration.
+ * Constructs Responder configuration, using a MAC address to identify the Responder.
*
* @param macAddress The MAC address of the Responder.
* @param responderType The type of the responder device, specified using
@@ -210,7 +209,7 @@
}
/**
- * Constructs Responder configuration.
+ * Constructs Responder configuration, using a Wi-Fi Aware PeerHandle to identify the Responder.
*
* @param peerHandle The Wi-Fi Aware peer identifier of the Responder.
* @param responderType The type of the responder device, specified using
@@ -245,6 +244,45 @@
}
/**
+ * Constructs Responder configuration. This is an internal-only constructor which specifies both
+ * a MAC address and a Wi-Fi PeerHandle to identify the Responder.
+ *
+ * @param macAddress The MAC address of the Responder.
+ * @param peerHandle The Wi-Fi Aware peer identifier of the Responder.
+ * @param responderType The type of the responder device, specified using
+ * {@link ResponderType}.
+ * @param supports80211mc Indicates whether the responder supports IEEE 802.11mc.
+ * @param channelWidth Responder channel bandwidth, specified using {@link ChannelWidth}.
+ * @param frequency The primary 20 MHz frequency (in MHz) of the channel of the Responder.
+ * @param centerFreq0 Not used if the {@code channelWidth} is 20 MHz. If the Responder uses
+ * 40, 80 or 160 MHz, this is the center frequency (in MHz), if the
+ * Responder uses 80 + 80 MHz, this is the center frequency of the first
+ * segment (in MHz).
+ * @param centerFreq1 Only used if the {@code channelWidth} is 80 + 80 MHz. If the
+ * Responder
+ * uses 80 + 80 MHz, this is the center frequency of the second segment
+ * (in
+ * MHz).
+ * @param preamble The preamble used by the Responder, specified using
+ * {@link PreambleType}.
+ * @hide
+ */
+ public ResponderConfig(@NonNull MacAddress macAddress, @NonNull PeerHandle peerHandle,
+ @ResponderType int responderType, boolean supports80211mc,
+ @ChannelWidth int channelWidth, int frequency, int centerFreq0, int centerFreq1,
+ @PreambleType int preamble) {
+ this.macAddress = macAddress;
+ this.peerHandle = peerHandle;
+ this.responderType = responderType;
+ this.supports80211mc = supports80211mc;
+ this.channelWidth = channelWidth;
+ this.frequency = frequency;
+ this.centerFreq0 = centerFreq0;
+ this.centerFreq1 = centerFreq1;
+ this.preamble = preamble;
+ }
+
+ /**
* Creates a Responder configuration from a {@link ScanResult} corresponding to an Access
* Point (AP), which can be obtained from {@link android.net.wifi.WifiManager#getScanResults()}.
*/
diff --git a/wifi/tests/src/android/net/wifi/WifiScannerTest.java b/wifi/tests/src/android/net/wifi/WifiScannerTest.java
index e542789..a4366d4 100644
--- a/wifi/tests/src/android/net/wifi/WifiScannerTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiScannerTest.java
@@ -16,19 +16,23 @@
package android.net.wifi;
+import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.validateMockitoUsage;
import static org.mockito.Mockito.when;
import android.content.Context;
import android.os.Handler;
+import android.os.Parcel;
import android.os.test.TestLooper;
import android.test.suitebuilder.annotation.SmallTest;
+import android.net.wifi.WifiScanner.ScanSettings;
import com.android.internal.util.test.BidirectionalAsyncChannelServer;
import org.junit.After;
import org.junit.Before;
+import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -70,4 +74,50 @@
validateMockitoUsage();
}
+ /**
+ * Verify parcel read/write for ScanSettings.
+ */
+ @Test
+ public void verifyScanSettingsParcelWithBand() throws Exception {
+ ScanSettings writeSettings = new ScanSettings();
+ writeSettings.type = WifiScanner.TYPE_LOW_POWER;
+ writeSettings.band = WifiScanner.WIFI_BAND_BOTH_WITH_DFS;
+
+ ScanSettings readSettings = parcelWriteRead(writeSettings);
+ assertEquals(readSettings.type, writeSettings.type);
+ assertEquals(readSettings.band, writeSettings.band);
+ assertEquals(0, readSettings.channels.length);
+ }
+
+ /**
+ * Verify parcel read/write for ScanSettings.
+ */
+ @Test
+ public void verifyScanSettingsParcelWithChannels() throws Exception {
+ ScanSettings writeSettings = new ScanSettings();
+ writeSettings.type = WifiScanner.TYPE_HIGH_ACCURACY;
+ writeSettings.band = WifiScanner.WIFI_BAND_UNSPECIFIED;
+ writeSettings.channels = new WifiScanner.ChannelSpec[] {
+ new WifiScanner.ChannelSpec(5),
+ new WifiScanner.ChannelSpec(7)
+ };
+
+ ScanSettings readSettings = parcelWriteRead(writeSettings);
+ assertEquals(writeSettings.type, readSettings.type);
+ assertEquals(writeSettings.band, readSettings.band);
+ assertEquals(2, readSettings.channels.length);
+ assertEquals(5, readSettings.channels[0].frequency);
+ assertEquals(7, readSettings.channels[1].frequency);
+ }
+
+ /**
+ * Write the provided {@link ScanSettings} to a parcel and deserialize it.
+ */
+ private static ScanSettings parcelWriteRead(ScanSettings writeSettings) throws Exception {
+ Parcel parcel = Parcel.obtain();
+ writeSettings.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0); // Rewind data position back to the beginning for read.
+ return ScanSettings.CREATOR.createFromParcel(parcel);
+ }
+
}